Conditional模式下注册Bean的顺序问题

我们知道在SpringBoot中提供了很多的Conditionalxxx的注解,

使用方法参考博客:Spring Boot的自动配置是啥?Auto-configuration?

首先想一下,为什么我们需要这种的注解存在呢?因为选择性问题。所以程序不仅仅是顺序执行,还有很多的if-else,其实理解Conditionalxxx就是if-else,只是针对的场景就是适合某种条件才注册某个bean。尤其做公共组件,必须满足各种使用者不同的需求。

现在有如下这样一个类

package com.example.condition;

public class OrderService {

    private String type;

    public String getType() {
        return type;
    }

    public void setType(String type) {
        this.type = type;
    }
}

尝试按照以下方式去注册一个bean,首先注册一个,然后按照条件进行注册,理论上应该可以的吧

package com.example.condition;

import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class RootConfig {

    @Bean
    public OrderService normalService() {
        OrderService orderService = new OrderService();
        orderService.setType("normal");
        return orderService;
    }

    @ConditionalOnMissingBean
    @Bean
    public OrderService conditionService() {
        OrderService orderService = new OrderService();
        orderService.setType("condition");
        return orderService;
    }
}

在这里插入图片描述
测试结果确实OK。
现在我们稍微修改一下上面的配置类并进行测试
在这里插入图片描述
异常信息:Exception in thread "main" org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'com.example.condition.OrderService' available: expected single matching bean but found 2: conditionService,normalService.

说明此时这两个Bean都注册成功了!@ConditionalOnMissingBean失效了?

其实这里引出了自动配置的顺序问题。那么怎么样才能保证这个conditionService一定有效呢?

有可能你很快想到了进行排序,比如:

@Configuration
// @AutoConfigureAfter(RootConfig.class)
@AutoConfigureOrder(Integer.MAX_VALUE)
public class ConditionConfig {
    @ConditionalOnMissingBean
    @Bean
    public OrderService conditionService() {
        OrderService orderService = new OrderService();
        orderService.setType("condition");
        return orderService;
    }
}

按照猜想,将AutoConfigureOrder的优先级设置为最低,那么里面的@Bean应该是后注册的吧?但是结果呢?@ConditionalOnMissingBean仍然没有达到想要的效果。

其实上面这里误用了@AutoConfigureOrder注解,这个注解只有通过自动配置的方式(也就是在spring.factories中定义,通过EnableAutoConfiguration引入的才有效)。

可以,那么我们把这两个配置类移到另一个包(当前Spring Boot无法扫描到的路径),并在META-INF/spring.factories文件中配置上。如下所示:
在这里插入图片描述
这样却是是可以的,但是有一点很不好,就是这两个类需要移到其他路径之下,总感觉很奇怪。那有没有更好的办法呢?当然有。

首先将这个带条件的类放到另一个类里面

package com.example.condition;

import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;

public class ConditionBeanHoder {

    @ConditionalOnMissingBean
    @Bean
    public OrderService conditionService() {
        OrderService orderService = new OrderService();
        orderService.setType("condition");
        return orderService;
    }
}

然后创建一个DeferredImportSelector类型的实现类

package com.example.condition;

import org.springframework.context.annotation.DeferredImportSelector;
import org.springframework.core.type.AnnotationMetadata;


public class ConditionDeferredImportSelector implements DeferredImportSelector {
    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        return new String[]{ConditionBeanHoder.class.getName()};
    }
}

最后配置类如下所示(通过Import来导入这个DeferredImportSelector类即可):

package com.example.condition;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;

@Configuration
@Import(ConditionDeferredImportSelector.class)
public class RootConfig {

    @Bean
    public OrderService normalService() {
        OrderService orderService = new OrderService();
        orderService.setType("normal");
        return orderService;
    }
}

最后主类如下所示

package com.example.condition;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;

@SpringBootApplication
public class SpringStartMain {

    public static void main(String[] args) {
        ConfigurableApplicationContext applicationContext = SpringApplication.run(SpringStartMain.class);
        OrderService orderService = applicationContext.getBean(OrderService.class);
        System.out.println(orderService.getType());
        applicationContext.close();
    }
}

结构如下
在这里插入图片描述
此时如果有注入OrderService实例,启动程序:
在这里插入图片描述
执行结果为normal,说明当前起作用的是在RootConfig中的类。
如果注释掉这个类呢?
在这里插入图片描述
可见达到了不移动类路径也不需要在spring.factories中配置就实现了目标。那么这个DeferredImportSelector为什么有这样的魔力呢?

我们首先要弄懂是Conditional如何起作用的.
对于这类注解的定义,画风一般都是如下的格式

@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(OnClassCondition.class)
public @interface ConditionalOnMissingClass {

	/**
	 * The names of the classes that must not be present.
	 * @return the names of the classes that must not be present
	 */
	String[] value() default {};

}

会包含@Conditional(OnClassCondition.class)这样的元注解。因为ConditionalOnxxx是由Spring Boot提供的,而注解Conditional在Spring 4.0的时代就有了。首先去看一下org.springframework.context.annotation.Conditional的注释。
在这里插入图片描述
以上说明包含如下几个意思

  1. 该注释主要是用于干涉bean的注册的(暗示一个组件只有在符合某个条件的情况下才能注册),可以是各种条件,但是有一点要注意:这个条件必须是在bean注册之前就可以判断的。等注册到了bean容器之后,这个条件其实也就没啥用了。(时机很重要)
  2. 作用在包含@Component@Configuration注解的类上面或者包含@Bean注解的方法上
  3. 如果使用在包含@Configuration注解的类上面,那么这个条件判断作用于这个类里面所有的@Bean方法、@Import注解、ComponentScan注解。
  4. 最后一点特别要注意的就是,这个注解不支持继承性。(不包含元注解@Inherited),不会从父类中或被继承方法中继承这个注解,限定了作用范围的单一性。而且,建议所有其他添加了@Conditional的元注解的所有的复合注解(composed annotation)都不要声明为@Inherited

这个注解必须定义一个Condition接口的实现类。

为了这个注解的功能能够实现,必须有另一个类的存在,那就是ConditionEvaluator.毕竟@Conditional只是一个注解,如果作用还是要有一套算法来支持,而ConditionEvaluator就是这套算法。除了这两个角色,还不够,那就是作用的对象。在Spring中就是ConditionContext,这样三者就齐全了。在ConditionContext中,通过ConditionEvaluator来判断,@Conditional的结果。

创建ConditionEvaluator实例化了ConditionContext

/**
 * Create a new {@link ConditionEvaluator} instance.
 */
public ConditionEvaluator(@Nullable BeanDefinitionRegistry registry,
		@Nullable Environment environment, @Nullable ResourceLoader resourceLoader) {

	this.context = new ConditionContextImpl(registry, environment, resourceLoader);
}

在判断shouldSkip的时候

public boolean shouldSkip(@Nullable AnnotatedTypeMetadata metadata, @Nullable ConfigurationPhase phase) {
	// 没有@Conditional注解,不需要过滤
	if (metadata == null || !metadata.isAnnotated(Conditional.class.getName())) {
		return false;
	}
	
	// 默认phase	
	if (phase == null) {
		if (metadata instanceof AnnotationMetadata &&
				ConfigurationClassUtils.isConfigurationCandidate((AnnotationMetadata) metadata)) {
			return shouldSkip(metadata, ConfigurationPhase.PARSE_CONFIGURATION);
		}
		return shouldSkip(metadata, ConfigurationPhase.REGISTER_BEAN);
	}

	// 获取Condition条件
	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;
}

如果仅仅是针对@Conditional注解来说,此处的ConfigurationPhase其实是没用的。因为requiredPhase == null一直成立,然后所有的结果就看condition.matches(this.context, metadata)了。

比如使用上面的定义一个仅在类存在的情况下才起作用的配置类

package com.example.condition;

import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.type.AnnotatedTypeMetadata;

public class OnClassH2DriverCondition implements Condition {
    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        try {
            context.getClassLoader().loadClass("org.h2.Driver");
            return true;
        } catch (ClassNotFoundException e) {
            return false;
        }
    }
}
package com.example.condition;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;

@Configuration
@Conditional(OnClassH2DriverCondition.class)
public class RootConfig {

    @Bean
    public OrderService normalService() {
        OrderService orderService = new OrderService();
        orderService.setType("normal");
        return orderService;
    }

}

其实还是蛮简单的。Spring仅仅是针对以上的这种实现做了一些封装而已。
比如OnClassCondition的判断类是否存在的主要逻辑就是org.springframework.boot.autoconfigure.condition.FilteringSpringBootCondition.ClassNameFilter.

protected enum ClassNameFilter {

	PRESENT {

		@Override
		public boolean matches(String className, ClassLoader classLoader) {
			return isPresent(className, classLoader);
		}

	},

	MISSING {

		@Override
		public boolean matches(String className, ClassLoader classLoader) {
			return !isPresent(className, classLoader);
		}

	};

	public abstract boolean matches(String className, ClassLoader classLoader);

	public static boolean isPresent(String className, ClassLoader classLoader) {
		if (classLoader == null) {
			classLoader = ClassUtils.getDefaultClassLoader();
		}
		try {
			forName(className, classLoader);
			return true;
		}
		catch (Throwable ex) {
			return false;
		}
	}

	private static Class<?> forName(String className, ClassLoader classLoader) throws ClassNotFoundException {
		if (classLoader != null) {
			return classLoader.loadClass(className);
		}
		return Class.forName(className);
	}

}

最终就是通过classLoader.loadClass(className)来判断的。

对于OnBeanCondition,最终也是去查看当前容器内是否存在注册的的Bean定义

private BeanDefinition findBeanDefinition(ConfigurableListableBeanFactory beanFactory, String beanName,
		boolean considerHierarchy) {
	if (beanFactory.containsBeanDefinition(beanName)) {
		return beanFactory.getBeanDefinition(beanName);
	}
	if (considerHierarchy && beanFactory.getParentBeanFactory() instanceof ConfigurableListableBeanFactory) {
		return findBeanDefinition(((ConfigurableListableBeanFactory) beanFactory.getParentBeanFactory()), beanName,
				considerHierarchy);
	}
	return null;
}

判断条件本来不是难事,其实真正的难点在于顺序性,就像本文开头的案例那样。对于normalServiceconditionService这两个Bean,必须保证normalService先进行注册,conditionService此时才会因为容器中已经存在了相同类型的bean不再注册,而如果是normalService在后面进行注册,conditionService注册时容器中确实不存在相同类型的bean,当然就进行了注册,最终导致容器中存在了两个该类型的Bean了。

按照时间先后顺序,

  1. org.springframework.context.annotation.ConfigurationClassParser#processConfigurationClass,此时是在解析一个ConfigurationClass开始之时,此时会根据类上的OnConditional条件来判断这个类是不是需要注册,注意此处ConfigurationPhasePARSE_CONFIGURATION.
	protected void processConfigurationClass(ConfigurationClass configClass) throws IOException {
		if (this.conditionEvaluator.shouldSkip(configClass.getMetadata(), ConfigurationPhase.PARSE_CONFIGURATION)) {
			return;
		}
		// ....
}		
  1. org.springframework.context.annotation.ConfigurationClassParser#doProcessConfigurationClass,如果上面的判断是不需要过滤的,此时遇到了ComponentScans或者ComponentScan注解的时候,也会根据条件进行判断。此时ConfigurationPhaseREGISTER_BEAN。(与当前关系不大)
// Process any @ComponentScan annotations
		Set<AnnotationAttributes> componentScans = AnnotationConfigUtils.attributesForRepeatable(
				sourceClass.getMetadata(), ComponentScans.class, ComponentScan.class);
		if (!componentScans.isEmpty() &&
				!this.conditionEvaluator.shouldSkip(sourceClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN)) {
	// ...
}
  1. org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader#loadBeanDefinitionsForConfigurationClass,此时是在ConfigurationClassParser完成了classpath下所有类的初步判断并解析成ConfigurationClass之后,针对ConfigurationClass每个元素(比如importedBybeanMethodsimportBeanDefinitionRegistrars等)进行注册。
    在这里插入图片描述
    第一处:
    在这里插入图片描述
    第二处:
    在这里插入图片描述
    从以上可以看出,在解析和注册时ConfigurationPhase的值是不一样。而ConfigurationPhase的最关键的作用就在于ConditionEvaluator进行比较的时候首先排除掉不属于当前阶段的判断。
// 真实比较
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;
	}
}

OnBeanCondition就属于REGISTER_BEAN这个阶段的。所以在ConfigurationClassParser进行解析的过程当中其实是不会起作用的。只会在ConfigurationClassBeanDefinitionReader解析时才作用。
在这里插入图片描述
在这里插入图片描述

如上图所示:

Set<ConfigurationClass> configClasses = new LinkedHashSet<>(parser.getConfigurationClasses());

用于注册的configClasses是一个LinkedHashSet的,是有顺序的,然后再查看ConfigurationClassParser中的configurationClasses属性。

private final Map<ConfigurationClass, ConfigurationClass> configurationClasses = new LinkedHashMap<>();

这个属性是LinkedHashMap类型的,也是保证顺序的。
因为只要在解析过程中保证了configurationClasses的顺序性,就保证了注册的顺序性。

最后我们看一下org.springframework.context.annotation.ConfigurationClassParser#parse(java.util.Set<org.springframework.beans.factory.config.BeanDefinitionHolder>)方法:
在这里插入图片描述
很明显分为了两大块,最后一块针对deferredImportSelectorHandler做处理,按照顺序,这一块的处理结果一定排在```LinkedHashMap````的后面。

那么这个DeferredImportSelectorHandler如何作用的呢?
在这里插入图片描述
在进行@Import注解解析的时候,如果遇到的是DeferredImportSelector就首先保存在DeferredImportSelectorHandler对象中的deferredImportSelectors列表当中,然后在解析完了所有的ConfigurationClass之后再来处理deferredImportSelectors
在这里插入图片描述

总结:Spring中注册的工作主要是在ConfigurationClassPostProcessor来实现的,而注册之前首先必须进行各种类的解析,主要就是各种注解。而针对这些注解的解析操作,都是解析一个就放到一个LinkedHashMap当中,但是唯一的一个例外就是DeferredImportSelector,这种结果的实现类都是先存放到一个容器中,等到其他的类都处理完成之后,再进行处理的,最后再放到LinkedHashMap中(说到底,其实就是通过LinkedHashMap这种数据结构来保证注册的顺序性。)

想要对ConfigurationClassPostProcessor有深入的了解,可以参考本人的博客:Spring中最重要的一个后置处理器

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

lang20150928

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值