06.Spring的新注解

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

1.spring的新注解-Configuration和ComponentScan
@Configuration
@ComponentScan(value = "com.itheima")
public class SpringConfiguaration {
}
  • @Configuration
    作用:指定该类是一个配置类
  • @ComponentScan
    作用:用于通过注解指定spring在创建容器时要扫描的包
    属性:value:它的作用和basepackage的作用是一样的,都是用于指定容器创建时要扫描的包.
    使用它相当于在xml中配置了
 

06.Spring的新注解_第1张图片
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中这剩下的部分


06.Spring的新注解_第2张图片
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中的配置作用是一样的吗???

    
    
        
        
    

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

  • @Bean
    作用:用于把当前方法的返回值作为bean对象存入Ioc容器中
    属性:用于指定当前bean的id,当不写时,默认值是当前方法的名称.
    细节:当我们使用注解配置方法时,如果方法有参数,spring框架会去容器中查找有没有可用的bean对象,查找的方式和AutoWried注解的作用是一样的
 /**
     * 创建一个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的使用
  • AnnotationConfigApplicationContext
    用注解取代beam.xml,获取spring核心容器的时候,ApplicationContext ac = new ClassPathXmlApplicationContext("beans.xml") ;很显然我们不需要再读取xml中的配置,所以这个写法不再有效
       //获取容器
        ApplicationContext ac = new ClassPathXmlApplicationContext("beans.xml") ;
       //获取容器
        ApplicationContext ac = new AnnotationConfigApplicationContext(SpringConfiguaration.class);

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

4.spring的新注解-Import

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

06.Spring的新注解_第3张图片
注释掉的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的包
06.Spring的新注解_第4张图片
加上JDBCConfiguation的包

此时再运行仍然报错,错误仍然为
image.png

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

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

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


06.Spring的新注解_第6张图片
import JDBCConfiguation

06.Spring的新注解_第7张图片
import JDBCConfiguation

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

5.spring的新注解-PropertySource

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

06.Spring的新注解_第8张图片
可以改造的地方

在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比较方便,如果是自己写的,用注解比较方便,用哪种方式更方便,就用哪种配置.


06.Spring的新注解_第9张图片
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查找到了


06.Spring的新注解_第10张图片
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已经不能从从两个同类型数据源里找到相匹配的了


06.Spring的新注解_第11张图片
此时把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注解,不要太惊讶,这是允许存在的


06.Spring的新注解_第12张图片
image.png
06.Spring的新注解_第13张图片
再回顾一下@Qualifier注解
7.spring整合junit问题分析

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


06.Spring的新注解_第14张图片
重复代码

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


06.Spring的新注解_第15张图片
image.png

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

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

  • 应用程序的入口是main方法
  • Junit单元测试中 没有main方法也能执行 这是因为Junit集成了一个main方法 该方法就会判断当前测试类中哪些方法*有@Test注解 如果有@Test注解的时候,Junit就会让有@Test注解的方法执行
  • Junit不会管我们是否采用Spring框架,所以也不会为我们读取配置文件/配置类,创建spring核心容器
    由以上3点可知,当测试方法执行时,没有ioc容器,就算写了@Autowired注解,也无法实现注入
    为什么出现了空指针异常?就是当我们执行的时候,只是让方法调用了一下,并没有创建容器,读取配置文件,所以无法注入,所以初始值是null,造成了空指针异常.

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

 
        
            org.springframework
            spring-test
            5.1.5.RELEASE
            test
        
06.Spring的新注解_第17张图片

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


06.Spring的新注解_第18张图片
可以看到@Runwith的属性value是要求是Runner的继承类
/**
 * 使用Junit单元测试
 */
@RunWith(SpringJUnit4ClassRunner.class)
public class AccountServiceTest {
    @Autowired
    private IAccountService accountService;

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

06.Spring的新注解_第19张图片
注解的方式

06.Spring的新注解_第20张图片
bean.xml的方式

你可能感兴趣的:(06.Spring的新注解)