SpringBoot2 | 條件註解 @ConditionalOnBean 原理源碼分析(七)

微信公衆號:吉姆餐廳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。 命名定義了bean2bean1卻未加載。


源碼分析

首先要明確一點,條件註解的解析必定發生在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的條件註解依賴的是bean2bean2是被定義的配置類中的,由於兩個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(五)

SpringBoot2 | SpringBoot監聽器源碼分析 | 自定義ApplicationListener(六)

SpringBoot2 | 條件註解@ConditionalOnBean原理源碼深度解析(七)

相關文章
相關標籤/搜索