Conditional分析
作用
表示这个component是否有资格,能不能添加到BeanFactory中。
在使用Conditional注解的时候,被Conditional标注的类,会调用到Conditional里面value里面的Condition的实现类,如果这些都为true,表示匹配,当前的bean就会添加到BeanFactory中,如果有一个为false,就不会添加进去。
因为它的这个特殊的功能,在使用他的时候就可以在bean注册到beanfactory之前,做检查。比如像ConditionalOnClass
......
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Conditional {
Class<? extends Condition>[] value();
}
复制代码
使用方式
-
直接或者间接使用到的@Component。比如像@Configuration。(Configuration注解元注解就一个@Component,但是如果在一个类上面使用了它和Conditional注解,Conditional注解还是会起作用的)
-
作为一个元注解,可以自定义注解来使用。比如像Springboot里面的ConditionalOnClass注解。
@Target({ ElementType.TYPE, ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) @Documented @Conditional(OnClassCondition.class) //作为元注解 public @interface ConditionalOnClass { Class<?>[] value() default {}; String[] name() default {}; } 复制代码
-
还可以用在@Bean标注的方法上面。
下面例子中ConditionalOnMissingBean和上面说的ConditionalOnClass差不多,Conditional注解都是他们的元注解,这也能说明问题。
@Configuration(proxyBeanMethods = false) @ConditionalOnClass(MongoTemplate.class) @ConditionalOnBean(MongoTemplate.class) @ConditionalOnEnabledHealthIndicator("mongo") @AutoConfigureAfter({ MongoAutoConfiguration.class, MongoDataAutoConfiguration.class, MongoReactiveHealthContributorAutoConfiguration.class }) public class MongoHealthContributorAutoConfiguration extends CompositeHealthContributorConfiguration<MongoHealthIndicator, MongoTemplate> { @Bean @ConditionalOnMissingBean(name = { "mongoHealthIndicator", "mongoHealthContributor" }) public HealthContributor mongoHealthContributor(Map<String, MongoTemplate> mongoTemplates) { return createContributor(mongoTemplates); } } 复制代码
特殊情况:
- 如果一个@Configuration类上面标注了@Conditional注解,这个Bean里面所有的被@Bean标注的方法,@Import注解,@ComponentScan,都会关联到这个@Conditional上面。也就是说,都是由这个@Conditional来决定的。
Condition接口分析
Condition接口分析
这个接口是用来判断的,在@Conditional的value存放的是实现了Condition接口的类,这个类会被实例化,并且调用它的matches方法。
在matches方法里里面,需要传递两个参数。
-
ConditionContext context:当前Condition的上下文对象。
因为在Condition接口里面会由一些操作,一般操作基本都是直接操作BeanFactory或者Environment,当然,肯定还有ResourceLoader。所以,在ConditionContext的接口里面就定义了这些方法,在实现类里面,也是保留了这些的对象。
public interface ConditionContext { BeanDefinitionRegistry getRegistry(); @Nullable ConfigurableListableBeanFactory getBeanFactory(); Environment getEnvironment(); ResourceLoader getResourceLoader(); @Nullable ClassLoader getClassLoader(); } 复制代码
-
AnnotatedTypeMetadata metadata
类上面注解的的信息,这个信息被Spring包装了,包装成这个对象了。
返回值为true表示匹配,当前的bean可以添加到BeanFactory中,返回为false表示,当前的bean不需要添加到Beanfactory中。
@FunctionalInterface
public interface Condition {
boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);
}
复制代码
类图
ConfigurationCondition接口分析
继承于Condition接口,比起Condition,他有更详细的控制@Configuration。
在不同的配置阶段,对Condition做处理,简而言之,它讲配置解析,分为了两个阶段,在不同的阶段可以做不同的事情,并且在不同的阶段允许特定的存在。
分为了下面两个阶段
-
ConfigurationPhase.PARSE_CONFIGURATION
在解析
@Configuration
标注的类的时候Condition
要计算。如果不匹配,就不会将这个@Configuration
添加进来。 -
ConfigurationPhase.REGISTER_BEAN
在添加一个规则的bean,才会去做匹配。
public interface ConfigurationCondition extends Condition {
ConfigurationPhase getConfigurationPhase();
enum ConfigurationPhase {
PARSE_CONFIGURATION,
REGISTER_BEAN
}
}
复制代码
PARSE_CONFIGURATION和REGISTER_BEAN阶段在Spring中具体是哪些地方。
为什么要分为两个阶段
按照常理来说,解析就是加载,因为解析完了,下一步直接就加载了,没什么问题。但是对于Spring的Configuration来说,不是这个样子的。
对于配置类来说,配置类里面是是可以导入普通的类,也是可以导入一个配置类,这就分为两个阶段了。在读取之后,需要加载。
- 读取@import,@Bean,@ComponentScan,等这种可以向容器中添加bean的注解。
- 加载,这里的加载是要解析那些普通的注解,比如,@role。@Scope,还有bean的那些init和destroy方法,等等。这些注解不用给在给Spring中添加具体的bean信息。
基于上面的原因,所以在分为了两个阶段。分成两个节点,就可以更加细粒的控制,比如对于一个配置类来说,getConfigurationPhase
返回值是REGISTER_BEAN。那么在解析Configuration的时候,就会将这个bean添加进去。并不会调用它的Mathch方法,也就是在这个阶段不用管Match方法的结果。
就可以用这样的一个例子,比如,作为一个配置类来说,它需要在一个类存在的前提下才能用,但是这个类又是别的配置类导入的,那么,这种场景就很适合。
PARSE_CONFIGURATION阶段
在开始解析Configuration标注的类。这个阶段主要是解析@import,@Bean,@ComponentScan。
主要是在ConfigurationClassParser#parse方法里面,下面主要分析一些关键的代码。具体的代码太长了,这里就不写了。
下面只是列举了一个简单的代码,因为,解析的逻辑是一样的,所以,和下面REGISTER_BEAN阶段的分析写在一起了。
ConfigurationClassParser#processConfigurationClass
// 调用了conditionEvaluator来计算当前的类是否是一个Condition,并且传递的阶段是PARSE_CONFIGURATION。
if (this.conditionEvaluator来.shouldSkip(configClass.getMetadata(), ConfigurationPhase.PARSE_CONFIGURATION)) {
return;
}
复制代码
REGISTER_BEAN阶段
在上面解析完之后,这个阶段会解析@role,这种不会向Spring中添加bean的注解,构建BeanDefinition,添加到BeanFactory中,这个时候才会真正的添加进来。
主要是在ConfigurationClassBeanDefinitionReader#loadBeanDefinitions方法里面,下面主要分析一些关键的代码。具体的代码太长了,这里就不写了
ConfigurationClassParser#processConfigurationClass
ConfigurationClassBeanDefinitionReader#loadBeanDefinitionsForConfigurationClass
private void loadBeanDefinitionsForConfigurationClass(
ConfigurationClass configClass, TrackedConditionEvaluator trackedConditionEvaluator) {
// 如果一个对象是被导入的,这里的返回值是通过导入的的类决定的
// 这里也调用conditional判断
if (trackedConditionEvaluator.shouldSkip(configClass)) {
String beanName = configClass.getBeanName();
if (StringUtils.hasLength(beanName) && this.registry.containsBeanDefinition(beanName)) {
this.registry.removeBeanDefinition(beanName);
}
this.importRegistry.removeImportingClass(configClass.getMetadata().getClassName());
return;
}
if (configClass.isImported()) {
registerBeanDefinitionForImportedConfigurationClass(configClass);
}
for (BeanMethod beanMethod : configClass.getBeanMethods()) {
loadBeanDefinitionsForBeanMethod(beanMethod);
}
loadBeanDefinitionsFromImportedResources(configClass.getImportedResources());
loadBeanDefinitionsFromRegistrars(configClass.getImportBeanDefinitionRegistrars());
}
复制代码
ConditionEvaluator分析
要注意,它不是public的,它是给内部用的,来处理Conditional
注解。在使用它的时候需要一个context对象,ConditionContextImpl是它的静态内部类。这个context主要是为了封装常用的对象,传递给Condition的matches
方法。
其中最主要的方法是shouldSkip
方法,方法需要传递两个参数:
- AnnotatedTypeMetadata metadata:当前类的注解元信息。
- ConfigurationPhase phase:之前说的配置的阶段。
下面重点看看shouldSkip
方法
方法的返回值:
true:表示当前的bean不需要添加进来。
false:表示当前的bean需要添加。
主要有以下几个步骤:
-
在这个方法里面会判断当前的类是否有Conditional注解。
-
如果ConfigurationPhase没有传递,会有一个阶段增强的过程,null->PARSE_CONFIGURATION->REGISTER_BEAN;
-
会先实例化Conditional注解里面的values的值(这些都是实现了Condition接口)。
-
排序。
-
循环调用。重点要看这个方法
如果这个Condition是ConfigurationCondition,就会获取ConfigurationPhase。注意下面有一个比对ConfigurationPhase是否一致(从方法的入参和ConfigurationCondition返回值做比较,判断是否要起作用的阶段。)。
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; } } 复制代码
class ConditionEvaluator {
private final ConditionContextImpl context;
public boolean shouldSkip(AnnotatedTypeMetadata metadata) {
return shouldSkip(metadata, null);
}
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;
}
private static class ConditionContextImpl implements ConditionContext {
@Nullable
private final BeanDefinitionRegistry registry;
@Nullable
private final ConfigurableListableBeanFactory beanFactory;
private final Environment environment;
private final ResourceLoader resourceLoader;
@Nullable
private final ClassLoader classLoader;
}
}
复制代码
TrackedConditionEvaluator分析
TrackedConditionEvaluator是ConfigurationClassBeanDefinitionReader的内部类,主要是用了装饰者设计模式
.在正常的conditionEvaluator调用之前,增加了一些操作.
增加了对Import的处理,这里实现了Configuration注解上面的@Conditional上面的级联操作,也就是说,一个配置类上的Conditional注解会关联到由这个配置类导入的一些bean。
这是怎么做的?
- 首先这是一个递归操作。这个方法的本质是判断当前的bean是否不要添加到beanfactory中。(记住这是本质)。
- 假如A是由B导入的。现在是A调用到了这个方法。
- 一上来先看缓存中没有,然后再看A是否是导入的,在拿到A是被谁导入的。
- 开始遍历集合,这里就是B。
- B就开始走上面的方法。又来了一次。但是这里发现B不是被导入的。这个时候B就继续往下走了
- B就走到了conditionEvaluator.shouldSkip()里面了。假设现在B返回了true。表示B不需要添加到BeanFactory中。同时B也会在缓存中
- 方法就回到了2了。继续走,allSkipped还是它的默认值true了,skip为true。就不会走到下面的matches的判断里面,直接跳过。放到缓存里面。直接返回 skip。
- 通过上面的步骤分析,因为B的不满足,导致A不会添加进来。
private class TrackedConditionEvaluator {
// 这就是一个缓存,key是类,value表示当前的这个bea是否要跳过(也就是不添加到beanFactory中)
private final Map<ConfigurationClass, Boolean> skipped = new HashMap<>();
public boolean shouldSkip(ConfigurationClass configClass) {
// 先从缓存中捞。
Boolean skip = this.skipped.get(configClass);
if (skip == null) {
// 当前的bean是是否是导入的,ConfigurationClass的importedBy属性,存放的是导入当前bean的类。
if (configClass.isImported()) {
boolean allSkipped = true;
for (ConfigurationClass importedBy : configClass.getImportedBy()) {
// 开始判断,importedBy。
if (!shouldSkip(importedBy)) {
allSkipped = false;
break;
}
}
if (allSkipped) {
// allSkipped只有在shouldSkip为false的情况下,allSkipped才会变为false
skip = true;
}
}
if (skip == null) {
// 这就会调用上面的判断
skip = conditionEvaluator.shouldSkip(configClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN);
}
// 放缓存
this.skipped.put(configClass, skip);
}
return skip;
}
}
复制代码
使用
通过上面的分析,举一个简单的例子。模拟一个@conditionOnBean的功能。只不过,这里是beanName
-
自定义注解
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Conditional(SimpleCondition.class) @Order(Ordered.LOWEST_PRECEDENCE) public @interface LcConditionalOnBeanName { String beanName(); } 复制代码
-
两个简单的实体类
public class A { private String name; private Integer age; public A(String name, Integer age) { this.name = name; this.age = age; } @Override public String toString() { return "A{" + "name='" + name + '\'' + ", age=" + age + '}'; } } public class B { private String name; private Integer age; public B(String name, Integer age) { this.name = name; this.age = age; } @Override public String toString() { return "A{" + "name='" + name + '\'' + ", age=" + age + '}'; } } 复制代码
-
两个相互作用的配置类
@Configuration(proxyBeanMethods = false) @LcConditionalOnBeanName(beanName = "b") public class SimpleConfig { @Bean public A a(){ return new A("a",12); } } @Configuration(proxyBeanMethods = false) @Order(Ordered.HIGHEST_PRECEDENCE) public class SimpleConfig2 { @Bean public B b (){ return new B("a",12); } } 复制代码
-
ConfigurationCondition的实现类
public class SimpleCondition implements ConfigurationCondition { @Override public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { Map<String, Object> annotationAttributes = metadata.getAnnotationAttributes(LcConditionalOnBeanName.class.getName()); if (Objects.isNull(annotationAttributes)) { return false; } String beanName = (String) annotationAttributes.get("beanName"); Assert.notNull(beanName, "beanName not null"); boolean contains = Arrays.asList(context.getRegistry().getBeanDefinitionNames()).contains(beanName); return contains; } @Override public ConfigurationPhase getConfigurationPhase() { return ConfigurationPhase.REGISTER_BEAN; } } 复制代码
-
主启动类
public static void test() { try { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(SimpleConfig.class, SimpleConfig2.class); A bean = context.getBean(A.class); printLog(bean); context.close(); } catch (Throwable e) { e.printStackTrace(); } } public static void printLog(Object s) { SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); System.out.println(Thread.currentThread().getName() + "-" + simpleDateFormat.format(new Date()) + ":" + s); } 复制代码
-
结果
普普通通一个获取bean,答应bean信息
说明
在上面的例子中,有两个配置类,
SimpleConfig上面有我自定义的注解,表示,当前的找个bean要是想起作用,就必须需要一个bean名字为b的bean存在,才能起作用。
SimpleConfig2里面无脑往里面注入一个名字问b,类型为B的bean。
SimpleCondition说明
获取这个bean上面的LcConditionalOnBeanName注解,获取值,从beanFactory中获取bean。
LcConditionalOnBeanName说明
@Conditional和@Order注解是它的元注解信息。
问题?
-
为什么需要@order注解
这是一个投巧的方法,因为通过order注解来控制Configuration类的加载顺序,我得在保证在解析加载SimpleConfig之前,SimpleConfig里面的B会被加载到BeanFactory中。
-
Ordered接口可以不可以?
不可以。
具体的代码逻辑在 ConfigurationClassUtils#checkConfigurationClassCandidate 里面。这里面会有下面的代码
会获取Bean上面标注的Order注解,并且获取value值。会将这个值放在属性里面(ORDER_ATTRIBUTE = Conventions.getQualifiedAttributeName(ConfigurationClassPostProcessor.class, "order");)
public static Integer getOrder(AnnotationMetadata metadata) { Map<String, Object> orderAttributes = metadata.getAnnotationAttributes(Order.class.getName()); return (orderAttributes != null ? ((Integer) orderAttributes.get(AnnotationUtils.VALUE)) : null); } 复制代码
在解析Configuration的时候,会先排序,如果没有,默认是最小。所以,这也是为什么在两个配置类上标注的原因。
configCandidates.sort((bd1, bd2) -> { int i1 = ConfigurationClassUtils.getOrder(bd1.getBeanDefinition()); int i2 = ConfigurationClassUtils.getOrder(bd2.getBeanDefinition()); return Integer.compare(i1, i2); }); 复制代码