前言
Spring提供了@Conditional注解在自动扫描Bean时可以根据条件来判断是否注册BeanDefinition。
简单使用
看一下@Conditional注解的声明,Spring4.0版本才提供。
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Conditional {
/**
* 配置Condition接口实现类,可以有多个
*/
Class<? extends Condition>[] value();
}
import java.util.Date;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ClassPathBeanDefinitionScanner;
import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.ConfigurationClassPostProcessor;
import org.springframework.core.type.AnnotatedTypeMetadata;
import org.springframework.util.ClassUtils;
public class TestConditional2 {
public static void main(String[] args) {
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(
beanFactory, true);
String packageName = ClassUtils.getPackageName(TestConditional2.class);
//扫描
scanner.scan(packageName);
new ConfigurationClassPostProcessor().postProcessBeanDefinitionRegistry(beanFactory);
System.out.println(beanFactory.containsBeanDefinition("beanConfig"));
System.out.println(beanFactory.containsBeanDefinition("myDate"));
}
@Configuration("beanConfig")
@Conditional(BeanConfigCondition.class)
public static class BeanConfig {
@Bean("myDate")
@Conditional(BeanCondition.class)
public Date date() {
return new Date();
}
}
public static class BeanConfigCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
//ConditionContext容器上下文,当前Class上下文
return true;
}
}
public static class BeanCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
//ConditionContext容器上下文,当前Class上下文
return false;
}
}
}
使用ClassPathBeanDefinitionScanner作为扫描器用来扫描@Component注解标记的Class,创建BeanDefinition并注册到BeanFactory中。
ConfigurationClassPostProcessor用来处理@Configuration注解,解析出其中包含@Bean的方法,创建BeanDefinition并注册到BeanFactory中。
在注册时发现Class或者Method包含Conditional注解,会创建配置的Condition实现类对象,根据matches()方法来决定是否注册当前BeanDefinition。
关于ConfigurationCondition接口的使用
Spring在Condition接口之外还提供了ConfigurationCondition,看一下接口声明,它是Condition的子接口
public interface ConfigurationCondition extends Condition {
/**
* 条件被计算的阶段
*/
ConfigurationPhase getConfigurationPhase();
/**
* 条件被计算的阶段
*/
enum ConfigurationPhase {
/**
* 解析@Configuration类之前,如果条件不满足,@Configuration类不会被加载
*/
PARSE_CONFIGURATION,
/**
* 解析所有@Configuration类之后,如果此时条件还不满足,移除已经加载的@Configuration类
*/
REGISTER_BEAN
}
}
看注释可能不是太理解,下面写一个例子
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.context.annotation.ClassPathBeanDefinitionScanner;
import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.ConfigurationClassPostProcessor;
import org.springframework.context.annotation.ConfigurationCondition;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.core.type.AnnotatedTypeMetadata;
import org.springframework.util.ClassUtils;
public class TestConfigurationCondition {
public static void main(String[] args) {
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(
beanFactory, true);
String packageName = ClassUtils.getPackageName(TestConfigurationCondition.class);
//扫描
scanner.scan(packageName);
new ConfigurationClassPostProcessor().postProcessBeanDefinitionRegistry(beanFactory);
System.out.println(beanFactory.containsBeanDefinition("beanB"));
System.out.println(beanFactory.containsBeanDefinition("beanA"));
}
@Configuration("beanA")
@Order(Ordered.HIGHEST_PRECEDENCE + 1)
public static class BeanA {
}
@Configuration("beanB")
@Conditional(IfBeanAExistsConfigurationCondition.class)
@Order(Ordered.HIGHEST_PRECEDENCE)
public static class BeanB {
}
public static class IfBeanAExistsCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
return context.getRegistry().containsBeanDefinition("beanA");
}
}
public static class IfBeanAExistsConfigurationCondition implements ConfigurationCondition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
return context.getRegistry().containsBeanDefinition("beanA");
}
@Override
public ConfigurationPhase getConfigurationPhase() {
return ConfigurationPhase.REGISTER_BEAN;
}
}
}
使用@Order注解配置优先级(值越小优先级越高),保证BeanB在BeanA之前加载,BeanB的加载条件是BeanA在容器中存在。
如果使用IfBeanAExistsCondition,加载BeanB时BeanA还没有加载,所以matches()方法返回false,BeanB不会注册到容器中。
如果使用IfBeanAExistsConfigurationCondition,加载BeanB时先不管条件,等所有Configuration类都加载完了,此时判断条件,因为BeanA已经加载到容器中了,所以BeanB也可以加载。
源码分析
我们在Spring源码分析之@Component注解自动扫描的处理的基础上继续分析Spring对@Conditional注解的处理。
进入ClassPathScanningCandidateComponentProvider类。
protected boolean isCandidateComponent(MetadataReader metadataReader) throws IOException {
//符合此条件就排除,类似黑名单
for (TypeFilter tf : this.excludeFilters) {
if (tf.match(metadataReader, getMetadataReaderFactory())) {
return false;
}
}
//符合此条件就包含,类似白名单
for (TypeFilter tf : this.includeFilters) {
if (tf.match(metadataReader, getMetadataReaderFactory())) {
//处理@Conditional注解
return isConditionMatch(metadataReader);
}
}
return false;
}
满足包含条件之后,还要检查@Conditional注解,继续isConditionMatch()方法
private boolean isConditionMatch(MetadataReader metadataReader) {
if (this.conditionEvaluator == null) {
this.conditionEvaluator =
new ConditionEvaluator(getRegistry(), this.environment, this.resourcePatternResolver);
}
return !this.conditionEvaluator.shouldSkip(metadataReader.getAnnotationMetadata());
}
创建一个ConditionEvaluator,可以看做一个条件解析器,使用它来判断是否能够注册,shouldSkip()方法返回true,表示应该跳过(不能注册)
public boolean shouldSkip(AnnotatedTypeMetadata metadata) {
return shouldSkip(metadata, null);
}
核心方法
public boolean shouldSkip(@Nullable AnnotatedTypeMetadata metadata, @Nullable ConfigurationPhase phase) {
//如果没有被@Conditional注解标记,直接返回false,表示不跳过
if (metadata == null || !metadata.isAnnotated(Conditional.class.getName())) {
return false;
}
if (phase == null) {
//根据metadata是否是配置类,来判断当前是什么阶段(phase)
if (metadata instanceof AnnotationMetadata &&
ConfigurationClassUtils.isConfigurationCandidate((AnnotationMetadata) metadata)) {
//Component注解,ComponentScan注解,Import注解,ImportResource注解,如果包含4个注解中的一个,就看做配置类
return shouldSkip(metadata, ConfigurationPhase.PARSE_CONFIGURATION);
}
return shouldSkip(metadata, ConfigurationPhase.REGISTER_BEAN);
}
List<Condition> conditions = new ArrayList<>();
//解析出@Conditional注解配置的Class,就是Condition接口的实现类,可以配置多个
for (String[] conditionClasses : getConditionClasses(metadata)) {
for (String conditionClass : conditionClasses) {
//根据Class创建Condition对象
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();
}
//如果condition不是ConfigurationCondition接口类型,直接调用matches()方法判断
//如果是ConfigurationCondition接口类型,接口返回的阶段和当前阶段不一致,直接false,如果一致,再调用matches()方法判断
if ((requiredPhase == null || requiredPhase == phase) && !condition.matches(this.context, metadata)) {
return true;
}
}
return false;
}
@Conditional注解可以配置多个Class,也是可以配置多个@Conditional注解的,像下面这样,@ConditionalOnClass是SpringBoot提供的一个注解,包含@Conditional注解
@Configuration("beanA")
@ConditionalOnClass({BeanA.class, BeanB.class})
@Order(Ordered.HIGHEST_PRECEDENCE + 1)
public static class BeanA {
}
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(OnClassCondition.class)
public @interface ConditionalOnClass {
}
所以上面的getConditionClasses()方法返回的是一个二维数组结构。
分析总结
主要是引入了ConfigurationPhase(阶段)的概念,如果调用shouldSkip()方法时没有传入ConfigurationPhase参数,就会根据当前Class是否为配置类来确定ConfigurationPhase。
如果condition类型不是ConfigurationCondition类型,那么ConfigurationPhase这个参数也就没有作用。
如果condition类型是ConfigurationCondition类型,如果接口返回的阶段和当前方法的参数(阶段)不一致,直接false(表示不跳过),如果一致,再调用matches()方法判断。