SpringBoot自动配置源码解析+自定义Spring Boot Starter

@SpringBootApplication

        Spring Boot应用标注 @SpringBootApplication 注解的类说明该类是Spring Boot 的主配置类,需要运行该类的main方法进行启动 Spring Boot 应用

@SpringBootConfiguration

       该注解标注表示标注的类是个配置类

@EnableAutoConfiguration

        直译:开启自动配置

@AutoConfigurationPackage

        将当前配置类所在的包保存在 basePackages 的Bean 中,提供给Spring 使用

@Import(AutoConfigurationPackages.Registrar.class)

      注册一个保存当前配置类所在包的Bean

@Import(AutoConfigurationImportSelector.class)

        使用@Import注解完成导入AutoConfigurationImportSelector类 的功能。AutoConfigurationImportSelector 实现了DeferredImportSelector类

注:在解析ImportSelector时,所导入的配置类会被直接解析,而DeferredImportSelector导入的配置类会延迟进行解析(延迟在其他配置类都解析完之后)

        Spring容器在解析@Import时会去执行DeferredImportSelector 的 selectImports方法:

/**
	 * Return the {@link AutoConfigurationEntry} based on the {@link AnnotationMetadata}
	 * of the importing {@link Configuration @Configuration} class.
	 * @param annotationMetadata the annotation metadata of the configuration class
	 * @return the auto-configurations that should be imported
	 */
	protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
		if (!isEnabled(annotationMetadata)) {
			return EMPTY_ENTRY;
		}
		AnnotationAttributes attributes = getAttributes(annotationMetadata);
        // 从META-INF/spring.factories中获得候选的自动配置类
		List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
        //去重
		configurations = removeDuplicates(configurations);
        //根据EnableAutoConfiguration注解中exclude、excludeName属性、
        //spring.autoconfigure.exclude 配置的排除项
		Set<String> exclusions = getExclusions(annotationMetadata, attributes);
		checkExcludedClasses(configurations, exclusions);
		configurations.removeAll(exclusions);
        // 通过读取spring.factories 中        
        // OnBeanCondition\OnClassCondition\OnWebApplicationCondition进行过滤
		configurations = getConfigurationClassFilter().filter(configurations);
		fireAutoConfigurationImportEvents(configurations, exclusions);
		return new AutoConfigurationEntry(configurations, exclusions);
	}

        springboot应用中都会引入spring-boot-autoconfigure依赖,spring.factories文件就在该包的META-INF下面。spring.factories文件是Key=Value形式,多个Value时使用“,”逗号进行分割,该文件中定义了关于初始化、监听器、过滤器等信息,而真正使自动配置生效的key是org.springframework.boot.autoconfigure.EnableAutoConfiguration,如下所示:

Spring Boot 提供的自动配置类

   https://docs.spring.io/spring-boot/docs/current/reference/html/auto-configuration-classes.html#appendix.auto-configuration-classes

Spring Boot 常用条件注解

        ConditionalOnBean:是否存在某个某类或某个名字的Bean
        ConditionalOnMissingBean:是否缺失某个某类或某个名字的Bean
        ConditionalOnSingleCandidate:是否符合指定类型的Bean只有一个
        ConditionalOnClass:是否存在某个类
        ConditionalOnMissingClass:是否缺失某个类
        ConditionalOnExpression:指定的表达式返回的是true还是false
        ConditionalOnJava:判断Java版本
        ConditionalOnWebApplication:当前应用是不是一个Web应用
        ConditionalOnNotWebApplication:当前应用不是一个Web应用
        ConditionalOnProperty:Environment中是否存在某个属性

        我们也可以使用@Conditional注解进行自定义条件注解

        条件注解可以写在类和方法上,如果某个@xxxCondition条件注解写在自动配置类上,那该自动配置类会不会生效就要看当前条件是否符合条件,或者条件注解写在某个@Bean修饰的方法上,那么Bean生不生效也要看当前的条件是否符条件。

        Spring容器在解析某个自动配置类时,会先判断该自动配置类上是否有条件注解,如果有,则进一步判断条件注解所指定的条件当前情况是否满足,如果满足,则继续解析该配置类,如果不满足则不进行解析该配置类,于是配置类所定义的Bean也都得不到解析,那么在Spring容器中就不会存在该Bean。
        同理,Spring在解析某个@Bean的方法时,也会先判断方法上是否有条件注解,然后进行解析,如果不满足条件,则该Bean也不会生效。

        Spring Boot提供的自动配置,实际上就是Spring Boot源码中预先写好准备了一些常用的配置类,预先定义好了一些Bean,在用Spring Boot时,这些配置类就已经在我们项目的依赖中了,而这些自动配置类或自动配置Bean是否生效,就需要看具体指定的条件是否满足。

        下面代码就是根据 @Conditional 确定是否应跳过忽略

/**
	 * Determine if an item should be skipped based on {@code @Conditional} annotations.
	 * @param metadata the meta data
	 * @param phase the phase of the call
	 * @return if the item should be skipped
	 */
	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);
		}

		List<Condition> conditions = new ArrayList<>();
		for (String[] conditionClasses : getConditionClasses(metadata)) {
			for (String conditionClass : conditionClasses) {
				Condition condition = getCondition(conditionClass, this.context.getClassLoader());
				conditions.add(condition);
			}
		}

		AnnotationAwareOrderComparator.sort(conditions);

		for (Condition condition : conditions) {
			ConfigurationPhase requiredPhase = null;
			if (condition instanceof ConfigurationCondition) {
				requiredPhase = ((ConfigurationCondition) condition).getConfigurationPhase();
			}
			if ((requiredPhase == null || requiredPhase == phase) && 
// 重点判断
!condition.matches(this.context, metadata)) {
				return true;
			}
		}

		return false;
	}

以@ConditionalOnBean底层工作原理为示例

        OnBeanCondition类继承了FilteringSpringBootCondition,FilteringSpringBootCondition类又继承SpringBootCondition,而SpringBootCondition实现了Condition接口,matches()方法也是在
SpringBootCondition这个类中实现的:

public abstract class SpringBootCondition implements Condition {

	private final Log logger = LogFactory.getLog(getClass());

	@Override
	public final boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
// 获取当前解析的类名或方法名
		String classOrMethodName = getClassOrMethodName(metadata);
		try {
// 进行具体的条件匹配,ConditionOutcome表示匹配结果
			ConditionOutcome outcome = getMatchOutcome(context, metadata);
//日志记录匹配结果
			logOutcome(classOrMethodName, outcome);
			recordEvaluation(context, classOrMethodName, outcome);
// 返回匹配结果 true/false
			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);
		}
	}
//....
	public abstract ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata);

}

        具体的条件匹配逻辑在getMatchOutcome方法中实现的,而SpringBootCondition类中的
getMatchOutcome方法是一个抽象方法,具体的实现逻辑就在子类OnBeanCondition:

@Override
	public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
		ConditionMessage matchMessage = ConditionMessage.empty();
		MergedAnnotations annotations = metadata.getAnnotations();
        // 如果存在ConditionalOnBean注解
		if (annotations.isPresent(ConditionalOnBean.class)) {
			Spec<ConditionalOnBean> spec = new Spec<>(context, metadata, annotations, ConditionalOnBean.class);
			MatchResult matchResult = getMatchingBeans(context, spec);
            // 如果某个Bean不存在
			if (!matchResult.isAllMatched()) {
				String reason = createOnBeanNoMatchReason(matchResult);
				return ConditionOutcome.noMatch(spec.message().because(reason));
			}
            // 所有Bean都存在
			matchMessage = spec.message(matchMessage)
				.found("bean", "beans")
				.items(Style.QUOTE, matchResult.getNamesOfAllMatches());
		}
    // 如果存在ConditionalOnSingleCandidate注解
		if (metadata.isAnnotated(ConditionalOnSingleCandidate.class.getName())) {
			Spec<ConditionalOnSingleCandidate> spec = new SingleCandidateSpec(context, metadata, annotations);
			MatchResult matchResult = getMatchingBeans(context, spec);
			if (!matchResult.isAllMatched()) {
				return ConditionOutcome.noMatch(spec.message().didNotFind("any beans").atAll());
			}
			Set<String> allBeans = matchResult.getNamesOfAllMatches();
			if (allBeans.size() == 1) {
				matchMessage = spec.message(matchMessage).found("a single bean").items(Style.QUOTE, allBeans);
			}
			else {
				List<String> primaryBeans = getPrimaryBeans(context.getBeanFactory(), allBeans,
						spec.getStrategy() == SearchStrategy.ALL);
				if (primaryBeans.isEmpty()) {
					return ConditionOutcome
						.noMatch(spec.message().didNotFind("a primary bean from beans").items(Style.QUOTE, allBeans));
				}
				if (primaryBeans.size() > 1) {
					return ConditionOutcome
						.noMatch(spec.message().found("multiple primary beans").items(Style.QUOTE, primaryBeans));
				}
				matchMessage = spec.message(matchMessage)
					.found("a single primary bean '" + primaryBeans.get(0) + "' from beans")
					.items(Style.QUOTE, allBeans);
			}
		}
        // 存在ConditionalOnMissingBean注解
		if (metadata.isAnnotated(ConditionalOnMissingBean.class.getName())) {
			Spec<ConditionalOnMissingBean> spec = new Spec<>(context, metadata, annotations,
					ConditionalOnMissingBean.class);
			MatchResult matchResult = getMatchingBeans(context, spec);
			if (matchResult.isAnyMatched()) {
				String reason = createOnMissingBeanNoMatchReason(matchResult);
				return ConditionOutcome.noMatch(spec.message().because(reason));
			}
			matchMessage = spec.message(matchMessage).didNotFind("any beans").atAll();
		}
		return ConditionOutcome.match(matchMessage);
	}


protected final MatchResult getMatchingBeans(ConditionContext context, Spec<?> spec) {
		ClassLoader classLoader = context.getClassLoader();
		ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
		boolean considerHierarchy = spec.getStrategy() != SearchStrategy.CURRENT;
		Set<Class<?>> parameterizedContainers = spec.getParameterizedContainers();
		if (spec.getStrategy() == SearchStrategy.ANCESTORS) {
			BeanFactory parent = beanFactory.getParentBeanFactory();
			Assert.isInstanceOf(ConfigurableListableBeanFactory.class, parent,
					"Unable to use SearchStrategy.ANCESTORS");
			beanFactory = (ConfigurableListableBeanFactory) parent;
		}
		MatchResult result = new MatchResult();
		Set<String> beansIgnoredByType = getNamesOfBeansIgnoredByType(classLoader, beanFactory, considerHierarchy,
				spec.getIgnoredTypes(), parameterizedContainers);
		for (String type : spec.getTypes()) {
			Collection<String> typeMatches = getBeanNamesForType(classLoader, considerHierarchy, beanFactory, type,
					parameterizedContainers);
			typeMatches
				.removeIf((match) -> beansIgnoredByType.contains(match) || ScopedProxyUtils.isScopedTarget(match));
			if (typeMatches.isEmpty()) {
				result.recordUnmatchedType(type);
			}
			else {
				result.recordMatchedType(type, typeMatches);
			}
		}
		for (String annotation : spec.getAnnotations()) {
			Set<String> annotationMatches = getBeanNamesForAnnotation(classLoader, beanFactory, annotation,
					considerHierarchy);
			annotationMatches.removeAll(beansIgnoredByType);
			if (annotationMatches.isEmpty()) {
				result.recordUnmatchedAnnotation(annotation);
			}
			else {
				result.recordMatchedAnnotation(annotation, annotationMatches);
			}
		}
		for (String beanName : spec.getNames()) {
			if (!beansIgnoredByType.contains(beanName) && containsBean(beanFactory, beanName, considerHierarchy)) {
				result.recordMatchedName(beanName);
			}
			else {
				result.recordUnmatchedName(beanName);
			}
		}
		return result;
	}

        getMatchingBeans方法中会利用BeanFactory去获取指定类型的Bean,如果没有指定类型的Bean,则会将该类型记录在MatchResult对象的unmatchedTypes集合中,如果有该类型的Bean,则会把该Bean的beanName记录在MatchResult对象的matchedNames集合中,所以MatchResult对象中记录了哪些类没有对应的Bean,哪些类有对应的Bean。

        大概流程如下:

        Spring在解析某个配置类,或某个Bean定义时如果发现它们上面用到了条件注解,就会取出所有的条件注解,并生成对应的条件对象,比如OnBeanCondition对象;
        依次调用条件对象的matches方法,进行条件匹配,看是否符合条件
        条件匹配逻辑中,会拿到@ConditionalOnBean等条件注解的信息,判断哪些Bean存在
        然后利用BeanFactory来进行判断
        最后只有所有条件注解的条件都匹配,那么当前Bean定义才算符合条件生效

自定义Spring Boot Starter

        SpringBoot 最强大的功能就是把我们常用的业务场景抽取成了一个个starter(场景启动器),我们通过引入SpringBoot 提供的这些场景启动器,再进行少量的配置就能使用相应的功能。但是,SpringBoot 不能囊括我们所有的业务使用场景,往往我们需要自定义starter,来简化我们对springboot的使用。

        下面是自定义starter的示例代码地址:

lp-springboot-start: 自定义Spring Boot Start

  • 30
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值