06.Spring的新注解

2020-02-08  本文已影响0人  吃伏冒有礼貌

刚刚两个问题
第一个问题,测试类重复代码
第二个问题,xml和注解配置,xml文件都无法脱离,只能用在我们的类上,比如QueryRunner这个类,dbutil.jar包下的类,它是无法加上注解的.
既然想去掉bean.xml配置文件,那么我们需要一个相同功能的注解出现.
创建一个配置类,他的作用和bean.xml 是一样的

1.spring的新注解-Configuration和ComponentScan
@Configuration
@ComponentScan(value = "com.itheima")
public class SpringConfiguaration {
}
 <context:component-scan base-package="com.itheima"></context:component-scan>
ComponentScan.png
点进去可以看到用value属性和basePackages属性,value使用的别名是basePackages,basePackages的别名是value,所以写哪个都可以,如果注解的属性有且只有一个值的时候,不写也可以,basePackages的路径必须是类路径,另外可以看到属性的值是string类型,所以其实完整的写法应该是@ComponentScan(basePackages={"com.itheima"})
@Configuration
@ComponentScan(basePackages={"com.itheima"})
@Configuration
@ComponentScan(basePackages="com.itheima")
public class SpringConfiguaration {
2.spring的新注解-Bean

接下来我们需要考虑如何去掉在bean.xml中这剩下的部分


bean.xml

如果细心的话可以看到 红框标注的这一部分,这表示着可以用构造方法来创造对象,new Instance,不同的是一个需要参数,一个不需要
接下来我们在SpringConfiguation里继续配置,创建QueryRunner对象和DataSource对象

 /**
     * 创建一个QueryRunner对象
     * @return
     */    public QueryRunner createQueryrunner(DataSource dataSource){
            return new QueryRunner(dataSource);
    }
 /**
     * 创建一个QueryRunner对象
     * @return
     */    public QueryRunner createQueryrunner(DataSource dataSource){
            return new QueryRunner(dataSource);
    }

SpringConfiguation中createQueryrunner方法与xml中的配置作用是一样的吗???

    <!--配置QueryRunner-->
    <bean id="runner" class="org.apache.commons.dbutils.QueryRunner">
        <!--注入数据源-->
        <constructor-arg name="ds" ref="dataSource"></constructor-arg>
    </bean>

不是一样的
在xml中,spring容器创建了runner对象并存放于Spring容器中,但SpringConfiguation 只是创建了对象,但没有存放于容器中,于是相应的,我们需要用到一个能把对象存放spring容器的注解

 /**
     * 创建一个QueryRunner对象
     * @return
     */
    @Bean(name = "runner")
    public QueryRunner createQueryrunner(DataSource dataSource){
        return new QueryRunner(dataSource);
    }

再创建一个createDataSource方法,得到DataSource对象

 /**
     * 创建一个DataSource对象
     * @return
     */
    @Bean( name = "DataSource")
    public DataSource createDataSource(){
        ComboPooledDataSource ds = new ComboPooledDataSource();
        try {
            ds.setDriverClass("com.mysql.jdbc.Driver");
            ds.setJdbcUrl("jdbc:mysql://localhost:3306/eesy");
            ds.setUser("root");
            ds.setPassword("password");
            return ds;
        } catch (Exception e) {
            throw new RuntimeException();
        }
    }
3.AnnotationConfigApplicationContext的使用
       //获取容器
        ApplicationContext ac = new ClassPathXmlApplicationContext("beans.xml") ;
       //获取容器
        ApplicationContext ac = new AnnotationConfigApplicationContext(SpringConfiguaration.class);

要获取核心容器需要用到AnotationConfigApplication来获取核心容器.

4.spring的新注解-Import

至此,bean.xml需要配置的东西都用同功能的注解实现了.
如果把@Configuation 这个注解从SpringConfiguation类上注释掉,再运行testFindAll方法发现程序运行并不受影响,仍然可以成功.

注释掉的Configuration
这是因为 当配置类作为AnotationConfigApplicationContext对象创建参数时,该注解可以不写
但这不是绝对的,如果再建立一个config类,比如JDBCConfiguation ,此时把SpringConfiguation类置空
package jdbcconfig;
import com.mchange.v2.c3p0.ComboPooledDataSource;
import org.apache.commons.dbutils.QueryRunner;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.sql.DataSource;

/**
 * 数据库相关配置类
 */
@Configuration
public class JDBCConfiguation {
    /**
     * 创建一个QueryRunner对象
     * @return
     */
    @Bean(name = "runner")
    public QueryRunner createQueryrunner(DataSource dataSource){
        return new QueryRunner(dataSource);
    }

    /**
     * 创建一个DataSource对象
     * @return
     */
    @Bean( name = "DataSource")
    public DataSource createDataSource(){
        ComboPooledDataSource ds = new ComboPooledDataSource();
        try {
            ds.setDriverClass("com.mysql.jdbc.Driver");
            ds.setJdbcUrl("jdbc:mysql://localhost:3306/eesy");
            ds.setUser("root");
            ds.setPassword("password");
            return ds;
        } catch (Exception e) {
            throw new RuntimeException();
        }
    }
}

这个时候,JDBCConfiguation并没有打上Configuation的注解,其他类不变,包括Test类,运行程序是会报错的.

运行报错
这个类虽然写了注解,但是要扫描的包并没有扫描JDBCConfiguation的包,那么我们在SpringConfiguation加上JDBCConfiguation的包
加上JDBCConfiguation的包
此时再运行仍然报错,错误仍然为
image.png
这也就是说QueryRunner并没有被找到.虽然包扫描了,但是这个包并没有注解加载.ComponetScan在扫描包时,首先它得认为是个配置类,才会对里面的注解去扫描.所以我们需要使用Configuation这个注解.
加上Configuation这个注解,程序又能继续运行了
这也说明了 Configuation不写的情况,只有当配置类作为AnotationConfigApplicationContext对象创建参数时,该注解可以不写
所以在AnotationConfigApplicationContext再加上JDBCConfiguation.class时,Configuation可以不写.
在AnotationConfiguationContext加入JDBCConfiguation字节码

如果只想要SpringConfiguation作为主配置类,可以使用@Import注解,可以看到Idea有提示输入.class的value,选择JDBCConfiguation作为Import的配置类.


import JDBCConfiguation
import JDBCConfiguation

(运行时junit很慢,几次找不到com.mysql.jdbc.Driver,发现properties里用了引号,更正以后仍然加载不成功,才发现是数据库没启动)

5.spring的新注解-PropertySource

再想想代码里还可以有什么可以改造的地方,这里写死的地方可以再改造一下,把这些写死的内容单独写出来

可以改造的地方

在resources文件下,创建一个新的 JdbcConfig.properties,来放有关jdbc的配置

jdbc.driver = com.mysql.jdbc.Driver
jdbc.url =jdbc:mysql://localhost:3306/eesy
jdbc.user = root
jdbc.password = password

怎么去读取这些配置呢?可以在JDBCConfiguation里创建几个变量,用spring的El表达式和@Value注解

/**
 * 数据库相关配置类
 *
 */
public class JDBCConfiguation {
    @Value("${jdbc.driver}")
    private String jdbcDriver;
    @Value("${jdbc.url}")
    private String jdbcUrl;
    @Value("${jdbc.user}")
    private String jdbcUser;
    @Value("${jdbc.password}")
    private String jdbcPassword;
    /**
     * 创建一个QueryRunner对象
     * @return
     */
    @Bean(name = "runner")
    public QueryRunner createQueryrunner(DataSource dataSource){
        return new QueryRunner(dataSource);
    }

    /**
     * 创建一个DataSource对象
     * @return
     */
    @Bean( name = "DataSource")
    public DataSource createDataSource(){
        ComboPooledDataSource ds = new ComboPooledDataSource();
        try {         
            ds.setDriverClass(jdbcDriver);
            ds.setJdbcUrl(jdbcUrl);
            ds.setUser(jdbcUser);
            ds.setPassword(jdbcPassword);
            return ds;
        } catch (Exception e) {
            throw new RuntimeException();
        }
    }
}

怎样读取文件呢?就需要@PropertySource这个注解了,在SpringConfiguation类中添加@PropertySource注解

@Configuration
@ComponentScan(basePackages={"com.itheima"})
@Import(JDBCConfiguation.class)
@PropertySource("classpath:JdbcConfig.properties")
public class SpringConfiguaration {
}

在这段内容改造完成以后,纯注解的方式和不是纯注解的方式相比,并没有轻松很多,反而更费事.
实际开发中,注解和xml如何去选择,纯xml可以配,但是里面有些复杂性,但纯注解的配置,也很复杂,从这两点来说,选择有注解和有xml的方式更合适,如果这个类是已经写好的,存在于jar包中,用xml比较方便,如果是自己写的,用注解比较方便,用哪种方式更方便,就用哪种配置.


image.png
6.Qualifier注解的另一种用法

如果容器中有多个同类型的数据源呢?
我们再在JDBCConguation里创建一个数据源ds2

   /**
     * 创建一个QueryRunner对象
     * @return
     */
    @Bean(name = "runner")
    @Scope("prototype")
    public QueryRunner createQueryrunner( DataSource dataSource){
        return new QueryRunner(dataSource);
    }

    /**
     * 创建一个DataSource对象
     * @return
     */
    @Bean( name = "dataSource")
    public DataSource createDataSource(){
        ComboPooledDataSource ds = new ComboPooledDataSource();
        try {
            ds.setDriverClass(jdbcDriver);
            ds.setJdbcUrl(jdbcUrl);
            ds.setUser(jdbcUser);
            ds.setPassword(jdbcPassword);
            return ds;
        } catch (Exception e) {
            throw new RuntimeException();
        }
    }
    @Bean( name = "ds2")
    public DataSource createDataSource2(){
        ComboPooledDataSource ds = new ComboPooledDataSource();
        try {
            ds.setDriverClass("com.mysql.jdbc.Driver");
            ds.setJdbcUrl("jdbc:mysql://localhost:3306/eesy02");
            ds.setUser("root");
            ds.setPassword("password");
            return ds;
        } catch (Exception e) {
            throw new RuntimeException();
        }
    }

再在数据里创建一个数据库eesy02

mysql> create database eesy02;
Query OK, 1 row affected (0.02 sec)

mysql> use eesy02
Database changed
mysql> create table account(
    ->  id int primary key auto_increment,
    ->  name varchar(40),
    ->  money float
    -> )character set utf8 collate utf8_general_ci;
Query OK, 0 rows affected, 2 warnings (0.04 sec)

mysql>
mysql> insert into account(name,money) values('aaa',1000);
Query OK, 1 row affected (0.01 sec)

mysql> insert into account(name,money) values('bbb',1000);
Query OK, 1 row affected (0.00 sec)

mysql> insert into account(name,money) values('ccc',1000);
Query OK, 1 row affected (0.00 sec)

此时我们运行testFindAll方法,还是能够执行成功,这是因为spring在选择数据源的时候,有两个相同类型的数据源,它首先根据bean的id去选择,而此时有个数据源的bean id就为 dataSource,用形参作为bean的id查找到了


image.png

可以从运行结果看到它选择了eesy这个数据库

["Account{id=1,name='aaa,money=1000.0}, "Account{id=4,name='ccc,money=1000.0}, "Account{id=5,name='bbb,money=1000.0}, "Account{id=8,name='ddd,money=3000.0}]

此时把bean id 为dataSource改为bean id 为 ds1,形参dataSource作为bean的id已经不能从从两个同类型数据源里找到相匹配的了


此时把bean id 为dataSource改为bean id 为 ds1

运行testFindAll会发现运行报错,报错的原因是 No qualifying bean of type 'javax.sql.DataSource' available expected single matching bean but found 2,这就是以前讲到@Autowired时遇到的错误


No qualifying bean of type 'javax.sql.DataSource' available
要解决这个问题就要用到@Qualifier注解,曾经讲过它不能脱离@Autowired单独使用,这个不能单独使用是指不能再类成员上单独使用,但是可以在方法和变量上使用,
这也就是说这里其实是暗藏了一个@Autowired的功能,一开始先按照类型注入,没有类型匹配或者有多个类型匹配,并且形参无法在多个匹配的类型中找到符合名称的id时就会报错,在这种情况下,@Qualifier注解就起到了作用.

在实际开发中,确实存在一个对象有多个实现类的情况,如果发现在参数上出现了一个@Qualifier注解,不要太惊讶,这是允许存在的


image.png 再回顾一下@Qualifier注解
7.spring整合junit问题分析

一开始我们就说了,测试类存在大量重复字段.


重复代码

我们当然可以通过把 这两行抽出来分方式来精简代码,把变量写在方法外,再用init方法再test方法执行之前为它们赋值.


image.png
(老师在讲这一段的时候,提到测试工程师只想通过定义好了accountService对象就开始测试的前提,所以不需要我们写的那个init方法,姑且把它理解为如何用spring的方式精简这段代码吧)
删掉init代码,并打上@Autoired,注入IAccountService

在删掉init方法后,为IAccountService 打上@Autowired的注解,理论上Spring会自动按类型注入,但运行起来仍然为空指针异常,也就是说没有按照类型自动注入
为什么会这样呢?

解决的思路:让它在执行的时候创建容器
spring整合junit
第一步,导入整合spring整合junit的jar包spring-text
第二步,使用Junit提供的一个注解把原有的main方法替换了,替换成spring提供的
第三步,告诉spring的运行器,spring和ioc是基于xml还是注解,并且说明位置
在pom.xml加入spring-test的依赖

 <!-- https://mvnrepository.com/artifact/org.springframework/spring-test -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>5.1.5.RELEASE</version>
            <scope>test</scope>
        </dependency>

@Runwith注解
可以看到在Junit的核心runner的包下由个注解@Runwith,它就是
要替换Runner的运行器,运行器也就是带有main方法的类,接下来在Test方法上打上@Runwith注解,SpringJunitClassRunner 它继承了Junit的Runner类


可以看到@Runwith的属性value是要求是Runner的继承类
/**
 * 使用Junit单元测试
 */
@RunWith(SpringJUnit4ClassRunner.class)
public class AccountServiceTest {
    @Autowired
    private IAccountService accountService;

这个类是Spring提供的,它一定会为我们创建容器,前提是它得知道是注解还是xml的配置.
@ContextConfiguation
location:指定xml文件的位置,加上classpath关键字,表示在类路径下
classes:指定注解类所在的位置

注解的方式 bean.xml的方式
上一篇下一篇

猜你喜欢

热点阅读