目錄
一、簡介和目標
- 簡介:在 Spring Boot 場景下,基於約定大於配置的原則,實現 Spring 組件自動裝配的目的。
- 目標:完成一個可通過配置和@EnableXXX 來控制的是否裝配的Bean
二、底層裝配技術簡述
-
Spring 模式註解裝配
-
Spring @Enable 模塊裝配
-
註解驅動方式 eg:
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) @Documented @Import(DelegatingWebMvcConfiguration.class) public @interface EnableWebMvc { }
@Configuration public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport { }
-
接口編程方式 eg:
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Import(CachingConfigurationSelector.class) public @interface EnableCaching { }
public class CachingConfigurationSelector extends AdviceModeImportSelector<EnableCaching> { public String[] selectImports(AdviceMode adviceMode) { switch(adviceMode) { case PROXY: return this.getProxyImports(); case ASPECTJ: return this.getAspectJImports(); default: return null; } } }
-
-
Spring 條件裝配
- 配置方式 -
@Profile
,根據不同環境進行裝配 - 編程方式 -
@Conditional
@Target({ElementType.TYPE, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Documented @Conditional({OnClassCondition.class}) public @interface ConditionalOnClass { Class<?>[] value() default {}; String[] name() default {}; }
- 配置方式 -
-
Spring 工廠加載機制
- 實現類: SpringFactoriesLoader
- 配置資源: META-INF/spring.factories
三、實現
1、激活自動裝配 @EnableAutoConfiguration
項目結構如下
添加核心Bean=>HelloWorld,只有一個hello方法用於測試輸出結果
@Slf4j
public class HelloWorld {
public void hello(){
log.info("hello world 2019!");
}
}
添加HelloWorldConfiguration用於註冊HelloWorld
@Slf4j
public class HelloWorldConfiguration {
@Bean
public HelloWorld hello(){
log.info("Load HelloWorld");
return new HelloWorld();
}
}
添加HelloWorldImportSelector實現ImportSelector,通過接口編程方式實現@Enable功能
@Slf4j
public class HelloWorldImportSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
log.info("annotationMetadata.getAnnotationTypes():{}",annotationMetadata.getAnnotationTypes());
// 此處可寫分支條件,根據指定條件選擇性註冊某些類 或者返回null
return new String[]{HelloWorldConfiguration.class.getName()};
}
}
添加@EnableHelloWorld用於控制是否裝配HelloWorld
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
//@Import(HelloWorldConfiguration.class) // 基於註解驅動實現Sprig @Enable模塊
@Import(HelloWorldImportSelector.class) // 基於接口驅動實現Spring @Enable模塊
public @interface EnableHelloWorld {
}
注:如果直接使用@Import(HelloWorldConfiguration.class)註解方式實現,則不需要HelloWorldImportSelector類,但是註解方式無法添加分支判斷,只能指定加載指定類
2、實現自動裝配配置類 HelloWorldAutoConfiguration
@Configuration // 模式註解,聲明是一個bean
@ConditionalOnSystemProperty(name = "user.name", value = "Administrator") // 正確的條件裝配
//@ConditionalOnSystemProperty(name = "user.name", value = "lxt") // 錯誤的條件裝配
@EnableHelloWorld // Spring @Enable 模塊裝配
public class HelloWorldAutoConfiguration {
}
- 先根據條件註解@ConditionalOnSystemProperty判斷是否滿足
- 滿足則執行@EnableHelloWorld,加載HelloWorldImportSelector,註冊HelloWorldConfiguration進而註冊HelloWorld
3、配置自動裝配實現 META-INF/spring.factories
在resources下添加META-INF/spring.factories配置文件,用於啓動是通過工廠機制(SpringFactoriesLoader)加載
# 自動裝配
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.lxt.springboot.autoconfigure.configuration.HelloWorldAutoConfiguration
4、測試
添加測試類HelloWorldService
@Component
public class HelloWorldService {
@Autowired
private HelloWorld helloWorld;
@PostConstruct
public void init(){
helloWorld.hello();
}
}
啓動項目,控制檯輸出如下,測試開啓情況成功
2019-11-24 21:25:02.299 INFO 13880 --- [ main] c.l.s.a.a.HelloWorldImportSelector : annotationMetadata.getAnnotationTypes():[com.lxt.springboot.autoconfigure.condition.ConditionalOnSystemProperty, com.lxt.springboot.autoconfigure.annotation.EnableHelloWorld]
2019-11-24 21:25:02.710 INFO 13880 --- [ main] c.l.s.a.c.HelloWorldConfiguration : Load HelloWorld
2019-11-24 21:25:02.712 INFO 13880 --- [ main] c.l.s.autoconfigure.entity.HelloWorld : hello world 2019!
去掉@EnableHelloWorld 註解或者條件註解修改爲ConditionalOnSystemProperty(name = “user.name”, value = “lxt”),分別重啓,控制檯輸出如下,測試關閉情況成功
***************************
APPLICATION FAILED TO START
***************************
Description:
Field helloWorld in com.lxt.springboot.autoconfigure.service.HelloWorldService required a bean of type 'com.lxt.springboot.autoconfigure.entity.HelloWorld' that could not be found.
The injection point has the following annotations:
- @org.springframework.beans.factory.annotation.Autowired(required=true)
Action:
Consider defining a bean of type 'com.lxt.springboot.autoconfigure.entity.HelloWorld' in your configuration.
五、源碼
- https://github.com/hdlxt/dive-in-spring-boot