(一)Configuration注解和ComponentScan注解
(二)Bean注解
(三)AnnotationConfigApplicationContext实现类
(四)Import注解
(五)PropertySource注解
(六)Qualifier注解的另一种用法
(七)Spring整合junit问题分析
(八)完成Spring整合junit
我们目前似乎只能给我们自己写的类加注解
我们调用的QueryRunner类并不是我们自己的类,似乎不能加注解,只能用xml
jar包里面的代码通常是不能修改的,我们一般也不去修改
QueryRunner是如此,它所依赖的DataSource也是如此,无法加注解
只有加注解才能省掉那一大段xml,那有什么解决方案吗?
创建一个普通maven项目,并且把上一个项目的代码拷贝进去
创建一个配置类,如下:
(该类的作用和bean.xml是一样的)
首先介绍Configuration注解
作用:
指定当前类是一个配置类
细节:
当配置类作为AnnotationConfigApplicationContext对象创建的参数时,该注解可以不写
(后面再介绍这个细节)
作用:
用于通过注解指定spring在创建容器时要扫描的包
属性:
value:它和basePackages的作用是一样的,都是用于指定创建容器时要扫描的包
我们使用此注解就等同于在xml中配置了:
此注解的属性有两个:value和basePackages,并且互为别名
我们使用其中一个即可,并且用value的话还可以使用省略写法
并且由于数组中只有一个元素,所以可以省略花括号
此时我们可以删掉这行
我们先看看QueryRunner对象,我们发现它是用构造函数注入的,并且是带参数的
那我们能不能自己写一个构造函数去初始化对象呢?
可以看到自己写的话是实现不了的,我们需要一个注解帮我们把自己创建的对象存入Spring容器中
接下来介绍Bean注解
作用:
用于把当前方法的返回值作为bean对象存入spring的ioc容器中
属性:
name:用于指定bean的id。当不写时,默认值是当前方法的名称
细节:
当我们使用注解配置方法时,如果方法有参数,spring框架会去容器中查找有没有可用的bean对象
查找的方式和Autowired注解的作用是一样的
注意:细节如上面所描述的那样,如果方法有参数,spring会到容器去找有没有对应的对象,如果没有则报错(其实规则跟Autowired注解一样,至于多种匹配的情况就不详细介绍了)
我们把DataSource对象也配置上,报错就消失了
此时这一段代码也可以去掉了
虽然bean.xml已经没有什么用处了,但是如果我们直接删掉是会报错的,如下:
我们的测试类还是依赖于bean.xml的
我们回想之前讲ApplicationContext实现类的时候,还有最后一种没有讲,现在可以讲了
进去看看它的构造方法
替换成AnnotationConfigApplicationContext即可
虽然成功运行了,但是有一些问题还是要注意的:
曾经的QueryRunner是多例的,但是现在的是单例的
因为spring默认就是单例的(见之前的博客)
那我们要怎么改成多例呢?
还有一个细节,之前也提到过,如下图:
我们验证一下
但是并不是所有情况下都能省略Configuration注解的,下面这种情况就不能省略
如果觉得初始化代码全部都写在SpringConfiguration一个类中太过于臃肿,我们可以分开写
比如说创建一个JdbcConfig专门用来初始化连接池对象
/**
* 和spring链接数据库相关的配置类
*/
public class JdbcConfig {
/**
* 用于创建一个QueryRunner对象
*
* @param dateSource
* @return
*/
@Bean(name = "runner")
@Scope("prototype")
public QueryRunner createQueryRunner(DataSource dataSource) {
return new QueryRunner(dateSource);
}
/**
* 创建数据源对象
*
* @return
*/
@Bean(name = "dataSource")
public DataSource createDataSource() {
try {
ComboPooledDataSource ds = new ComboPooledDataSource();
ds.setDriverClass("com.mysql.cj.jdbc.Driver");
ds.setJdbcUrl("jdbc:mysql://localhost:3306/test?useSSL=true&serverTimezone=UTC");
ds.setUser("root");
ds.setPassword("root");
return ds;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
此时的SpringConfiguration是注释掉Configuration注解的,如下:
我们尝试运行测试类,发现报错
因为我们要扫描的包com.zzq
里没有包括刚才创建的JdbcConfig
我们之前说过ComponentScan注解的属性是一个数组,我们加多一个包路径试试
结果也是报错的
因为我们之前提到过:包在扫描类时,只有确定这个类是配置类,才对里面的注解进行扫描
那怎么样才能被spring认为是配置类呢?当然是加上Configuration注解啦
SpringConfiguration加不加该注解无所谓,我们给JdbcConfig加上即可,如下:
其实如果实在不想写Configuration注解,可以这样子(把Configuration注解都注释掉)
总结
一共有两种方法:
@ComponentScan({"com.zzq","config"})
@Configuration
注解ApplicationContext ac = new AnnotationConfigApplicationContext(SpringConfiguration.class, JdbcConfig.class);
那有没有办法解决以上问题呢?这就需要用到Import注解了
作用:
用于导入其他的配置类
属性:
value:用于指定其他配置类的字节码
细节:
当我们使用Import的注解之后,有Import注解的类就父配置类,而导入的都是子配置类
我们此时不需要配置包路径,不需要加Configuration注解,只需要在父配置类上添加Import注解
运行结果是成功的,这里不再展示了
作用:
用于指定properties文件的位置
属性:
value:指定文件的名称和路径
关键字:
classpath:表示类路径下
我们的目标是把连接池对象的配置信息分离出来,如下:
我们创建一个jdbcConfig.properties配置文件,如下:
然后新建一些成员变量
此时用我们之前学过的Value注解即可使用spring的EL表达式获取值
@Value("${jdbc.driver}")
private String driver;
@Value("${jdbc.url}")
private String url;
@Value("${jdbc.username}")
private String username;
@Value("${jdbc.password}")
private String password;
我们通过PropertySource注解跟配置文件关联起来
其中的类路径如下:
(如果配置文件是放在包下,可以根据实际情况修改classpath后面的值,如果是放在target下的classes的根目录,则不需要加包名,直接指定文件即可)
测试类运行结果也是没问题的,这里不再展示运行结果了
我们再创建一个新的数据库,并且创建一张一模一样的表单
并且在JdbcConfig配置类中增加一段代码,如下:
@Bean(name = "d1")
public DataSource createDataSource1() {
try {
ComboPooledDataSource ds = new ComboPooledDataSource();
ds.setDriverClass(driver);
ds.setJdbcUrl("jdbc:mysql://localhost:3306/test01?useSSL=true&serverTimezone=UTC");
ds.setUser(username);
ds.setPassword(password);
return ds;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
这个时候运行,得出5条数据,也就是说访问了test(旧的)数据库
也就说spring会优先匹配注解属性的值与形参的变量名一样的DataSource构造方法
如果没有与之匹配的,就报错,如下:
解决方法其实跟之前一样,我们可以改变形参的变量名,如下:
无论是改变注解属性的值还是改变形参的变量名都非常的麻烦,这时候就需要用到Qualifier注解
也就是它的第二种用法:给方法参数注入时,可以单独使用
(第一种用法见:Spring之常用的注解(四))
到这里为止,我们可以通过改变Qualifier注解属性的值而改变访问的数据库,非常的方便
这个问题之前也提到过,测试类的代码重复率太高
我们可以通过junit提供的Before注解来实现代码的复用,如下:
private ApplicationContext ac;
private IAccountService as;
@Before
public void init() {
//1.获取容器
ac = new AnnotationConfigApplicationContext(SpringConfiguration.class);
//2.得到业务层对象
as = ac.getBean("accountService", IAccountService.class);
}
@Test
public void testFindAll() {
//3.执行方法
List<Account> accounts = as.findAllAccount();
for (Account account : accounts) {
System.out.println(account);
}
}
这些代码软件工程师当然可以编写没问题,但是测试工程师不一定可以写的出来
所以spring专门为测试工程师整合了junit,简化了操作
我们尝试用Autowired注解自动注入所需依赖,发现抛出了空指针异常
分析:
由以上三点可知:
当测试方法执行时,没有ioc容器,就算写了Autowired注解也无法实现注入
似乎可行的解决方案:我们手动创建一个ioc容器
但是我们无法修改junit的字节码文件(.class结尾的)
所以spring帮我们整合了junit,为我们提供了解决方案
步骤如下:
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-testartifactId>
<version>5.0.2.RELEASEversion>
dependency>
使用junit提供的一个注解(@Runwith)把原有的main方法替换成spring提供的main方法
该注解的功能是替换Runner(运行器)
告知spring的运行器,spring和ioc创建是基于xml还是注解的,并且说明位置
使用ContextConfiguration注解说明
locations:
指定xml文件的位置,加上classpath关键字,表示在类路径下
classes:
指定注解类所在地位置
注意:当我们使用spring 5.x版本的时候,要求junit的jar必须是4.12及以上
到目前为止,已经全部写完了,功能都可以正常测试了,但是这只是基于注解配置类的
我们再看一个基于xml配置文件的,我们打开之前的xml案例项目