美文网首页
06.Spring的新注解

06.Spring的新注解

作者: 吃伏冒有礼貌 | 来源:发表于2020-02-08 23:58 被阅读0次

    刚刚两个问题
    第一个问题,测试类重复代码
    第二个问题,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中配置了
     <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容器的注解

    • @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方法发现程序运行并不受影响,仍然可以成功.

    注释掉的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字节码
    • @Import
      作用:用于导入其他配置类
      属性:value,用于指定其他配置类的字节码
      当我们使用Import的注解后,有Import注解的类为父配置类,而导入的都是子配置类

    如果只想要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会自动按类型注入,但运行起来仍然为空指针异常,也就是说没有按照类型自动注入
    为什么会这样呢?

    • 应用程序的入口是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的依赖

     <!-- 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的方式

    相关文章

      网友评论

          本文标题:06.Spring的新注解

          本文链接:https://www.haomeiwen.com/subject/ehmhvctx.html