1. 程式人生 > >180531-Spring中JavaConfig知識小結

180531-Spring中JavaConfig知識小結

博文 關系 有效 () div eth view bug print

原文鏈接: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或者有更好的建議,歡迎批評指正,不吝感激

  • 微博地址: 小灰灰Blog
  • QQ: 一灰灰/3302797840

掃描關註

技術分享圖片 blogInfoV2.png

180531-Spring中JavaConfig知識小結