注:該源碼分析對應SpringBoot版本爲2.1.0.RELEASEhtml
本篇接
如何分析SpringBoot源碼模塊及結構?--SpringBoot源碼(二)java
上一篇分析了SpringBoot源碼結構及各個模塊pom之間的關係後,那麼此篇開始就開始解開SpringBoot新特性之一--自動配置的神祕面紗了。由於SpringBoot自動配置原理是基於其大量的條件註解ConditionalOnXXX
,所以,本節咱們先來擼下Spring的條件註解的相關源碼。linux
咱們都知道,SpringBoot自動配置是須要知足相應的條件纔會自動配置,所以SpringBoot的自動配置大量應用了條件註解ConditionalOnXXX
。以下圖:web
那麼上圖的條件註解如何使用呢?spring
舉個栗子,咱們來看下如何使用
@ConditionalOnClass
和@ConditionalOnProperty
這兩個註解,先看下圖代碼:HelloWorldEnableAutoConfiguration
這個自動配置類應用了@ConditionalOnClass
和ConditionalOnProperty
兩個條件註解,那麼只有在知足:classpath
中存在HelloWorldComponent.class
和配置了hello.world.name
和hello.world.age
屬性這兩個條件的狀況下才會建立HelloWorldComponent
這個bean
。springboot
其實SpringBoot的@ConditionalOnXXX
等條件註解都是派生註解,那麼什麼是派生註解呢?
就拿上面的栗子來講,以@ConditionalOnClass(HelloWorldComponent.class)
爲例,咱們打開ConditionalOnClass
註解源碼,以下:session
@Target({ ElementType.TYPE, ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) @Documented @Conditional(OnClassCondition.class) public @interface ConditionalOnClass { Class<?>[] value() default {}; String[] name() default {}; }
能夠看到@ConditionalOnClass
註解上面又標註了@Conditional(OnClassCondition.class)
註解,所以@ConditionalOnClass
是@Conditional
的派生註解,@Conditional(OnClassCondition.class)
和@ConditionalOnClass
註解是等價的,即這兩個註解標註在某個配置類上的效果是等價的。app
而SpringBoot的自動配置原理正是創建在這些大量的派生條件註解@ConditionalOnXXX
之上,而這些條件註解的原理跟Spring的Condition接口有關。所以咱們先來研究下Condition接口的相關源碼。ide
分析Condition接口源碼前先看下如何自定義ConditionalOnXXX
註解,舉個栗子,好比自定義一個@ConditionalOnLinux
註解,該註解只有在其屬性environment
是"linux"纔會建立相關的bean。定義瞭如下代碼:源碼分析
/** * 實現spring 的Condition接口,而且重寫matches()方法,若是@ConditionalOnLinux的註解屬性environment是linux就返回true * */ public class LinuxCondition implements Condition { @Override public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { // 得到註解@ConditionalOnLinux的全部屬性 List<AnnotationAttributes> allAnnotationAttributes = annotationAttributesFromMultiValueMap( metadata.getAllAnnotationAttributes( ConditionalOnLinux.class.getName())); for (AnnotationAttributes annotationAttributes : allAnnotationAttributes) { // 得到註解@ConditionalOnLinux的environment屬性 String environment = annotationAttributes.getString("environment"); // 若environment等於linux,則返回true if ("linux".equals(environment)) { return true; } } return false; } }
@Target({ ElementType.TYPE, ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) @Documented @Conditional(LinuxCondition.class) public @interface ConditionalOnLinux { // 標註是哪一個環境 String environment() default ""; }
@Configuration public class ConditionConfig { // 只有`@ConditionalOnLinux`的註解屬性`environment`是"linux"時纔會建立bean @Bean @ConditionalOnLinux(environment = "linux") public Environment linuxEnvironment() { return new LinuxEnvironment(); } }
上面的代碼咱們捋一下:
LinuxCondition
實現了Condition
接口並實現了matches
方法,而matches
方法則判斷@ConditionalOnLinux
的註解屬性environment
是否"linux",是則返回true,不然false。@ConditionalOnLinux
,這個註解是@Conditional
的派生註解,與@Conditional(LinuxCondition.class)
等價,注意@ConditionalOnLinux
註解定義了一個屬性environment
。而咱們最終能夠利用LinuxCondition
的matches
方法中的參數AnnotatedTypeMetadata
來獲取@ConditionalOnLinux
的註解屬性environment
的值,從而用來判斷值是否爲linux"。ConditionConfig
,在linuxEnvironment
方法上標註了@ConditionalOnLinux(environment = "linux")
。所以,這裏只有 LinuxCondition
的matches
方法返回true纔會建立bean
。學會了如何自定義@ConditionalOnXXX
註解後,咱們如今再來看下Condition
接口的源碼:
@FunctionalInterface public interface Condition { boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata); }
Condition接口主要有一個matches
方法,該方法決定了是否要註冊相應的bean
對象。其中matches
方法中有兩個參數,參數類型分別是ConditionContext
和AnnotatedTypeMetadata
,這兩個參數很是重要。它們分別用來獲取一些環境信息和註解元數據從而用在matches
方法中判斷是否符合條件。
ConditionContext
,顧名思義,主要是跟Condition
的上下文有關,主要用來獲取Registry
,BeanFactory
,Environment
,ResourceLoader
和ClassLoader
等。那麼獲取這些用來幹什麼呢?舉個栗子,好比OnResourceCondition
須要靠ConditionContext
來獲取ResourceLoader
來加載指定資源,OnClassCondition
須要靠ConditionContext
來獲取ClassLoader
來加載指定類等,下面看下其源碼:public interface ConditionContext { BeanDefinitionRegistry getRegistry(); @Nullable ConfigurableListableBeanFactory getBeanFactory(); Environment getEnvironment(); ResourceLoader getResourceLoader(); @Nullable ClassLoader getClassLoader(); }
AnnotatedTypeMetadata
,這個跟註解元數據有關,利用AnnotatedTypeMetadata
能夠拿到某個註解的一些元數據,而這些元數據就包含了某個註解裏面的屬性,好比前面的栗子,利用AnnotatedTypeMetadata
能夠拿到@ConditionalOnLinux
的註解屬性environment
的值。下面看下其源碼:public interface AnnotatedTypeMetadata { boolean isAnnotated(String annotationName); Map<String, Object> getAnnotationAttributes(String annotationName); Map<String, Object> getAnnotationAttributes(String annotationName, boolean classValuesAsString); MultiValueMap<String, Object> getAllAnnotationAttributes(String annotationName); MultiValueMap<String, Object> getAllAnnotationAttributes(String annotationName, boolean classValuesAsString); }
回到剛纔的栗子,咱們知道@ConditionalOnLinux
註解真正起做用的是Condition
接口的具體實現類LinuxCondition
的matches
方法,那麼這個matches
方法是在什麼時候被調用的呢?
經過idea調試看調用的棧幀,以下圖:
發現是在ConditionEvaluator
的shouldSkip
方法中調用了LinuxCondition
的matches
方法,天然咱們再去看看ConditionEvaluator
的shouldSkip
的方法執行了什麼邏輯。
// 這個方法主要是若是是解析階段則跳過,若是是註冊階段則不跳過 public boolean shouldSkip(@Nullable AnnotatedTypeMetadata metadata, @Nullable ConfigurationPhase phase) { // 若沒有被@Conditional或其派生註解所標註,則不會跳過 if (metadata == null || !metadata.isAnnotated(Conditional.class.getName())) { return false; } // 沒有指定phase,注意phase能夠分爲PARSE_CONFIGURATION或REGISTER_BEAN類型 if (phase == null) { // 若標有@Component,@Import,@Bean或@Configuration等註解的話,則說明是PARSE_CONFIGURATION類型 if (metadata instanceof AnnotationMetadata && ConfigurationClassUtils.isConfigurationCandidate((AnnotationMetadata) metadata)) { return shouldSkip(metadata, ConfigurationPhase.PARSE_CONFIGURATION); } // 不然是REGISTER_BEAN類型 return shouldSkip(metadata, ConfigurationPhase.REGISTER_BEAN); } List<Condition> conditions = new ArrayList<>(); // TODO 得到全部標有@Conditional註解或其派生註解裏面的Condition接口實現類並實例化成對象。 // 好比@Conditional(OnBeanCondition.class)則得到OnBeanCondition.class,OnBeanCondition.class每每實現了Condition接口 for (String[] conditionClasses : getConditionClasses(metadata)) { // 將類實例化成對象 for (String conditionClass : conditionClasses) { Condition condition = getCondition(conditionClass, this.context.getClassLoader()); conditions.add(condition); } } // 排序,即按照Condition的優先級進行排序 AnnotationAwareOrderComparator.sort(conditions); for (Condition condition : conditions) { ConfigurationPhase requiredPhase = null; if (condition instanceof ConfigurationCondition) { // 從condition中得到對bean是解析仍是註冊 requiredPhase = ((ConfigurationCondition) condition).getConfigurationPhase(); } // 若requiredPhase爲null或獲取的階段類型正是當前階段類型且不符合condition的matches條件,則跳過 if ((requiredPhase == null || requiredPhase == phase) && !condition.matches(this.context, metadata)) { return true; } } return false; }
shouldSkip
這個方法執行的邏輯主要是若是是解析階段則跳過,若是是註冊階段則不跳過;若是是在註冊階段即REGISTER_BEAN
階段的話,此時會獲得全部的Condition
接口的具體實現類並實例化這些實現類,而後再執行下面關鍵的代碼進行判斷是否須要跳過。
if ((requiredPhase == null || requiredPhase == phase) && !condition.matches(this.context, metadata)) { return true; }
上面代碼最重要的邏輯是調用了Condition
接口的具體實現類的matches
方法,若matches
返回false
,則跳過,不進行註冊bean
的操做;若matches
返回true
,則不跳過,進行註冊bean
的操做;
好了,Condition
的源碼分析就到此爲止,再往上翻調用方法的話應該就是Spring加載bean
定義的相關源碼了,不屬於這裏的分析範圍。
前面咱們學會了如何自定義條件註解及Condition
的源碼分析,那麼咱們不由好奇,Spring究竟內置了哪些Condition
接口的實現類呢?
那麼看下Spring的Condition
接口的具體實現類的類圖:
發現Spring內置的Condition
接口的具體實現類雖然有多個,但只有ProfileCondition
不是測試相關的,所以能夠說真正的內置的Condition
接口的具體實現類只有ProfileCondition
一個,很是很是少,這跟SpringBoot的大量派生條件註解造成了鮮明的對比。ProfileCondition
你們都知道,是跟環境有關,好比咱們平時通常有dev
,test
和prod
環境,而ProfileCondition
就是判斷咱們項目配置了哪一個環境的。下面是ProfileCondition
的源碼,很簡單,這裏就不分析了。
class ProfileCondition implements Condition { @Override public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { MultiValueMap<String, Object> attrs = metadata.getAllAnnotationAttributes(Profile.class.getName()); if (attrs != null) { for (Object value : attrs.get("value")) { if (context.getEnvironment().acceptsProfiles(Profiles.of((String[]) value))) { return true; } } return false; } return true; } }
前面看到Spring對Condition
的內置註解能夠說只有ProfileCondition
一個,可是咱們都知道,SpringBoot則內置了大量的條件註解ConditionalOnXXX
。在分析前,咱們先來看一下SpringBootCondition
的總體類圖來個總體的理解,以下圖:
能夠看到SpringBootCondition
做爲SpringBoot條件註解的基類,處於整個類圖的中心,它實現了Condition
接口,而後又有不少具體的子類OnXXXCondition
,這些OnXXXCondition
其實就是@ConditionalOnXXX
的條件類。
咱們先來看下SpringBootCondition
這個父類是主要作了哪些事情,抽象了哪些共有的邏輯?
SpringBootConditon
實現了Condition
接口,做爲SpringBoot衆多條件註解OnXXXCondtion
的父類,它的做用主要就是打印一些條件註解評估報告的日誌,好比打印哪些配置類是符合條件註解的,哪些是不符合的。打印的日誌形式以下圖:
由於SpringBootConditon
實現了Condition
接口,也實現了matches
方法,所以該方法一樣也是被ConditionEvaluator
的shouldSkip
方法中調用,所以咱們就以SpringBootConditon
的matches
方法爲入口去進行分析。直接上代碼:
// SpringBootCondition.java public final boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { // 獲得metadata的類名或方法名 String classOrMethodName = getClassOrMethodName(metadata); try { // 判斷每一個配置類的每一個條件註解@ConditionalOnXXX是否知足條件,而後記錄到ConditionOutcome結果中 // 注意getMatchOutcome是一個抽象模板方法,交給OnXXXCondition子類去實現 ConditionOutcome outcome = getMatchOutcome(context, metadata); // 打印condition評估的日誌,哪些條件註解@ConditionalOnXXX是知足條件的,哪些是不知足條件的,這些日誌都打印出來 logOutcome(classOrMethodName, outcome); // 除了打印日誌外,這些是否匹配的信息還要記錄到ConditionEvaluationReport中 recordEvaluation(context, classOrMethodName, outcome); // 最後返回@ConditionalOnXXX是否知足條件 return outcome.isMatch(); } catch (NoClassDefFoundError ex) { throw new IllegalStateException( "Could not evaluate condition on " + classOrMethodName + " due to " + ex.getMessage() + " not " + "found. Make sure your own configuration does not rely on " + "that class. This can also happen if you are " + "@ComponentScanning a springframework package (e.g. if you " + "put a @ComponentScan in the default package by mistake)", ex); } catch (RuntimeException ex) { throw new IllegalStateException( "Error processing condition on " + getName(metadata), ex); } }
上面代碼的註釋已經很是詳細,咱們知道了SpringBootCondition
抽象了全部其具體實現類OnXXXCondition
的共有邏輯--condition
評估信息打印,最重要的是封裝了一個模板方法getMatchOutcome(context, metadata)
,留給各個OnXXXCondition
具體子類去覆蓋實現屬於本身的判斷邏輯,而後再返回相應的匹配結果給SpringBootCondition
用於日誌打印。
所以咱們知道了SpringBootCondition
其實就是用來打印condition
評估信息的,對於其餘枝節方法咱們沒必要追究過深,省得丟了主線。咱們如今的重點是放在交給OnXXXCondition
子類實現的模板方法上getMatchOutcome(context, metadata);
,由於這個方法將會由不少OnXXXCondition
覆蓋重寫判斷邏輯,這裏是咱們接下來分析的重點。
由於SpringBootCondition
有衆多具體實現類,下面只挑OnResourceCondition
,OnBeanCondition
和OnWebApplicationCondition
進行講解,而AutoConfigurationImportFilter
跟自動配置有關,則留到自動配置源碼解析的時候再進行分析。
如今先來看下一個邏輯及其簡單的註解條件類OnResourceCondition
,OnResourceCondition
繼承了SpringBootCondition
父類,覆蓋了其getMatchOutcome
方法,用於@ConditionalOnResource
註解指定的資源存在與否。OnResourceCondition
的判斷邏輯很是簡單,主要拿到@ConditionalOnResource
註解指定的資源路徑後,而後用ResourceLoader
根據指定路徑去加載看資源存不存在。下面直接看代碼:
先來看下@ConditionalOnResource
的代碼,
@Target({ ElementType.TYPE, ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) @Documented @Conditional(OnResourceCondition.class) public @interface ConditionalOnResource { /** * The resources that must be present. * @return the resource paths that must be present. */ String[] resources() default {}; }
再來看OnResourceCondition
的代碼:
@Order(Ordered.HIGHEST_PRECEDENCE + 20) class OnResourceCondition extends SpringBootCondition { private final ResourceLoader defaultResourceLoader = new DefaultResourceLoader(); @Override public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) { // 得到@ConditionalOnResource註解的屬性元數據 MultiValueMap<String, Object> attributes = metadata .getAllAnnotationAttributes(ConditionalOnResource.class.getName(), true); // 得到資源加載器,若ConditionContext中有ResourceLoader則用ConditionContext中的,沒有則用默認的 ResourceLoader loader = (context.getResourceLoader() != null) ? context.getResourceLoader() : this.defaultResourceLoader; List<String> locations = new ArrayList<>(); // 將@ConditionalOnResource中定義的resources屬性值取出來裝進locations集合 collectValues(locations, attributes.get("resources")); Assert.isTrue(!locations.isEmpty(), "@ConditionalOnResource annotations must specify at " + "least one resource location"); // missing集合是裝不存在指定資源的資源路徑的 List<String> missing = new ArrayList<>(); // 遍歷全部的資源路徑,若指定的路徑的資源不存在則將其資源路徑存進missing集合中 for (String location : locations) { // 這裏針對有些資源路徑是Placeholders的狀況,即處理${} String resource = context.getEnvironment().resolvePlaceholders(location); if (!loader.getResource(resource).exists()) { missing.add(location); } } // 若是存在某個資源不存在,那麼則報錯 if (!missing.isEmpty()) { return ConditionOutcome.noMatch(ConditionMessage .forCondition(ConditionalOnResource.class) .didNotFind("resource", "resources").items(Style.QUOTE, missing)); } // 全部資源都存在,那麼則返回能找到就提的資源 return ConditionOutcome .match(ConditionMessage.forCondition(ConditionalOnResource.class) .found("location", "locations").items(locations)); } // 將@ConditionalOnResource中定義的resources屬性值取出來裝進locations集合 private void collectValues(List<String> names, List<Object> values) { for (Object value : values) { for (Object item : (Object[]) value) { names.add((String) item); } } } }
能夠看到OnResourceCondition
的getMatchOutcome
方法很是簡單,這裏再也不詳述。
OnBeanCondition
一樣繼承了FilteringSpringBootCondition
父類,覆蓋了父類FilteringSpringBootCondition
的getOutcomes
方法。而FilteringSpringBootCondition
又是SpringBootCondition
的子類,FilteringSpringBootCondition
跟自動配置類過濾有關,這裏先不分析。值得注意的是OnBeanCondition
一樣重寫了SpringBootCondition
的getMatchOutcome
方法,用來判斷Spring容器中是否存在指定條件的bean
。同時是OnBeanCondition
是@ConditionalOnBean
,@ConditionalOnSingleCandidate
和ConditionalOnMissingBean
的條件類。
一樣,先來看OnBeanCondition
複寫父類SpringBootCondition
的getMatchOutcome
方法的代碼:
@Override public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) { ConditionMessage matchMessage = ConditionMessage.empty(); // (1),配置類(metadata)標註@ConditionalOnBean註解的狀況 if (metadata.isAnnotated(ConditionalOnBean.class.getName())) { // 將@ConditionalOnBean註解屬性封裝進BeanSearchSpec對象中 // 注意BeanSearchSpec是一個靜態內部類,用來存儲@ConditionalOnBean和@ConditionalOnMissingBean註解的屬性值 BeanSearchSpec spec = new BeanSearchSpec(context, metadata, ConditionalOnBean.class); // 調用getMatchingBeans獲得符合條件的bean MatchResult matchResult = getMatchingBeans(context, spec); // 若是不匹配 if (!matchResult.isAllMatched()) { String reason = createOnBeanNoMatchReason(matchResult); return ConditionOutcome.noMatch(ConditionMessage .forCondition(ConditionalOnBean.class, spec).because(reason)); } // 若是匹配 matchMessage = matchMessage.andCondition(ConditionalOnBean.class, spec) .found("bean", "beans") .items(Style.QUOTE, matchResult.getNamesOfAllMatches()); } // (2),配置類(metadata)標註@ConditionalOnSingleCandidate註解的狀況 if (metadata.isAnnotated(ConditionalOnSingleCandidate.class.getName())) { BeanSearchSpec spec = new SingleCandidateBeanSearchSpec(context, metadata, ConditionalOnSingleCandidate.class); MatchResult matchResult = getMatchingBeans(context, spec); if (!matchResult.isAllMatched()) { return ConditionOutcome.noMatch(ConditionMessage .forCondition(ConditionalOnSingleCandidate.class, spec) .didNotFind("any beans").atAll()); } else if (!hasSingleAutowireCandidate(context.getBeanFactory(), matchResult.getNamesOfAllMatches(), spec.getStrategy() == SearchStrategy.ALL)) { return ConditionOutcome.noMatch(ConditionMessage .forCondition(ConditionalOnSingleCandidate.class, spec) .didNotFind("a primary bean from beans") .items(Style.QUOTE, matchResult.getNamesOfAllMatches())); } matchMessage = matchMessage .andCondition(ConditionalOnSingleCandidate.class, spec) .found("a primary bean from beans") .items(Style.QUOTE, matchResult.getNamesOfAllMatches()); } // (3),配置類(metadata)標註@ConditionalOnMissingBean註解的狀況 if (metadata.isAnnotated(ConditionalOnMissingBean.class.getName())) { BeanSearchSpec spec = new BeanSearchSpec(context, metadata, ConditionalOnMissingBean.class); MatchResult matchResult = getMatchingBeans(context, spec); if (matchResult.isAnyMatched()) { String reason = createOnMissingBeanNoMatchReason(matchResult); return ConditionOutcome.noMatch(ConditionMessage .forCondition(ConditionalOnMissingBean.class, spec) .because(reason)); } matchMessage = matchMessage.andCondition(ConditionalOnMissingBean.class, spec) .didNotFind("any beans").atAll(); } // 最終返回matchMessage return ConditionOutcome.match(matchMessage); }
咱們能夠看到OnBeanCondition
類覆蓋的getMatchOutcome
方法分別處理了標註@ConditionalOnBean
,@ConditionalOnSingleCandidate
和@ConditionalOnMissingBean
註解的狀況,分別對應上面代碼註釋的(1)
,(2)
和(3)
處。
如今咱們只看針對@ConditionalOnBean
註解的處理邏輯,從上面代碼中能夠看到若配置類(metadata)標註@ConditionalOnBean
註解的話,主要作了如下事情:
BeanSearchSpec
對象中;getMatchingBeans(context, spec)
方法來獲取是否有匹配的bean
;bean
的匹配狀況;能夠看到最重要的邏輯是第2步,那麼咱們再來看下getMatchingBeans
方法,直接上代碼:
protected final MatchResult getMatchingBeans(ConditionContext context, BeanSearchSpec beans) { // 得到Spring容器的beanFactory ConfigurableListableBeanFactory beanFactory = context.getBeanFactory(); // 判斷bean的搜索策略是不是SearchStrategy.ANCESTORS策略 if (beans.getStrategy() == SearchStrategy.ANCESTORS) { BeanFactory parent = beanFactory.getParentBeanFactory(); Assert.isInstanceOf(ConfigurableListableBeanFactory.class, parent, "Unable to use SearchStrategy.PARENTS"); beanFactory = (ConfigurableListableBeanFactory) parent; } // MatchResult用來存儲bean的匹配結果 MatchResult matchResult = new MatchResult(); // 若是bean的搜索策略不是SearchStrategy.CURRENT的話,則置considerHierarchy爲true boolean considerHierarchy = beans.getStrategy() != SearchStrategy.CURRENT; // 獲取TypeExtractor,TypeExtractor是用來判斷bean的類型的 TypeExtractor typeExtractor = beans.getTypeExtractor(context.getClassLoader()); // 獲取是否有被忽略bean類型,如有的話將該bean類型的名稱裝進beansIgnoredByType集合 // 這裏主要是針對@ConditionalOnMissingBean的ignored屬性 List<String> beansIgnoredByType = getNamesOfBeansIgnoredByType( beans.getIgnoredTypes(), typeExtractor, beanFactory, context, considerHierarchy); // 遍歷bean的全部類型 for (String type : beans.getTypes()) { // 調用getBeanNamesForType方法根據bean類型獲得全部符合條件的bean類型,並放到typeMatches集合 Collection<String> typeMatches = getBeanNamesForType(beanFactory, type, typeExtractor, context.getClassLoader(), considerHierarchy); // 移除掉Ignored的類型 typeMatches.removeAll(beansIgnoredByType); // 若typeMatches爲空,那麼則說明正在遍歷的這個type類型不符合匹配條件,此時用matchResult記錄一下這個不符合條件的類型 if (typeMatches.isEmpty()) { matchResult.recordUnmatchedType(type); } // 若typeMatches不爲空,那麼則說明正在遍歷的這個type類型符合匹配條件,此時用matchResult記錄一下這個符合條件的類型 else { matchResult.recordMatchedType(type, typeMatches); } } // 這裏針對@ConditionalOnBean等註解的annotation屬性的處理 for (String annotation : beans.getAnnotations()) { List<String> annotationMatches = Arrays .asList(getBeanNamesForAnnotation(beanFactory, annotation, context.getClassLoader(), considerHierarchy)); annotationMatches.removeAll(beansIgnoredByType); if (annotationMatches.isEmpty()) { matchResult.recordUnmatchedAnnotation(annotation); } else { matchResult.recordMatchedAnnotation(annotation, annotationMatches); } } // 這裏針對@ConditionalOnBean等註解的name屬性的處理 for (String beanName : beans.getNames()) { // beansIgnoredByType集合不包含beanName且beanFactory包含這個bean,則匹配 if (!beansIgnoredByType.contains(beanName) && containsBean(beanFactory, beanName, considerHierarchy)) { matchResult.recordMatchedName(beanName); } // 不然,不匹配 else { matchResult.recordUnmatchedName(beanName); } } // 最後返回匹配結果 return matchResult; }
上面的邏輯主要是從spring容器中搜索有無指定條件的bean
,搜索Spring容器搜索bean的話有三種搜索策略,分別是CURRENT
,ANCESTORS
和ALL
,分表表示只從當前的context
中搜索bean
,只從父context
中搜索bean
和從整個context
中搜索bean
;定義了搜索策略後,而後再根據BeanSearchSpec
對象封裝的註解屬性分別取指定的容器中查找有無符合條件的bean
,而後再進行一些過濾。好比@ConditionalOnMissingBean
註解有定義ignored
屬性值,那麼從容器中搜索到有符合條件的bean
時,此時還要移除掉ignored
指定的bean
。
好了,上面就已經分析了OnBeanCondition
這個條件類了,咱們堅持主線優先的原則,具體的細節代碼不會深究。
OnWebApplicationCondition
一樣繼承了FilteringSpringBootCondition
父類,覆蓋了父類FilteringSpringBootCondition
的getOutcomes
方法。而FilteringSpringBootCondition
又是SpringBootCondition
的子類,FilteringSpringBootCondition
跟自動配置類過濾有關,這裏先不分析。值得注意的是OnWebApplicationCondition
一樣重寫了SpringBootCondition
的getMatchOutcome
方法,用來判斷當前應用是否web應用。同時是OnWebApplicationCondition
是@ConditionalOnWebApplication
的條件類。
一樣,先來看OnWebApplicationCondition
重寫SpringBootCondition
的getMatchOutcome
方法:
public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) { // 配置類是否標註有@ConditionalOnWebApplication註解 boolean required = metadata .isAnnotated(ConditionalOnWebApplication.class.getName()); // 調用isWebApplication方法返回匹配結果 ConditionOutcome outcome = isWebApplication(context, metadata, required); // 如有標註@ConditionalOnWebApplication但不符合條件,則返回不匹配 if (required && !outcome.isMatch()) { return ConditionOutcome.noMatch(outcome.getConditionMessage()); } // 若沒有標註@ConditionalOnWebApplication但符合條件,則返回不匹配 if (!required && outcome.isMatch()) { return ConditionOutcome.noMatch(outcome.getConditionMessage()); } // 這裏返回匹配的狀況,TODO 不過有個疑問:若是沒有標註@ConditionalOnWebApplication註解,又不符合條件的話,也會執行到這裏,返回匹配? return ConditionOutcome.match(outcome.getConditionMessage()); }
上面代碼的邏輯很簡單,主要是調用isWebApplication
方法來判斷當前應用是不是web應用。所以,咱們再來看下isWebApplication
方法:
private ConditionOutcome isWebApplication(ConditionContext context, AnnotatedTypeMetadata metadata, boolean required) { // 調用deduceType方法判斷是哪一種類型,其中有SERVLET,REACTIVE和ANY類型,其中ANY表示了SERVLET或REACTIVE類型 switch (deduceType(metadata)) { // SERVLET類型 case SERVLET: return isServletWebApplication(context); // REACTIVE類型 case REACTIVE: return isReactiveWebApplication(context); default: return isAnyWebApplication(context, required); } }
在isWebApplication
方法中,首先從@ConditionalOnWebApplication
註解中獲取其定義了什麼類型,而後根據不一樣的類型進入不一樣的判斷邏輯。這裏咱們只看下SERVLET
的狀況判斷處理,看代碼:
private ConditionOutcome isServletWebApplication(ConditionContext context) { ConditionMessage.Builder message = ConditionMessage.forCondition(""); // 若classpath中不存在org.springframework.web.context.support.GenericWebApplicationContext.class,則返回不匹配 if (!ClassNameFilter.isPresent(SERVLET_WEB_APPLICATION_CLASS, context.getClassLoader())) { return ConditionOutcome.noMatch( message.didNotFind("servlet web application classes").atAll()); } // 若classpath中存在org.springframework.web.context.support.GenericWebApplicationContext.class,那麼又分爲如下幾種匹配的狀況 // session if (context.getBeanFactory() != null) { String[] scopes = context.getBeanFactory().getRegisteredScopeNames(); if (ObjectUtils.containsElement(scopes, "session")) { return ConditionOutcome.match(message.foundExactly("'session' scope")); } } // ConfigurableWebEnvironment if (context.getEnvironment() instanceof ConfigurableWebEnvironment) { return ConditionOutcome .match(message.foundExactly("ConfigurableWebEnvironment")); } // WebApplicationContext if (context.getResourceLoader() instanceof WebApplicationContext) { return ConditionOutcome.match(message.foundExactly("WebApplicationContext")); } // 若以上三種都不匹配的話,則說明不是一個servlet web application return ConditionOutcome.noMatch(message.because("not a servlet web application")); }
對因而SERVLET
的狀況,首先根據classpath
中是否存在org.springframework.web.context.support.GenericWebApplicationContext.class
,若是不存在該類,則直接返回不匹配;若存在的話那麼又分爲如下幾種匹配的狀況:
若上面三種狀況都不匹配,則說明不是一個servlet web application。
因爲springboot的OnXXXCondition
類實現太多,不可能每一個條件類都分析一遍,所以上面只分析了OnResourceCondition
,OnBeanCondition
和onWebApplicationCondition
的源碼。咱們分析源碼不可能把全部代碼都通讀一遍的,閱讀源碼的話,只要理解了某個模塊的類之間的關係及挑幾個有表明性的類分析下就行,不可能一網打盡。
如有時間的話,推薦看下幾個咱們經常使用的條件類的源碼:OnPropertyCondition
,OnClassCondition
和OnExpressionCondition
等。
前文咱們知道了如何擴展Spring的Condition
接口,那麼咱們該如何擴展SpringBoot的SpringBootCondition
類呢?
推薦閱讀springboot之使用SpringBootCondition得到答案
好了,本篇文章是SpringBoot自動配置源碼分析的前置文章,這裏分析了條件註解源碼,那麼下篇文章咱們就來看看SpringBoot自動配置的源碼了。
下節預告:
<font color=Blue>SpringBoot新特性:SpringBoot是如何自動配置的?--SpringBoot源碼(四)</font>
原創不易,幫忙點個讚唄!
參考:
3,spring boot 系列之六:深刻理解spring boot的自動配置