IoC 的纯注解配置
# 45.IoC 的纯注解配置
在上一篇博客中,基于注解的 IoC 配置已经完成,但是大家都发现了一个问题:我们依然离不开 Spring 的 XML 配置文件,另外,数据源和 JdbcTemplate 的配置也需要靠注解来实现。那么能不能不写这个 bean.xml,所有配置都用注解来实现呢?
# @Configuration
我们新建一个配置类:(类名和包名随意取)
package com.peterjxl.config;
public class SpringConfiguration {}
2
接下来,我们就可以用 Spring 提供的注解,来替代 bean.xml 文件了。我们使用@Configuration 注解,该注解用于指定当前类是一个 Spring 配置类,当创建容器时会从该类上加载注解:
@Configuration
public class SpringConfiguration {}
2
# @ComponentScan
我们已经把配置文件用类来代替了,但是如何配置创建容器时要扫描的包呢?用 @ComponentScan
。该注解用于通过注解指定 Spring 在创建容器时要扫描的包,作用和在 Spring 的 XML 配置文件中的配置扫描包是一样的:
<context:component-scan base-package="com.peterjxl"/>
@ComponentScan
的属性:basePackages,用于指定创建容器时要扫描的包:
@Configuration
@ComponentScan(basePackages = {"com.peterjxl"})
public class SpringConfiguration {}
2
3
其实在 ComponentScan 的源码中,basePackages 是 value 属性的别名:
@AliasFor("basePackages")
String[] value() default {};
@AliasFor("value")
String[] basePackages() default {};
2
3
4
5
AliasFor 就是别名的意思,所以使用该注解时,使用 value 属性和 basePackages 是一样的,他们互为别名。
因此,该注解的写法可以简写为:
@Configuration
@ComponentScan("com.peterjxl")
public class SpringConfiguration {}
2
3
注意:可以配置多个要扫描的包,这里由于只有一个,因此简写了下,没有大括号 {}。
# @Bean 注入容器
现在,bean.xml 文件中,还剩下 QueryRunner 和 DataSource 没有使用注解配置。由于这几个都是第三方依赖,我们很难在里面加上注解;
此时我们可以用@Bean 注解:
- 作用:该注解只能写在方法上,表明使用此方法创建一个对象,并且放入 Spring 容器。
- 属性 : name 属性,用于给当前@Bean 注解方法创建的对象指定一个名称(即 bean 的 id)。默认值是当前方法的名称
至此,我们就可以新建方法,创造这些对象了:
@Configuration
@ComponentScan("com.peterjxl")
public class SpringConfiguration {
@Bean(name = "runner")
public QueryRunner createQueryRunner(DataSource dataSource) {
return new QueryRunner(dataSource);
}
@Bean(name = "dataSource")
public DataSource createDataSource() {
try {
ComboPooledDataSource ds = new ComboPooledDataSource();
ds.setDriverClass("com.mysql.jdbc.Driver");
ds.setJdbcUrl("jdbc:mysql://localhost:3306/learnSpring");
ds.setUser("learnSpringUser");
ds.setPassword("learnSpringPassword");
return ds;
} catch (PropertyVetoException e) {
throw new RuntimeException(e);
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
我们已经把数据源从配置文件中移除了,此时可以删除 bean.xml 了。
# 获取容器
我们之前讲过,ApplicationContext 有一个实现类是基于注解的:AnnotationConfigApplicationContext
因此,我们获取容器就是用这个实现类,我们修改下测试方法:
@Test
public void testFindAll() {
ApplicationContext ac = new AnnotationConfigApplicationContext(SpringConfiguration.class);
IAccountService as = ac.getBean("accountService", IAccountService.class);
List<Account> allAccount = as.findAllAccount();
for (Account account : allAccount) {
System.out.println(account);
}
}
2
3
4
5
6
7
8
9
其他测试方法同理,改下获取容器的代码,就可以运行其他测试方法了。
# 配置多例模式
我们可以测试下,QueryRunner 是否多例模式:
public class QueryRunnerTest {
@Test
public void testQueryRunner() {
ApplicationContext ac = new AnnotationConfigApplicationContext(SpringConfiguration.class);
// 获取 QueryRunner 对象
QueryRunner runner = ac.getBean("runner", QueryRunner.class);
QueryRunner runner2 = ac.getBean("runner", QueryRunner.class);
System.out.println(runner == runner2);
}
}
2
3
4
5
6
7
8
9
10
11
12
由于我们并没有配置是否多例,所以运行结果是 true。为此,我们需要加上一个 scope 注解:
public class SpringConfiguration {
@Bean(name = "runner")
@Scope("prototype")
public QueryRunner createQueryRunner(DataSource dataSource) {
return new QueryRunner(dataSource);
}
}
2
3
4
5
6
7
8
此时我们再次运行测试方法,可以看到输出了 false,也就是目前是多例模式了。
# @Configuration 的细节
当我们的配置类,作为容器对象创建的参数时,其实不写@Configuration 也可以:
//@Configuration
@ComponentScan(basePackages = "com.peterjxl")
public class SpringConfiguration {}
2
3
那么什么时候必须写呢?有多个配置类的情况。例如,我们的项目中配置是有很多的,单个配置类难以维护,我们可以新建一个配置类:
@Configuration
public class JdbcConfig {
@Bean(name = "runner")
@Scope("prototype")
public QueryRunner createQueryRunner(DataSource dataSource) {
return new QueryRunner(dataSource);
}
@Bean(name = "dataSource")
public DataSource createDataSource() {
try {
ComboPooledDataSource ds = new ComboPooledDataSource();
ds.setDriverClass("com.mysql.jdbc.Driver");
ds.setJdbcUrl("jdbc:mysql://localhost:3306/learnSpring");
ds.setUser("learnSpringUser");
ds.setPassword("learnSpringPassword");
return ds;
} catch (PropertyVetoException e) {
throw new RuntimeException(e);
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
注意,此时必须加上@Configuration 注解,不然扫描的时候,Spring 不会认为该类是一个配置类,也就不会扫描里面的方法,并创建对象放入容器里。
综上所述,主要当配置类是作为容器创建的参数的时候,才不用写@Configuration。当然,创建容器的时候支持传入多个配置类对象,此时两个配置类都不用写@Configuration:
ApplicationContext ac = new AnnotationConfigApplicationContext(SpringConfiguration.class, JdbcConfig.class);
但这样,这两个配置类是平级的;而我们想要的是,SpringConfiguration 是一个父配置,其他都是子配置,这时候就需要@Import
# @Import
我们可以使用@Import 注解,将配置类分成几个。
作用:用于导入其他的配置类
属性:value 用于指定其他配置类的字节码,可以是一个字节码数组,本例中只有一个。
当我们使用 Import 的注解之后,有 Import 注解的类就父配置类,而导入的都是子配置类
@Configuration
@ComponentScan(basePackages = "com.itheima.spring")
@Import(JdbcConfig.class)
public class SpringConfiguration {
2
3
4
# @PropertySource
在数据源信息的配置类中,我们的数据库地址、用户密码都是放在代码中的;如果要修改,还得重新编译一次代码,不太方便。此时我们可以用@Property 注解,导入配置文件中的数据。
作用:用于加载.properties 文件中的配置。我们可以把连接数据库的信息写到 properties 配置文件中,就可以使用此注解指定 properties 配置文件的位置。
属性 : value[]
,用于指定 properties 文件位置。如果是在类路径下,需要写上 classpath。
我们在 resources 目录下新建 jdbcConfig.properties 文件:
jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/learnSpring
jdbc.username=learnSpringUser
jdbc.password=learnSpringPassword
2
3
4
然后我们使用@PropertySource 注解,指定配置文件的位置;并且新建一些成员变量,使用@Value 和 EL 表达式,读取配置文件的内容并注入:
@PropertySource("classpath:jdbcConfig.properties") //注意不能用空格
public class JdbcConfig {
@Value("${jdbc.driver}")
private String driver;
@Value("${jdbc.url}")
private String url;
@Value("${jdbc.username}")
private String username;
@Value("${jdbc.password}")
private String password;
@Bean(name = "runner")
@Scope("prototype")
public QueryRunner createQueryRunner(DataSource dataSource) {
return new QueryRunner(dataSource);
}
@Bean(name = "dataSource")
public DataSource createDataSource() {
try {
ComboPooledDataSource ds = new ComboPooledDataSource();
ds.setDriverClass(driver);
ds.setJdbcUrl(url);
ds.setUser(username);
ds.setPassword(password);
return ds;
} catch (PropertyVetoException e) {
throw new RuntimeException(e);
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
# 总结
使用注解配置,并没有比使用 XML 配置更简便一些,特别是对于第三方依赖的情况。此时可能既使用 XML,也使用注解是不错的选择。
关于实际的开发中到底使用 XML 还是注解,则得具体情况具体分析了,一般哪个更方便,就用哪个。
至此,关于 IoC 的概念,我们基本讲完了。
# 源码
本项目已将源码上传到 GitHub (opens new window) 和 Gitee (opens new window) 上。并且创建了分支 demo7,读者可以通过切换分支来查看本文的示例代码。