微信公衆號:吉姆餐廳ak 學習更多源碼知識,歡迎關注。 spring
SpringBoot2 | SpringBoot啓動流程源碼分析(一)springboot
SpringBoot2 | SpringBoot啓動流程源碼分析(二)bash
SpringBoot2 | @SpringBootApplication註解 自動化配置流程源碼分析(三)微信
SpringBoot2 | SpringBoot Environment源碼分析(四)框架
SpringBoot2 | SpringBoot自定義AutoConfiguration | SpringBoot自定義starter(五)ide
SpringBoot2 | SpringBoot監聽器源碼分析 | 自定義ApplicationListener(六)源碼分析
SpringBoot2 | 條件註解@ConditionalOnBean原理源碼深度解析(七)post
SpringBoot2 | Spring AOP 原理源碼深度剖析(八)學習
SpringBoot2 | SpingBoot FilterRegistrationBean 註冊組件 | FilterChain 責任鏈源碼分析(九)ui
SpringBoot2 | BeanDefinition 註冊核心類 ImportBeanDefinitionRegistrar (十)
SpringBoot2 | Spring 核心擴展接口 | 核心擴展方法總結(十一)
條件註解是
Spring4
提供的一種bean加載特性,主要用於控制配置類和bean初始化條件。在springBoot,springCloud
一系列框架底層源碼中,條件註解的使用處處可見。
很多人在使用@ConditionalOnBean
註解時會遇到不生效的狀況,依賴的 bean 明明已經配置了,但就是不生效。到底@ConditionalOnBean
和bean加載的順序有沒有關係呢?跟着源碼,一探究竟。
問題演示:
@Configuration
public class Configuration1 {
@Bean
@ConditionalOnBean(Bean2.class)
public Bean1 bean1() {
return new Bean1();
}
}
複製代碼
@Configuration
public class Configuration2 {
@Bean
public Bean2 bean2(){
return new Bean2();
}
}
複製代碼
結果: @ConditionalOnBean(Bean2.class)
返回false。 命名定義了bean2
,bean1
卻未加載。
首先要明確一點,條件註解的解析必定發生在spring ioc的bean definition
階段,由於 spring bean初始化的前提條件就是有對應的bean definition
,條件註解正是經過判斷bean definition
來控制bean可否實例化。
對上述示例進行源碼調試。
從 bean definition解析的入口開始:ConfigurationClassPostProcessor
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
int registryId = System.identityHashCode(registry);
if (this.registriesPostProcessed.contains(registryId)) {
throw new IllegalStateException(
"postProcessBeanDefinitionRegistry already called on this post-processor against " + registry);
}
if (this.factoriesPostProcessed.contains(registryId)) {
throw new IllegalStateException(
"postProcessBeanFactory already called on this post-processor against " + registry);
}
this.registriesPostProcessed.add(registryId);
// 解析bean definition入口
processConfigBeanDefinitions(registry);
}
複製代碼
跟進processConfigBeanDefinitions
方法:
public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
//省略沒必要要的代碼...
//解析候選bean,先獲取全部的配置類,也就是@Configuration標註的類
parser.parse(candidates);
parser.validate();
//配置類存入集合
Set<ConfigurationClass> configClasses = new LinkedHashSet<>(parser.getConfigurationClasses());
configClasses.removeAll(alreadyParsed);
// Read the model and create bean definitions based on its content
if (this.reader == null) {
this.reader = new ConfigurationClassBeanDefinitionReader(
registry, this.sourceExtractor, this.resourceLoader, this.environment,
this.importBeanNameGenerator, parser.getImportRegistry());
}
//開始解析配置類,也就是條件註解解析的入口
this.reader.loadBeanDefinitions(configClasses);
alreadyParsed.addAll(configClasses);
//...
}
複製代碼
跟進條件註解解析入口loadBeanDefinitions
,開始循環解析配置類。這裏是全部自定義的配置類和自動裝配的配置類,以下:
在解析方法loadBeanDefinitionsForConfigurationClass()
中,會得到配置類中定義bean的全部方法, 並調用loadBeanDefinitionsForBeanMethod()
方法來進行循環解析,解析時會執行以下校驗方法,也正是條件註解的入口:
public boolean shouldSkip(@Nullable AnnotatedTypeMetadata metadata, @Nullable ConfigurationPhase phase) {
//判斷是否有條件註解,不然直接返回
if (metadata == null || !metadata.isAnnotated(Conditional.class.getName())) {
return false;
}
if (phase == null) {
if (metadata instanceof AnnotationMetadata &&
ConfigurationClassUtils.isConfigurationCandidate((AnnotationMetadata) metadata)) {
return shouldSkip(metadata, ConfigurationPhase.PARSE_CONFIGURATION);
}
return shouldSkip(metadata, ConfigurationPhase.REGISTER_BEAN);
}
//獲取當前定義bean的方法上,全部的條件註解
List<Condition> conditions = new ArrayList<>();
for (String[] conditionClasses : getConditionClasses(metadata)) {
for (String conditionClass : conditionClasses) {
Condition condition = getCondition(conditionClass, this.context.getClassLoader());
conditions.add(condition);
}
}
//根據Order來進行排序
AnnotationAwareOrderComparator.sort(conditions);
//遍歷條件註解,開始執行條件註解的流程
for (Condition condition : conditions) {
ConfigurationPhase requiredPhase = null;
if (condition instanceof ConfigurationCondition) {
requiredPhase = ((ConfigurationCondition) condition).getConfigurationPhase();
}
//這裏執行條件註解的 condition.matches 方法來進行匹配,返回布爾值
if ((requiredPhase == null || requiredPhase == phase) && !condition.matches(this.context, metadata)) {
return true;
}
}
return false;
}
複製代碼
繼續跟進條件註解的匹配方法:
這裏開始解析示例代碼中bean1
的配置:
@Bean
@ConditionalOnBean(Bean2.class)
public Bean1 bean1() {
return new Bean1();
}
複製代碼
上述getMatchOutcome
方法中,參數metadata
是要解析的目標bean,也就是bean1
。條件註解依賴的bean
被封裝成了BeanSearchSpec
,從名字能夠看出是要尋找的對象,這是一個靜態內部類,構造方法以下:
BeanSearchSpec(ConditionContext context, AnnotatedTypeMetadata metadata,
Class<?> annotationType) {
this.annotationType = annotationType;
//讀取 metadata中的設置的value
MultiValueMap<String, Object> attributes = metadata
.getAllAnnotationAttributes(annotationType.getName(), true);
//設置各參數,根據這些參數進行尋找目標類
collect(attributes, "name", this.names);
collect(attributes, "value", this.types);
collect(attributes, "type", this.types);
collect(attributes, "annotation", this.annotations);
collect(attributes, "ignored", this.ignoredTypes);
collect(attributes, "ignoredType", this.ignoredTypes);
this.strategy = (SearchStrategy) metadata
.getAnnotationAttributes(annotationType.getName()).get("search");
BeanTypeDeductionException deductionException = null;
try {
if (this.types.isEmpty() && this.names.isEmpty()) {
addDeducedBeanType(context, metadata, this.types);
}
}
catch (BeanTypeDeductionException ex) {
deductionException = ex;
}
validate(deductionException);
}
複製代碼
繼續跟進搜索bean的方法:
MatchResult matchResult = getMatchingBeans(context, spec);
複製代碼
private MatchResult getMatchingBeans(ConditionContext context, BeanSearchSpec beans) {
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
if (beans.getStrategy() == SearchStrategy.ANCESTORS) {
BeanFactory parent = beanFactory.getParentBeanFactory();
Assert.isInstanceOf(ConfigurableListableBeanFactory.class, parent,
"Unable to use SearchStrategy.PARENTS");
beanFactory = (ConfigurableListableBeanFactory) parent;
}
MatchResult matchResult = new MatchResult();
boolean considerHierarchy = beans.getStrategy() != SearchStrategy.CURRENT;
List<String> beansIgnoredByType = getNamesOfBeansIgnoredByType(
beans.getIgnoredTypes(), beanFactory, context, considerHierarchy);
//由於實例代碼中設置的是類型,因此這裏會遍歷類型,根據type獲取目標bean是否存在
for (String type : beans.getTypes()) {
Collection<String> typeMatches = getBeanNamesForType(beanFactory, type,
context.getClassLoader(), considerHierarchy);
typeMatches.removeAll(beansIgnoredByType);
if (typeMatches.isEmpty()) {
matchResult.recordUnmatchedType(type);
}
else {
matchResult.recordMatchedType(type, typeMatches);
}
}
//根據註解尋找
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);
}
}
//根據設置的name進行尋找
for (String beanName : beans.getNames()) {
if (!beansIgnoredByType.contains(beanName)
&& containsBean(beanFactory, beanName, considerHierarchy)) {
matchResult.recordMatchedName(beanName);
}
else {
matchResult.recordUnmatchedName(beanName);
}
}
return matchResult;
}
複製代碼
getBeanNamesForType()
方法最終會委託給BeanTypeRegistry
類的getNamesForType
方法來獲取對應的指定類型的bean name:
Set<String> getNamesForType(Class<?> type) {
//同步spring容器中的bean
updateTypesIfNecessary();
//返回指定類型的bean
return this.beanTypes.entrySet().stream()
.filter((entry) -> entry.getValue() != null
&& type.isAssignableFrom(entry.getValue()))
.map(Map.Entry::getKey)
.collect(Collectors.toCollection(LinkedHashSet::new));
}
複製代碼
重點來了。 上述方法中的第一步即是同步bean,也就是獲取此時 spring 容器中的全部 beanDifinition。只有這樣,條件註解的判斷纔有意義。
咱們跟進updateTypesIfNecessary()
:
private void updateTypesIfNecessary() {
//這裏lastBeanDefinitionCount 表明已經同步的數量,若是和容器中的數量不相等,纔開始同步。
//不然,獲取beanFactory迭代器,開始同步。
if (this.lastBeanDefinitionCount != this.beanFactory.getBeanDefinitionCount()) {
Iterator<String> names = this.beanFactory.getBeanNamesIterator();
while (names.hasNext()) {
String name = names.next();
if (!this.beanTypes.containsKey(name)) {
addBeanType(name);
}
}
//同步完以後,更新已同步的beanDefinition數量。
this.lastBeanDefinitionCount = this.beanFactory.getBeanDefinitionCount();
}
}
複製代碼
離答案只差一步了,就是看一下從beanFactory
中迭代的是哪些beanDefinition
?
跟進beanFactory.getBeanNamesIterator();
方法:
@Override
public Iterator<String> getBeanNamesIterator() {
CompositeIterator<String> iterator = new CompositeIterator<>();
iterator.add(this.beanDefinitionNames.iterator());
iterator.add(this.manualSingletonNames.iterator());
return iterator;
}
複製代碼
分別來看:
beanDefinitionNames
就是存儲一些自動解析和裝配的bean,咱們的啓動類、配置類、controller、service
等。
manualSingletonNames
,從名字能夠看出,手工單例名稱。什麼意思呢?在 spring ioc
的過程當中,會手動觸發一些bean的註冊。好比在springboot
啓動過程當中,會顯示的註冊一些配置 bean,如: springBootBanner,systemEnvironment,systemProperties
等。
咱們來分析一下上面示例bean1
爲什麼沒有實例化?
在spring ioc
的過程當中,優先解析@Component,@Service,@Controller
註解的類。其次解析配置類,也就是@Configuration
標註的類。最後開始解析配置類中定義的bean
。 示例代碼中bean1
是定義在配置類中的,當執行到配置類解析的時候,@Component,@Service,@Controller ,@Configuration
標註的類已經所有被解析,因此這些BeanDifinition
已經被同步。 可是bean1
的條件註解依賴的是bean2
,bean2
是被定義的配置類中的,由於兩個Bean
都是配置類中Bean
,因此此時配置類的解析沒法保證前後順序,就會出現不生效的狀況。
一樣的道理,若是依賴的是FeignClient
,也有可能會出現不生效的狀況。由於FeignClient
最終仍是由配置類觸發,解析的前後順序也不能保證。
有兩種方式:
spring
容器管理,因此若是要在配置中Bean
經過@ConditionalOnBean
依賴配置中的Bean
時,徹底能夠用@ConditionalOnClass(Bean2.class)
來代替。EnableAutoConfiguration
管理和觸發。也就是定義在META-INF\spring.factories
中聲明是配置類,而後經過@AutoConfigureBefore、AutoConfigureAfter、AutoConfigureOrder
控制前後順序。由於這三個註解只對自動配置類生效。在配置類中定義Bean
,若是使用@ConditionalOnBean
依賴的也是配置類中Bean
,則執行結果不可控,和配置類加載順序有關。
SpringBoot2 | SpringBoot啓動流程源碼分析(一)
SpringBoot2 | SpringBoot啓動流程源碼分析(二)
SpringBoot2 | @SpringBootApplication註解 自動化配置流程源碼分析(三)
SpringBoot2 | SpringBoot Environment源碼分析(四)
SpringBoot2 | SpringBoot自定義AutoConfiguration | SpringBoot自定義starter(五)