在Spring生態中,JavaConfig 如何優雅的替換 XML ...
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
占領了
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