zoukankan      html  css  js  c++  java
  • 180531-Spring中JavaConfig知识小结

    原文链接:Spring中JavaConfig知识小结/

    Sring中JavaConfig使用姿势

    去掉xml的配置方式,改成用Java来配置,最常见的就是将xml中的 bean定义, scanner包扫描,属性文件的配置信息读取等

    I. 几个基本注解

    1. Configuration注解

    在javaConfig中注解@Configuration用来代替一个xml文件,可以简单的理解他们的作用是相等的,一般bean的定义也都是放在被这个注解修饰的类中

    如一个基本的配置文件如下

    @Configuration
    @ComponentScan("com.git.hui.rabbit.spring")
    public class SpringConfig {
        private Environment environment;
    
        @Autowired
        public void setEnvironment(Environment environment) {
            this.environment = environment;
            System.out.println("then env: " + environment);
        }
    
        @Bean(name="connectionFactory")
        public ConnectionFactory connectionFactory() {
            CachingConnectionFactory factory = new CachingConnectionFactory();
            factory.setHost("127.0.0.1");
            factory.setPort(5672);
            factory.setUsername("admin");
            factory.setPassword("admin");
            factory.setVirtualHost("/");
            return factory;
        }
    
        @Bean
        public RabbitAdmin rabbitAdmin(ConnectionFactory connectionFactory) {
            return new RabbitAdmin(connectionFactory);
        }
    }
    

    2. Bean 注解

    上面的例子中,在方法上添加了@Bean注解,这个就相当于传统的

    <bean name="rabbitAdmin" class="org.springframework.amqp.rabbit.core.RabbitAdmin"/>
    

    因此在需要引入rabbitAdmin实例的地方,可以如下使用

    a. 属性字段上添加 @Autowired注解

    public class RConsumer {
      @Autowired
      private RabbitAdmin rabbitAdmin;
    }
    

    b. 设置方法上添加 @Autowired注解

    public class RConsumer {
      private RabbitAdmin rabbitAdmin;
      
      @Autowired
      public void setRabbitAdmin(RabbitAdmin rabbitAdmin) {
        this.rabbitAdmin = rabbitAdmin;
      }
    }
    

    c. 使用构造器的方式

    public class RConsumer {
      private RabbitAdmin rabbitAdmin;
      public RConsumer(RabbitAdmin rabbitAdmin) {
            this.rabbitAdmin = rabbitAdmin;
      }
    }
    

    上面就是Spring容器支持的几种典型的IoC方式

    3. ComponentScan

    这个类似于xml中的 <context:component-scan"/> 标签

    @ComponentScan("com.git.hui.rabbit.spring")
    public class SpringConfig {
    }
    

    上面的这个配置,表示自动扫描包 com.git.hui.rabbit.spring 下面的bean (要求类上添加了 @Component, @Repository, @Service)

    那么一个问题来了,如果一个类既被自动扫描加载,又显示定义了bean,会怎样?

    package com.git.hui.rabbit.spring;
    
    import org.springframework.stereotype.Component;
    import java.util.concurrent.atomic.AtomicInteger;
    
    @Component
    public class TestBean {
        private static AtomicInteger count = new AtomicInteger(1);
    
        public TestBean() {
            System.out.println("testBean count: " + count.getAndAdd(1));
        }
    }
    

    对应的JavaConfig

    @Configuration
    @ComponentScan("com.git.hui.rabbit.spring")
    public class SpringConfig {
        @Bean
        public TestBean testBean() {
            return new TestBean();
        }
    }
    

    实际测试,发现这个bean只会有一个实例,即输出计数只会有一条,实际查看ApplicationContext中的内容,TestBean的实例,也确实只有一个,如果改成下面这种场景呢

    @Bean(name="testBean2")
    public TestBean testBean() {
        return new TestBean();
    }
    

    会有两条记录输出,实际查看容器中的Bean对象,会有两个实例如下

     
    180531_JavaConfig01.jpg

    这和我们的预期也是一样的,因为一个类我可能需要多个不同的Bean实例来干一些事情

    那么出现这种JavaConfig定义的beanName与自动扫描的冲突的情况会怎样呢?

    新增一个NewBean对象,

    public class NewBean {
        private static AtomicInteger count = new AtomicInteger(1);
    
        public NewBean() {
            System.out.println(" newbean count: " + count.getAndAdd(1));
        }
    }
    

    在JavaConfig中新加一个bean定义,但是BeanName与自动扫描的TestBean重复了

    @Bean(name="testBean")
    public NewBean newBean() {
      return new NewBean();
    }
    

    此时发现有意思的事情了,从Spring容器中,将查不到TestBean的实例,但是可以查到NewBean的实例

     
    180531_JavaConfig02.jpg

    这个的表现是:

    • 当beanName出现冲突时,JavaConfig的优先级会高于自动加载的,导致自动加载的Bean不会被加载到容器内

    那么跟着来的一个问题就是如果JavaConfig中定义了两个相同的BeanName的bean呢?

    @Bean(name = "testBean2")
    public NewBean newBean() {
        return new NewBean();
    }
    
    @Bean(name = "testBean2")
    public TestBean testBean() {
        return new TestBean();
    }
    

    因为我们TestBean上加了@Component注解,因此容器中至少有一个,但是否会有testBean2这个实例呢? 通过实际查看是没有的,testBean2这个名被 NewBean 占领了

     
    180531_JavaConfig03.jpg

    so,表现上看,加上实测,将上面的定义换个位置,得出下面的结论

    • 当出现beanName重名时,先定义的Bean占优

    然后就是最后一个问题了,当自动扫描时,两个类包不同,但是类名相同,会怎样?

    package com.git.hui.rabbit.spring.demo;
    
    import org.springframework.stereotype.Component;
    import java.util.concurrent.atomic.AtomicInteger;
    
    @Component
    public class TestBean {
        private static AtomicInteger count = new AtomicInteger(1);
    
        public TestBean() {
            System.out.println(" demo.TestBean count: " + count.getAndAdd(1));
        }
    }
    

    实测,会抛出一个异常,在使用xml的配置方式时,经常见到的一个BeanName冲突的异常

    org.springframework.context.annotation.ConflictingBeanDefinitionException: Annotation-specified bean name 'testBean' for bean class [com.git.hui.rabbit.spring.demo.TestBean] conflicts with existing, non-compatible bean definition of same name and class [com.git.hui.rabbit.spring.TestBean]
    

    小结:

    • JavaConfig 定义的BeanName与自动扫描的BeanName冲突时,JavaConfig的定义的会被实例化
    • JavaConfig 中定义了BeanName相同的Bean时,优先定义的有效(这里不抛异常不太能理解)
    • 自动扫描的Bean,不支持类名相同,但是包路径不同的场景(会抛异常)

    4. Import

    在xml配置中,另一个常见的case就是引入另一个xml配置,在JavaConfig中代替的就是Import注解

    @Configuration
    @ComponentScan("com.git.hui.rabbit.spring")
    @Import({DirectConsumerConfig.class, FanoutConsumerConfig.class, TopicConsumerConfig.class})
    public class SpringConfig {
    }
    

    这个就等同于xml中常见的:

    <import resource="service.xml" />
    

    II. 实例测试

    1. xml单测姿势

    上面说了用JavaConfig代替xml配置的方式,另一个关键的地方就是测试用例的写法了,对于之前的xml,有两种常见的使用姿势

    case1: 注解方式

    @RunWith(SpringJUnit4ClassRunner.class)
    @ContextConfiguration("classpath*:*.xml")
    public class BeanTest {
    }
    

    case2: 主动加载容器方式

    private ServiceA serviceA;
    
    @Before
    public void init() {
        ApplicationContext apc = new ClassPathXmlApplicationContext("classpath:*.xml");
        serviceA = (ServiceA) apc.getBean("serviceA");
    }
    

    2. JavaConfig单测使用姿势

    那么替换成JavaConfig的用法,也有两种

    case1: 注解方式,指定内部classes值

    @RunWith(SpringJUnit4ClassRunner.class)
    @ContextConfiguration(classes = SpringConfig.class)
    public class SprintUnit {
    }
    

    case2: 主动加载容器,改为AnnotationConfigApplicationContext

    @Test
    public void testServiceA() {
        ApplicationContext context = new AnnotationConfigApplicationContext(BeansConfiguration.class);
        ServiceA serviceA = (ServiceA) context.getBean("serviceA");
        serviceA.print();
    }
    

    III. 小结

    1. 注解映射关系

    JavaConfig方式基本上采用的是替换的思路来取代xml,即原xml中的一些东西,可以直接通过注解来代替,如

    • @Configuration 修饰类,与传统的xml文件作用相同
    • @Bean注解,修饰方法,表示声明一个Bean,与原来的xml中的 <bean> 标签作用相同
    • @ComponentScan注解,自动扫描包,类似xml中的 <context:component-scan>
    • @Import注解,与xml中的<import>标签类似,引入其他的配置信息

    2. BeanName重名规则

    在实际使用中,有一点需要额外注意,对于beanName相同的情况,通过测试的规则如下(没有看源码,不保证完全准确,仅为测试后得出的依据):

    • JavaConfig 定义的BeanName与自动扫描的BeanName冲突时,JavaConfig的定义的会被实例化
    • JavaConfig 中定义了BeanName相同的Bean时,优先定义的有效(这里不抛异常不太能理解)
    • 自动扫描的Bean,不支持类名相同,但是包路径不同的场景(会抛异常)

    3. 测试姿势

    最简单的就是修改原来的注解@ContextConfiguration中的值

    @ContextConfiguration(classes = SpringConfig.class)
    

    II. 其他

    一灰灰Blog: https://liuyueyi.github.io/hexblog

    一灰灰的个人博客,记录所有学习和工作中的博文,欢迎大家前去逛逛

    声明

    尽信书则不如,已上内容,纯属一家之言,因个人能力有限,难免有疏漏和错误之处,如发现bug或者有更好的建议,欢迎批评指正,不吝感激

    扫描关注

     
    blogInfoV2.png
  • 相关阅读:
    Javascript Property Names
    Java泛型
    Activity 与 Task
    使用ddns搭建免费服务器
    DDNS
    SimpleAdapter用法
    Java KeyNote
    Android无法访问本地服务器(localhost/127.0.0.1)的解决方案
    Android 添加网络权限
    Java 匿名内部类
  • 原文地址:https://www.cnblogs.com/yihuihui/p/9127388.html
Copyright © 2011-2022 走看看