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,读者可以通过切换分支来查看本文的示例代码。