Spring之新注解(六)

文章目录

(一)Configuration注解和ComponentScan注解
(二)Bean注解
(三)AnnotationConfigApplicationContext实现类
(四)Import注解
(五)PropertySource注解
(六)Qualifier注解的另一种用法
(七)Spring整合junit问题分析
(八)完成Spring整合junit

(一)Configuration和ComponentScan

我们目前似乎只能给我们自己写的类加注解
我们调用的QueryRunner类并不是我们自己的类,似乎不能加注解,只能用xml
Spring之新注解(六)_第1张图片
jar包里面的代码通常是不能修改的,我们一般也不去修改
Spring之新注解(六)_第2张图片
QueryRunner是如此,它所依赖的DataSource也是如此,无法加注解
只有加注解才能省掉那一大段xml,那有什么解决方案吗?

创建一个普通maven项目,并且把上一个项目的代码拷贝进去
Spring之新注解(六)_第3张图片
创建一个配置类,如下:
(该类的作用和bean.xml是一样的)
Spring之新注解(六)_第4张图片
首先介绍Configuration注解

作用:
	指定当前类是一个配置类
细节:
	当配置类作为AnnotationConfigApplicationContext对象创建的参数时,该注解可以不写
	(后面再介绍这个细节)

Spring之新注解(六)_第5张图片
然后介绍ComponentScan注解

作用:
	用于通过注解指定spring在创建容器时要扫描的包
属性:
	value:它和basePackages的作用是一样的,都是用于指定创建容器时要扫描的包
	我们使用此注解就等同于在xml中配置了:
		

此注解的属性有两个:valuebasePackages,并且互为别名
Spring之新注解(六)_第6张图片
我们使用其中一个即可,并且用value的话还可以使用省略写法
并且由于数组中只有一个元素,所以可以省略花括号
Spring之新注解(六)_第7张图片
此时我们可以删掉这行
Spring之新注解(六)_第8张图片

(二)Bean注解

我们先看看QueryRunner对象,我们发现它是用构造函数注入的,并且是带参数的
Spring之新注解(六)_第9张图片
那我们能不能自己写一个构造函数去初始化对象呢?
Spring之新注解(六)_第10张图片
可以看到自己写的话是实现不了的,我们需要一个注解帮我们把自己创建的对象存入Spring容器中
接下来介绍Bean注解

作用:
	用于把当前方法的返回值作为bean对象存入spring的ioc容器中
属性:
	name:用于指定bean的id。当不写时,默认值是当前方法的名称
细节:
	当我们使用注解配置方法时,如果方法有参数,spring框架会去容器中查找有没有可用的bean对象
	查找的方式和Autowired注解的作用是一样的

注意:细节如上面所描述的那样,如果方法有参数,spring会到容器去找有没有对应的对象,如果没有则报错(其实规则跟Autowired注解一样,至于多种匹配的情况就不详细介绍了)
Spring之新注解(六)_第11张图片
我们把DataSource对象也配置上,报错就消失了
Spring之新注解(六)_第12张图片
此时这一段代码也可以去掉了
Spring之新注解(六)_第13张图片

(三)AnnotationConfigApplicationContext实现类

虽然bean.xml已经没有什么用处了,但是如果我们直接删掉是会报错的,如下:
Spring之新注解(六)_第14张图片
我们的测试类还是依赖于bean.xml
我们回想之前讲ApplicationContext实现类的时候,还有最后一种没有讲,现在可以讲了
Spring之新注解(六)_第15张图片
进去看看它的构造方法
Spring之新注解(六)_第16张图片
替换成AnnotationConfigApplicationContext即可
Spring之新注解(六)_第17张图片
虽然成功运行了,但是有一些问题还是要注意的:

曾经的QueryRunner是多例的,但是现在的是单例的
Spring之新注解(六)_第18张图片
因为spring默认就是单例的(见之前的博客)
Spring之新注解(六)_第19张图片
那我们要怎么改成多例呢?
Spring之新注解(六)_第20张图片
还有一个细节,之前也提到过,如下图:
Spring之新注解(六)_第21张图片
我们验证一下
Spring之新注解(六)_第22张图片
但是并不是所有情况下都能省略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注解的,如下:
Spring之新注解(六)_第23张图片
我们尝试运行测试类,发现报错
在这里插入图片描述
因为我们要扫描的包com.zzq里没有包括刚才创建的JdbcConfig
Spring之新注解(六)_第24张图片
我们之前说过ComponentScan注解的属性是一个数组,我们加多一个包路径试试
Spring之新注解(六)_第25张图片
结果也是报错的
因为我们之前提到过:包在扫描类时,只有确定这个类是配置类,才对里面的注解进行扫描

那怎么样才能被spring认为是配置类呢?当然是加上Configuration注解啦
SpringConfiguration加不加该注解无所谓,我们给JdbcConfig加上即可,如下:
Spring之新注解(六)_第26张图片
其实如果实在不想写Configuration注解,可以这样子(把Configuration注解都注释掉)
Spring之新注解(六)_第27张图片
总结
一共有两种方法:

  1. SpringConfiguration加上包路径:@ComponentScan({"com.zzq","config"})
    并且在JdbcConfig加上@Configuration注解
    缺点:配置太繁琐
  2. 不需要加包路径,也不需要加注解,只需要创建实现类时传入字节码文件
    缺点:这样看起来两个配置类似乎没有大小之分,我们的想法是让SpringConfiguration作为最顶层的配置类,然后再给它加一些底层一点的配置类
ApplicationContext ac = new AnnotationConfigApplicationContext(SpringConfiguration.class, JdbcConfig.class);

那有没有办法解决以上问题呢?这就需要用到Import注解了

(四)Import注解

作用:
	用于导入其他的配置类
属性:
	value:用于指定其他配置类的字节码
细节:
	当我们使用Import的注解之后,有Import注解的类就父配置类,而导入的都是子配置类

我们此时不需要配置包路径不需要加Configuration注解,只需要在父配置类上添加Import注解
Spring之新注解(六)_第28张图片
运行结果是成功的,这里不再展示了

同时Import注解的属性是数组,可以加很多个值
Spring之新注解(六)_第29张图片

(五)PropertySource注解

作用:
	用于指定properties文件的位置
属性:
	value:指定文件的名称和路径
关键字:
	classpath:表示类路径下

我们的目标是把连接池对象的配置信息分离出来,如下:
Spring之新注解(六)_第30张图片
我们创建一个jdbcConfig.properties配置文件,如下:
Spring之新注解(六)_第31张图片
然后新建一些成员变量
Spring之新注解(六)_第32张图片
此时用我们之前学过的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注解跟配置文件关联起来
Spring之新注解(六)_第33张图片
其中的类路径如下:
(如果配置文件是放在包下,可以根据实际情况修改classpath后面的值,如果是放在target下的classes的根目录,则不需要加包名,直接指定文件即可)
Spring之新注解(六)_第34张图片
测试类运行结果也是没问题的,这里不再展示运行结果了

(六)Qualifier注解的另一种用法

我们再创建一个新的数据库,并且创建一张一模一样的表单
Spring之新注解(六)_第35张图片
并且在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之新注解(六)_第36张图片
也就说spring会优先匹配注解属性的值形参的变量名一样的DataSource构造方法
Spring之新注解(六)_第37张图片
如果没有与之匹配的,就报错,如下:
Spring之新注解(六)_第38张图片
解决方法其实跟之前一样,我们可以改变形参的变量名,如下:
Spring之新注解(六)_第39张图片
无论是改变注解属性的值还是改变形参的变量名都非常的麻烦,这时候就需要用到Qualifier注解
也就是它的第二种用法:给方法参数注入时,可以单独使用
(第一种用法见:Spring之常用的注解(四))
Spring之新注解(六)_第40张图片
到这里为止,我们可以通过改变Qualifier注解属性的值而改变访问的数据库,非常的方便

(七)Spring整合junit问题分析

这个问题之前也提到过,测试类的代码重复率太高
我们可以通过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注解自动注入所需依赖,发现抛出了空指针异常
Spring之新注解(六)_第41张图片
分析

  1. 应用程序的入口:main方法
  2. junit单元测试中为什么没有main方法也能执行?
    因为junit集成了一个main方法
    该方法会判断当前测试类中哪些方法有Test注解
    junit会让有Test注解的方法执行(使用method.involve)
  3. junit不会管我们是否采用了spring框架(或者说无法感知到)
    在测试方法时,junit根本不知道我们是不是使用了spring框架,所以也就不会为我们读取配置文件/配置类创建spring的ioc容器

由以上三点可知:
当测试方法执行时,没有ioc容器,就算写了Autowired注解也无法实现注入

(八)完成Spring整合junit

似乎可行的解决方案:我们手动创建一个ioc容器
Spring之新注解(六)_第42张图片
但是我们无法修改junit的字节码文件(.class结尾的)

所以spring帮我们整合了junit,为我们提供了解决方案
步骤如下:

  1. 我们先导入spring整合junit的maven坐标
	<dependency>
	    <groupId>org.springframeworkgroupId>
	    <artifactId>spring-testartifactId>
	    <version>5.0.2.RELEASEversion>
	dependency>
  1. 使用junit提供的一个注解(@Runwith)把原有的main方法替换成spring提供的main方法
    Spring之新注解(六)_第43张图片
    注解的功能是替换Runner(运行器)
    Spring之新注解(六)_第44张图片

  2. 告知spring的运行器,spring和ioc创建是基于xml还是注解的,并且说明位置

    使用ContextConfiguration注解说明
    	locations:
    			指定xml文件的位置,加上classpath关键字,表示在类路径下
    	classes:
    			指定注解类所在地位置
    

Spring之新注解(六)_第45张图片
注意:当我们使用spring 5.x版本的时候,要求junit的jar必须是4.12及以上

到目前为止,已经全部写完了,功能都可以正常测试了,但是这只是基于注解配置类
我们再看一个基于xml配置文件的,我们打开之前的xml案例项目
Spring之新注解(六)_第46张图片

你可能感兴趣的:(Spring,java,spring,spring,boot,mysql,编程语言)