自動配置絕對算得上是Spring Boot的最大亮點,完美的展現了CoC約定優於配置; Spring Boot能自動配置Spring各類子項目(Spring MVC, Spring Security, Spring Data, Spring Cloud, Spring Integration, Spring Batch等)以及第三方開源框架所須要定義的各類Bean。 Spring Boot內部定義了各類各樣的XxxxAutoConfiguration配置類,預先定義好了各類所需的Bean。只有在特定的狀況下這些配置類纔會被起效。 html
1、概述
SpringBoot的Auto-configuration的核心實現都位於spring-boot-autoconfigure-xxx.jar;其中SpringBoot將依據classpath裏面的依賴內容來自動配置bean到IoC容器,Auto-configuration會嘗試推斷哪些beans是用戶可能會須要的。java
自動化配置並非spring boot纔有的,從spring framework3.1開始,這個特性就有了,像@EnableAspectJAutoProxy、@EnableAsync都是從spring 3.1開始就有了。org.springframework.context.annotation包下面擁有自動配置的全部的相關的基礎設施。web
基礎設施
org.springframework.context.annotation包下面提供了各類基於註解配置的基礎設施:
1. @Profile:可跟@Bean配合、
2. @Bean、@Scope、@DependsOn、@Primary、@Lazy、@Role、@Description:
3. @Conditional、Condition:@Conditional註解標識在類或者方法上,標識在方法上,符合條件,建立該方法的返回值類型的Bean;標識在類上,符合條件所有建立。
4. @Import(@ImportResource):
5. @Configuration表示的Class(@EnableLoadTimeWeaving)、ImportSelector接口實現(@EnableAsync)或者ImportBeanDefinitionRegistrar接口實現(@EnableAspectJAutoProxy)
6. ImportSelector、DeferredImportSelector:
7. ImportRegistry
8. ImportBeanDefinitionRegistrar:用來手動註冊bean定義的, 能夠實現相似於Mybatis-Spring提供的掃描Mapper接口並註冊其bean定義, 事實上@MapperScan註解就@Import了MapperScannerRegistrar這個類, 而這個類實現了上面的接口, 來掃描Mapper並註冊bean定義.再多說點吧, Spring解析Java配置類的時候, 會判斷類是否是標註了@Import註解, 而後會判斷, 若是Import註解的value是ImportBeanDefinitionRegistrar類型, 會存到一個變量, 後面初始化bean工程完成後, 會回調ImportBeanDefinitionRegistrar.
9. @Configuration:跟@Controller、@Servcice和@Repository是同樣的套路,都用@Component註解了,做爲特定類型的組件
10. @PropertySource
11. Condition、ConfigurationCondition、@Conditionalspring
spring boot autoconfigure
Spring Boot AutoConfigure替代了XML風格的配置文件,帶來了史無前例的體驗。Spring Boot AutoConfigure模塊基於Spring Framework和Spring Boot提供的基礎設施,構建類配置Bean+屬性文件配置行爲的配置方式,Java類配置Bean爲咱們提供了更好的編程體驗,屬性文件配置行爲的方式使這種方式擁有跟XML外部配置文件配置方式一樣的靈活性。數據庫
org.springframework.boot.autoconfigure
首先,Spring Boot AutoConfigure在Spring Framework和Spring Boot提供的基礎設施上作了不少的擴展工做:
1. 順序控制:AutoConfigureOrder、AutoConfigureAfter、AutoConfigureBefore;
2. AutoConfigurationPackage:在spring boot mian class上標識EnableAutoConfiguration以後,全部子包下面的spring 組件都能被掃描到,就是這個註解的能力;
3. EnableAutoConfiguration/ImportAutoConfiguration:EnableAutoConfiguration開啓自動配置,自動應用spring.factories中配置的各類*AutoConfiguration;ImportAutoConfiguration跟EnableAutoConfiguration相比,只是沒有自動配置的功能,給ImportAutoConfiguration傳入誰的AutoConfiguration就應用誰的,單元測試等的場景用到的比較多;
4. 其餘的一些工具類,過濾器之類的東西你們能夠本身去看下編程
org.springframework.boot.autoconfigure.context.condition
ConditionalOnCloudPlatform:是否在雲環境下,spring boot cloud模塊提供了兩種實現,CLOUD_FOUNDRY和HEROKU,國內應該用不到這個註解了
ConditionalOnJava:指定的Java版本
ConditionalOnWebApplication:是Web環境的時候
ConditionalOnNotWebApplication:不是web環境的時候
ConditionalOnJndi:JNDI環境下使用
ConditionalOnClass:classpath中存在某個類
ConditionalOnMissingClass:classpath中不存在某個類
ConditionalOnBean:BeanFactory中存在某個類的Bean
ConditionalOnMissingBean:BeanFactory中不存在某個類的Bean
ConditionalOnExpression:SpEL的結果
ConditionalOnProperty:Environment中是否有某個屬性的配置信息
ConditionalOnResource:classpath中是否存在指定名稱的資源
ConditionalOnSingleCandidate:指定的類在BeanFactory中只有一個候選的bean,或者有多個候選的bean,可是其中一個指定了primary時
各類*AutoConfiguration的實現:
全部的*AutoConfiguration的具體實現包括兩部分,一個是標識了@Configuration註解的配置類,另外一個是Property文件。有些模塊比較複雜,像security的oauth2模塊,主要文件也是這兩類,剩下的是一些工具。數組
*AutoConfiguration也是Configuration,被@Configuration註解,只不過spring boot autoconfigure模塊內置的 *AutoConfiguration被配置到了 spring.factories文件中,啓動的時候自動配置。springboot
自動配置是Spring Boot的最大亮點,完美的展現了CoC約定優於配置。網絡
2、源碼解析
2.一、從SpringBoot啓動時的自動配置加載過程
查看源碼能夠看看自動配置類是如何被引入的。
a) 應用入口 架構
@SpringBootApplication
public class SpringBootDemoApplication {
public static void main(String[] args) {
SpringApplication.run(SpringBootDemoApplication.class, args);
}
}
b) 類註解 @SpringBootApplication = @EnableAutoConfiguration + @ComponentScan + @Configuration(而其中的@EnableAutoConfiguration
則正是實現Auto Config的關鍵之所在)
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = {
@Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
// ...
}
@Configuration
public @interface SpringBootConfiguration {
// ...
}
c)開啓自動配置註解 @EnableAutoConfiguration,是auto config 關鍵所在。
@AutoConfigurationPackage
@Import(EnableAutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
// ...
}
d)導入配置類 EnableAutoConfigurationImportSelector extends AutoConfigurationImportSelector
@EnableAutoConfiguration
註解會導入AutoConfigurationImportSelector
類的實例被引入到Spring容器中,而該類的繼承鏈以下:
public class AutoConfigurationImportSelector
implements DeferredImportSelector, BeanClassLoaderAware, ResourceLoaderAware,
BeanFactoryAware, EnvironmentAware, Ordered {
所以對於 AutoConfigurationImportSelector類, 咱們重點關注的是其實現自ImportSelector接口的方法selectImports,而直接繼承的DeferredImportSelector做爲一個標誌性接口,主要做用是爲了Defer(推遲;延期;)。
在咱們繼續探索以前,讓咱們暫停一下,先來回顧下Spring是如何執行到這裏來的,即如何調用到AutoConfigurationImportSelector.selectImports方法的。
在selectImports方法上打個斷點,啓動任意一個springboot項目,調用鏈以下圖:
![](http://static.javashuo.com/static/loading.gif)
從上述堆棧中咱們能夠看到 ConfigurationClassParser.parse() 被調用,而其參數candidates ,做爲一個集合參數其中只包含咱們在啓動SpringBoot時傳入的那個AutoConfigSpringBootApplication類包裹所造成的BeanDefinitionHolder實例。
該ConfigurationClassParser.parse(Set<BeanDefinitionHolder> configCandidates)方法最終會調用到自身內部私有的processDeferredImportSelectors()方法:
// 本方法位於 protected 訪問級別的 ConfigurationClassParser 中
private void processDeferredImportSelectors() {
// @EnableAutoConfiguration註解上修飾的@Import(AutoConfigurationImportSelector.class) 註解的解析是由 ConfigurationClassParser.parse中開始調度完成(本類中的processImports方法), 進而載入到本類的 deferredImportSelectors 字段中。
// 這裏要特別注意,正由於AutoConfigurationImportSelector是一個DeferredImportSelector實例,因此其生效時機晚於@Import生效的時機,這也使得邏輯時序能夠正確地運行下去。
List<DeferredImportSelectorHolder> deferredImports = this.deferredImportSelectors;
this.deferredImportSelectors = null;
Collections.sort(deferredImports, DEFERRED_IMPORT_COMPARATOR);
for (DeferredImportSelectorHolder deferredImport : deferredImports) {
// 這裏取到的configClass就是咱們自定義的 AutoConfigSpringBootApplication
ConfigurationClass configClass = deferredImport.getConfigurationClass();
try {
// 核心邏輯就是下面這兩句了
// 首先是這行, 負責回調咱們上面使用@Import導入的AutoConfigurationImportSelector裏的邏輯實現, 詳情將在本文接下來的內容
// 最終的返回值是通過篩選,知足要求的類名
String[] imports = deferredImport.getImportSelector().selectImports(configClass.getMetadata());
// configClass 就是咱們傳遞給 SpringApplication.run 的AutoConfigSpringBootApplication類
// 該方法最終會跳轉到 本類內部的doProcessConfigurationClass方法中,來將相應Bean註冊進容器, Auto Config完成。
processImports(configClass, asSourceClass(configClass), asSourceClasses(imports), false);
}
catch {
// 異常處理略
}
}
}
繞了一圈終於回到本小節本來關注的內容——有關AutoConfigurationImportSelector
實現的selectImports
方法:
// AutoConfigurationImportSelector (位於package - org.springframework.boot.autoconfigure, 因此是SpringBoot自帶的)
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return NO_IMPORTS;
}
try {
// 加載 META-INF/spring-autoconfigure-metadata.properties 中的相關配置信息, 注意這主要是供Spring內部使用的
AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
.loadMetadata(this.beanClassLoader);
AnnotationAttributes attributes = getAttributes(annotationMetadata);
// 獲取全部經過META-INF/spring.factories配置的, 此時還不會進行過濾和篩選
// KEY爲 : org.springframework.boot.autoconfigure.EnableAutoConfiguration
List<String> configurations = getCandidateConfigurations(annotationMetadata,
attributes);
// 開始對上面取到的進行過濾,去重,排序等操做
configurations = removeDuplicates(configurations);
configurations = sort(configurations, autoConfigurationMetadata);
Set<String> exclusions = getExclusions(annotationMetadata, attributes);
checkExcludedClasses(configurations, exclusions);
configurations.removeAll(exclusions);
configurations = filter(configurations, autoConfigurationMetadata);
fireAutoConfigurationImportEvents(configurations, exclusions);
// 這裏返回的知足條件, 經過篩選的配置類
return configurations.toArray(new String[configurations.size()]);
}
catch (IOException ex) {
throw new IllegalStateException(ex);
}
}
A、getCandidateConfigurations()方法:
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata,
AnnotationAttributes attributes) {
// 獲取全部經過META-INF/spring.factories配置的, 此時還不會進行過濾和篩選KEY爲:org.springframework.boot.autoconfigure.EnableAutoConfiguration的配置的value(類路徑+類名稱)
List<String> configurations = SpringFactoriesLoader.loadFactoryNames(
getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader());
Assert.notEmpty(configurations,
"No auto configuration classes found in META-INF/spring.factories. If you "
+ "are using a custom packaging, make sure that file is correct.");
return configurations;
}
//返回EnableAutoConfiguration.class
protected Class<?> getSpringFactoriesLoaderFactoryClass() {
return EnableAutoConfiguration.class;
}
public static List<String> loadFactoryNames(Class<?> factoryClass, ClassLoader classLoader) {
//此時爲org.springframework.boot.autoconfigure.EnableAutoConfiguration
String factoryClassName = factoryClass.getName();
try {
//配置項的默認位置META-INF/spring.factories
Enumeration<URL> urls = (classLoader != null ? classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
List<String> result = new ArrayList<String>();
while (urls.hasMoreElements()) {
URL url = urls.nextElement();
//從多個配置文件中查找,例如個人有:spring-boot-admin-starter-client-1.5.6.jar!/META-INF/spring.factories和stat-log-0.0.1-SNAPSHOT.jar!/META-INF/spring.factories
Properties properties = PropertiesLoaderUtils.loadProperties(new UrlResource(url));
String factoryClassNames = properties.getProperty(factoryClassName);
result.addAll(Arrays.asList(StringUtils.commaDelimitedListToStringArray(factoryClassNames)));
}
return result;
}
catch (IOException ex) {
throw new IllegalArgumentException("Unable to load [" + factoryClass.getName() +
"] factories from location [" + FACTORIES_RESOURCE_LOCATION + "]", ex);
}
}
看上面的源碼可知經過SpringFactoriesLoader.loadFactoryNames()把多個jar的/META-INF/spring.factories配置文件中的有EnableAutoConfiguration配置項都抓出來。
spring.factories的位置截圖以下:
![](http://static.javashuo.com/static/loading.gif)
B、在完成了對spring.factories中全部的EnableAutoConfiguration的解析後,對其過濾,去重,排序等操做等,返回。
C、ConfigurationClassParser.processImports(),ConfigurationClassParser工具類自身的邏輯並不註冊bean定義,它的主要任務是發現@Configuration註解的全部配置類並將這些配置類交給調用者(調用者會經過其餘方式註冊其中的bean定義),而對於非@Configuration註解的其餘bean定義,好比@Component註解的bean定義,該工具類使用另一個工具ComponentScanAnnotationParser掃描和註冊它們。
以上正是Springboot完成Auto Config功能的關鍵點之一了。在本實現中,SpringBoot只是告知Spring須要去加載(Import)哪些Config類,剩下的工做依然是Spring那已經通過千錘百煉的邏輯來完成; 這正是 「微核 + 擴展」的優秀架構設計經驗的極致體現。
2.二、springboot自動配置DIY
經過上面的源碼分析,能夠將以下的spring.factories的全部配置類以下:
spring-boot-1.5.10.RELEASE.jar/META-INF/spring.factories
# Application Context Initializers
org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.context.ConfigurationWarningsApplicationContextInitializer,\
org.springframework.boot.context.ContextIdApplicationContextInitializer,\
org.springframework.boot.context.config.DelegatingApplicationContextInitializer,\
org.springframework.boot.context.embedded.ServerPortInfoApplicationContextInitializer
spring-boot-autoconfigure-1.5.10.RELEASE.jar/META-INF/spring.factories
# Initializers
org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer,\
org.springframework.boot.autoconfigure.logging.AutoConfigurationReportLoggingInitializer
相應在作war時也是把當前的SpringBootDemoApplication做爲source傳給了ServletInitializer。
那麼,咱們能夠這樣啓動springboot,註解類不用SpringApplication,配置類也可自行導入
2.三、關鍵類EnableAutoConfiguration
2.3.一、EnableAutoConfiguration源碼解析
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
Class<?>[] exclude() default {};
String[] excludeName() default {};
}
從上往下:
- 首先,最關鍵的要屬@Import(AutoConfigurationImportSelector.class),藉助AutoConfigurationImportSelector,@EnableAutoConfiguration能夠幫助SpringBoot應用將全部符合條件的@Configuration配置都加載到當前SpringBoot建立並使用的IoC容器。(Import主要是配合Configuration來使用的,用來導出更多的Configuration類,ConfigurationClassPostProcessor會讀取Import的內容來實現具體的邏輯。)藉助於Spring框架原有的一個工具類:SpringFactoriesLoader的支持,@EnableAutoConfiguration能夠智能的自動配置(掃描每一個jar中的spring.factories)!
- 再看EnableAutoConfiguration的方法,就兩個方法exclude和excludeName,做用是自動配置過程當中包含和排查指定的類。
2.3.二、自定義EnableAutoConfiguration示例
一、自定義EnableAutoConfiguration,這裏Import了MyEnableAutoConfigurationImport。
package com.dxz.autoconfig;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.boot.autoconfigure.AutoConfigurationPackage;
import org.springframework.context.annotation.Import;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(MyEnableAutoConfigurationImport.class)
public @interface MyEnableAutoConfiguration {
}
二、自定義EnableAutoConfigurationImport,注入了ClassLoader,並調用SpringFactoriesLoader.loadFactoryNames()方法,導出Configuration的類。
package com.dxz.autoconfig;
import java.util.List;
import org.springframework.beans.factory.BeanClassLoaderAware;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.context.annotation.DeferredImportSelector;
import org.springframework.core.io.support.SpringFactoriesLoader;
import org.springframework.core.type.AnnotationMetadata;
public class MyEnableAutoConfigurationImport implements DeferredImportSelector, BeanClassLoaderAware {
private ClassLoader classLoader;
public void setBeanClassLoader(ClassLoader classLoader) {
this.classLoader = classLoader;
}
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
List<String> beanNames = SpringFactoriesLoader.loadFactoryNames(EnableAutoConfiguration.class, classLoader);
return beanNames.toArray(new String[beanNames.size()]);
}
}
三、入口類,這裏使用了MyEnableAutoConfiguration註解。
package com.dxz.autoconfig;
import java.util.HashMap;
import java.util.Map;
import org.springframework.boot.SpringApplication;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
@Configuration
@MyEnableAutoConfiguration
public class CustomizeEnableAutoConfigure {
public static void main(String[] args) {
SpringApplication application = new SpringApplication(CustomizeEnableAutoConfigure.class);
application.run(args);
}
@Controller
public static class MyController {
@RequestMapping
@ResponseBody
public Map index() {
Map<String, String> map = new HashMap<String, String>();
map.put("hello", "world2");
return map;
}
}
}
結果:
![](http://static.javashuo.com/static/loading.gif)
2.四、condition包
2.4.一、condition示例講解
springboot爲咱們提供了一批實用的XxxCondition,查看了他們的源碼後發現,他們都實現了spring提供的Condition接口,而後編寫對應的annotation。咱們在使用他們的時候,只須要在須要的地方寫上這些annotation就行了。這些註解都在springboot提供的jar包中
package org.springframework.boot.autoconfigure.condition。
提供這些condition主要目的:上面咱們討論的AutoConfigurationImportSelector只能告訴Spring哪些類須要加載,但判斷所配置的類是否能夠被加載(即Auto Config裏的Auto)是一個很是繁瑣的邏輯,若是由某個中央控制系統來處理的話,必然會形成代碼耦合和複雜性猛增,所以SpringBoot最終使用了一向的作法——將判斷是否加載的權限下放給了各個須要進行自動配置的需求方自己,這樣在springboot中擴展了不少condition。
基於Spring的@Conditional,SpringBoot提供了豐富的條件配置
@ConditionalOnClass : classpath中存在該類時起效
@ConditionalOnMissingClass : classpath中不存在該類時起效
@ConditionalOnBean : DI容器中存在該類型Bean時起效
@ConditionalOnMissingBean : DI容器中不存在該類型Bean時起效
@ConditionalOnSingleCandidate : DI容器中該類型Bean只有一個或@Primary的只有一個時起效
@ConditionalOnExpression : SpEL表達式結果爲true時
@ConditionalOnProperty : 參數設置或者值一致時起效
@ConditionalOnResource : 指定的文件存在時起效
@ConditionalOnJndi : 指定的JNDI存在時起效
@ConditionalOnJava : 指定的Java版本存在時起效
@ConditionalOnWebApplication : Web應用環境下起效
@ConditionalOnNotWebApplication : 非Web應用環境下起效
@AutoConfigureAfter:在指定的配置類初始化後再加載
@AutoConfigureBefore:在指定的配置類初始化前加載
@AutoConfigureOrder:數越小越先初始化
2.4.二、condition示例講解
1)@ConditionalOnBean
/@ConditionalOnMissingBean
當容器中存在/不存在某個bean時,加上此註解的bean被自動注入
package org.springframework.boot.autoconfigure.condition;
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(OnBeanCondition.class)
public @interface ConditionalOnBean {
Class<?>[] value() default {};
String[] type() default {};
Class<? extends Annotation>[] annotation() default {};
String[] name() default {};
SearchStrategy search() default SearchStrategy.ALL;
}
2)@ConditionalOnJava
根據當前使用的JDK版本,判斷是否自動注入
3)@
ConditionalOnProperty來控制Configuration是否生效
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.TYPE, ElementType.METHOD })
@Documented
@Conditional(OnPropertyCondition.class)
public @interface ConditionalOnProperty {
String[] value() default {}; //數組,獲取對應property名稱的值,與name不可同時使用
String prefix() default "";//property名稱的前綴,無關緊要
String[] name() default {};//數組,property完整名稱或部分名稱(可與prefix組合使用,組成完整的property名稱),與value不可同時使用
String havingValue() default "";//可與name組合使用,比較獲取到的屬性值與havingValue給定的值是否相同,相同才加載配置
boolean matchIfMissing() default false;//缺乏該property時是否能夠加載。若是爲true,沒有該property也會正常加載;反之報錯
boolean relaxedNames() default true;//是否能夠鬆散匹配,至今不知道怎麼使用的
}
經過其兩個屬性
name以及
havingValue來實現的,其中
name用來從
application.properties中讀取某個屬性值。
若是該值爲空,則返回false;
若是值不爲空,則將該值與havingValue指定的值進行比較,若是同樣則返回true;不然返回false。
若是返回值爲false,則該configuration不生效;爲true則生效。
@Configuration
//在application.properties配置"mf.assert",對應的值爲true
@ConditionalOnProperty(prefix="mf",name = "assert", havingValue = "true")
public class AssertConfig {
@Autowired
private HelloServiceProperties helloServiceProperties;
@Bean
public HelloService helloService(){
HelloService helloService = new HelloService();
helloService.setMsg(helloServiceProperties.getMsg());
return helloService;
}
}
2.4.2.三、示例:(參考網絡)
最近碰到個這樣的需求,須要同一套代碼適配個版本數據庫(數據庫不一樣,且部分表的字段及關聯關係可能會不一樣),即這套代碼配置不一樣的數據庫都能跑。項目採用的框架爲SpringBoot+Mybatis。通過一番思考,思路以下:
(1)在業務層(service)和數據訪問層(Mapper)之間添加一層適配層,用來屏蔽數據庫的差別
(2)適配層中代碼均採用接口加實現類的方式,不一樣的數據庫用的實現類不一樣
(3)業務層(service)中所有采用面向接口編程
(4)項目啓動後只實例化和數據庫相匹配的適配層實現類
實現上面的一個關鍵點是對bean的實例化添加一個條件判斷來控制。其實SpringBoot裏面新增了不少條件註解,能實現這個功能。可是都有些侷限性,最終是採用自定義條件註解的方案。
2.4.2.3.1)、經過SpringBoot自帶的註解ConditionalOnProperty實現
這個註解不作過多的解釋,只說經過這個註解怎麼實現咱們的功能。
假設咱們application.properties中配置一個配置項爲
#bean實例化條件配置項
conditionKey: 1.0
那麼只須要加上@ConditionalOnProperty的name和havingValue就能實現,只有配置文件中name對應的配置項的值和havingValue內容一致才實例化這個對象。
針對咱們上面配置的application.properties的內容,@ConditionalOnProperty的使用案例以下面代碼所示
ManageImpl1.java代碼以下:(MyManage接口、ManageImpl2省略)
package com.dxz.palmpay.condition;
import javax.annotation.PostConstruct;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.stereotype.Component;
//僅當conditionKey==1.0的時候實例化這個類
@ConditionalOnProperty(name = "conditionKey", havingValue = "1.0")
@Component
public class ManageImpl1 implements MyManage {
@Override
public void sayHello() {
System.out.println("我是實現類01");
}
//爲了效果,建立後打印一些信息
@PostConstruct
public void init() {
this.sayHello();
}
}
這個註解的侷限性:這個註解的havingValue裏面只能配置一個值。
因爲項目個性化需求,但願這個havingValue能夠配置多個值,name對應的配置項的Value只要知足havingValue裏面多個值的就表示匹配正確。即,havingValue裏面能夠配置多個值,name對應配置項的值來和havingValue匹配時,採用邏輯或匹配,知足一個值就算匹配正確。
2.4.2.3.2)、自定義條件註解
(1)思路
註解裏面有2個屬性,具體以下
- name:String類型,用來接受application.properties的配置項的key
- havingValue:String數組類型,用來和name對應key的Value進行匹配
(2)定義註解
package com.dxz.palmpay.condition;
import org.springframework.context.annotation.Conditional;
import java.lang.annotation.*;
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.TYPE, ElementType.METHOD })
@Documented
@Conditional(CustomOnPropertyCondition.class)
public @interface CustomConditionalOnProperty {
String name() default "";
//havingValue數組,支持or匹配
String[] havingValue() default {};
}
(3)定義註解的匹配規則
package com.dxz.palmpay.condition;
import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.type.AnnotatedTypeMetadata;
import java.util.Map;
/**
* 自定義條件註解的驗證規則
*/
public class CustomOnPropertyCondition implements Condition {
@Override
public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {
Map<String, Object> annotationAttributes = annotatedTypeMetadata
.getAnnotationAttributes(CustomConditionalOnProperty.class.getName());
String propertyName = (String) annotationAttributes.get("name");
String[] values = (String[]) annotationAttributes.get("havingValue");
if (0 == values.length) {
return false;
}
String propertyValue = conditionContext.getEnvironment().getProperty(propertyName);
// 有一個匹配上就ok
for (String havingValue : values) {
if (propertyValue.equalsIgnoreCase(havingValue)) {
return true;
}
}
return false;
}
}
@Component
@CustomConditionalOnProperty(name = "db.version", havingValue = {"3"})
public class ManageImpl3 implements MyManage {
@Component
@CustomConditionalOnProperty(name = "db.version", havingValue = {"1","2","4"})
public class ManageImpl4 implements MyManage {
自定義Condition註解,主要就2步
(1)定義一個條件註解
(2)定義一個條件的校驗規則
參考:https://blog.csdn.net/lqzkcx3/article/details/82807888
參考:https://www.cnblogs.com/zeng1994/p/8c10310d8a042d56eddd40635afb6e93.html