spring源码分析系列 (17) spring条件注册@Conditional 以及 springboot对条件注册的拓展

更多文章点击--spring源码分析系列

主要分析内容

一、注解@Conditional和接口Condition使用
二、注解@Conditional条件注册加载分析
三、spring boot对条件注册的拓展

(源码基于spring 5.1.3.RELEASE分析)


一、注解@Conditional和接口Condition使用

BeanCondition.java

public class BeanCondition implements Condition {
    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        BeanDefinitionRegistry registry = context.getRegistry() ;
        /** 表示要有name为testConditionBean的BeanDefinition */ 
        return registry.containsBeanDefinition("testConditionBean");
    }
}

BeanConfigurationCondition.java

public class BeanConfigurationCondition implements ConfigurationCondition {
    @Override
    public ConfigurationPhase getConfigurationPhase() {
        return ConfigurationPhase.PARSE_CONFIGURATION;
    }
    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        // 随机匹配是否加载
        boolean g = new Random().nextBoolean() ;
        System.out.println("BeanConfigurationCondition :" + g);
        return g;
    }
}

TestConditionBean.java

public class TestConditionBean {
    public TestConditionBean(){
        System.out.println("TestConditionBean");
    }
    @Override
    public String toString() {
        return "TestConditionBean";
    }
}

TestConditionBean2.java

public class TestConditionBean2 {
    public TestConditionBean2(){
        System.out.println("TestConditionBean2");
    }
    @Override
    public String toString() {
        return "TestConditionBean2";
    }
}

ConditionalConfiguration.java

/** 引入配置文件 条件注册的条件*/
@Configuration("conditionalConfiguration")
@Conditional(BeanConfigurationCondition.class)
public class ConditionalConfiguration {
    @Bean("testConditionBean")
    public TestConditionBean testConditionBean(){
        return new TestConditionBean() ;
    }
    /** 引入条件注册的条件*/
    @Bean
    @Conditional(BeanCondition.class)
    public TestConditionBean2 testConditionBean2(){
        return new TestConditionBean2() ;
    }
}

ioc-conditional.xml

<?xml version="1.0" encoding="utf-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
    <context:component-scan base-package="com.nancy.ioc.BeanFactoryPostProcessor.conditional">
    </context:component-scan>
</beans>

ConditionalTest

public class ConditionalTest {
    private ApplicationContext applicationContext;
    @Before
    public void beforeApplicationContext() {
        applicationContext = new ClassPathXmlApplicationContext("ioc-conditional.xml");
    }
    @Test
    public void test() {
        try {
            TestConditionBean testConditionBean = applicationContext.getBean("testConditionBean", TestConditionBean.class);
            System.out.println(testConditionBean);
        } catch (Exception e) {
            e.printStackTrace();
        }
        try {
            boolean flag = applicationContext.containsBean("testConditionBean2");
            System.out.println("testConditionBean2 has been load = " + flag);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    @After
    public void after() {
        ((ClassPathXmlApplicationContext) applicationContext).close();
    }
}

BeanConfigurationCondition条件中返回随机值将会产生不同的结果;可以注释掉TestConditionBean的@Bean注解观察运行结果

  • BeanConfigurationCondition matchs返回true

BeanConfigurationCondition :true
20:21:00.749 [main] DEBUG org.springframework.context.annotation.ClassPathBeanDefinitionScanner - Identified candidate component class: file [D:\project\learning-src\spring-src\target\classes\com\nancy\ioc\BeanFactoryPostProcessor\conditional\ConditionalConfiguration.class]
20:21:00.760 [main] DEBUG org.springframework.beans.factory.xml.XmlBeanDefinitionReader - Loaded 6 bean definitions from class path resource [ioc-conditional.xml]
20:21:00.782 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'org.springframework.context.annotation.internalConfigurationAnnotationProcessor'
BeanConfigurationCondition :true
20:21:00.892 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'org.springframework.context.event.internalEventListenerProcessor'
20:21:00.893 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'org.springframework.context.event.internalEventListenerFactory'
20:21:00.895 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'org.springframework.context.annotation.internalAutowiredAnnotationProcessor'
20:21:00.896 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'org.springframework.context.annotation.internalCommonAnnotationProcessor'
20:21:00.903 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'conditionalConfiguration'
20:21:00.910 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'testConditionBean'
TestConditionBean
20:21:00.926 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'testConditionBean2'
TestConditionBean2
TestConditionBean
testConditionBean2 has been load = true

  • BeanConfigurationCondition matchs返回false

BeanConfigurationCondition :false
20:23:38.016 [main] DEBUG org.springframework.beans.factory.xml.XmlBeanDefinitionReader - Loaded 5 bean definitions from class path resource [ioc-conditional.xml]
20:23:38.026 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'org.springframework.context.annotation.internalConfigurationAnnotationProcessor'
20:23:38.052 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'org.springframework.context.event.internalEventListenerProcessor'
20:23:38.053 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'org.springframework.context.event.internalEventListenerFactory'
20:23:38.055 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'org.springframework.context.annotation.internalAutowiredAnnotationProcessor'
20:23:38.055 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'org.springframework.context.annotation.internalCommonAnnotationProcessor'
org.springframework.beans.factory.NoSuchBeanDefinitionException: No bean named 'testConditionBean' available
.....省略堆栈信息
testConditionBean2 has been load = false

demo源码代码点击这里

二、注解@Conditional条件注册加载分析

涉及到bean注册很容易猜到跟ConfigurationClassPostProcessor类相关,如果不了解可点击这里

在构造ConfigurationClassParser对象的时候:

public ConfigurationClassParser(MetadataReaderFactory metadataReaderFactory,
			ProblemReporter problemReporter, Environment environment, ResourceLoader resourceLoader,
			BeanNameGenerator componentScanBeanNameGenerator, BeanDefinitionRegistry registry) {
	this.metadataReaderFactory = metadataReaderFactory;
	this.problemReporter = problemReporter;
	this.environment = environment;
	this.resourceLoader = resourceLoader;
	this.registry = registry;
	this.componentScanParser = new ComponentScanAnnotationParser(
				environment, resourceLoader, componentScanBeanNameGenerator, registry);
	// 条件注册核心逻辑类,里面包含了条件判断所需的条件信息ConditionContext		
	this.conditionEvaluator = new ConditionEvaluator(registry, environment, resourceLoader);
}	

跟进ConditionEvaluator类和ConditionContextImpl类:

public ConditionEvaluator(@Nullable BeanDefinitionRegistry registry,
			@Nullable Environment environment, @Nullable ResourceLoader resourceLoader) {
      this.context = new ConditionContextImpl(registry, environment, resourceLoader);
}


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;

	public ConditionContextImpl(@Nullable BeanDefinitionRegistry registry,
				@Nullable Environment environment, @Nullable ResourceLoader resourceLoader) {
		this.registry = registry;
		this.beanFactory = deduceBeanFactory(registry);
		this.environment = (environment != null ? environment : deduceEnvironment(registry));
		this.resourceLoader = (resourceLoader != null ? resourceLoader : deduceResourceLoader(registry));
		this.classLoader = deduceClassLoader(resourceLoader, this.beanFactory);
	}
	// ............
}	

由上述类的描述之后,进入条件注册入口ConfigurationClassParser#processConfigurationClass :

protected void processConfigurationClass(ConfigurationClass configClass) throws IOException {
		// 条件注册入口,判断configuration类本身是否加载进行判断
		if (this.conditionEvaluator.shouldSkip(configClass.getMetadata(), ConfigurationPhase.PARSE_CONFIGURATION)) {
			return;
		}
		//.....................
		SourceClass sourceClass = asSourceClass(configClass);
		do {
			//
			sourceClass = doProcessConfigurationClass(configClass, sourceClass);
		}
		while (sourceClass != null);
		this.configurationClasses.put(configClass, configClass);
}

跟进ConfigurationClassParser#doProcessConfigurationClass :

@Nullable
protected final SourceClass doProcessConfigurationClass(ConfigurationClass configClass, SourceClass sourceClass)
			throws IOException {
	// ...................
	// Process any @ComponentScan annotations
	Set<AnnotationAttributes> componentScans = AnnotationConfigUtils.attributesForRepeatable(
				sourceClass.getMetadata(), ComponentScans.class, ComponentScan.class);
	if (!componentScans.isEmpty() &&
			// 对configuration类内部定义的bean是否加载进行判断
			!this.conditionEvaluator.shouldSkip(sourceClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN)) {
		// ...............
	}
	// .........
	// No superclass -> processing is complete
	return null;
}

Condition.java

@Conditional的逻辑条件判断,根据返回结果判断是否满足条件注册

@FunctionalInterface
public interface Condition {
	boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);
}

ConfigurationCondition.java

继承自Condition,专门用于判断Configuration类是否满足条件注册

public interface ConfigurationCondition extends Condition {
	ConfigurationPhase getConfigurationPhase();
	enum ConfigurationPhase {
		PARSE_CONFIGURATION,
		REGISTER_BEAN
	}
}

重点看看 ConditionEvaluator#shouldSkip 判断是否应该忽略改bean加载 :

	public boolean shouldSkip(@Nullable AnnotatedTypeMetadata metadata, @Nullable ConfigurationPhase phase) {
		if (metadata == null || !metadata.isAnnotated(Conditional.class.getName())) {
			return false;
		}
		if (phase == null) {
			// 如果为configuration类,应先判断configuration类是否符合条件注册
			if (metadata instanceof AnnotationMetadata &&
					ConfigurationClassUtils.isConfigurationCandidate((AnnotationMetadata) metadata)) {
				return shouldSkip(metadata, ConfigurationPhase.PARSE_CONFIGURATION);
			}

			// 否则解析内部的bean定义是否符合条件注册
			return shouldSkip(metadata, ConfigurationPhase.REGISTER_BEAN);
		}
		// 主要步骤:
		// 1、获取所有的Condition条件对象
		// 2、排序满足优先顺序
		// 3、根据处理好的Condition条件对象集合,判断是否符合条件注册。只要某个不符合,直接返回true即,忽略该配置。
		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;
	}

以上分析可以清晰看出,spring条件注册入口也是在ConfigurationClassPostProcessor类,主要的核心组件:

  • 注解@Conditional是接触注解定义加载条件
  • 接口Condition/ConfigurationCondition提供模板统一方法,实现该接口封装实际的判断逻辑
  • 工具类ConditionEvaluator/ConditionContextImpl整合具体的逻辑
三、spring boot对条件注册的拓展

SpringBootCondition实际是对spring的Condition进行拓展,主要增加:
1、日志处理
2、条件注册的详细报告
3、拓展@Conditional提供各式条件注册支持(重点),例如:@ConditionalOnBean @ConditionalOnProperty等

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 outcome = getMatchOutcome(context, metadata);
			logOutcome(classOrMethodName, outcome);
			recordEvaluation(context, classOrMethodName, outcome);
			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);
		}
	}
	private String getName(AnnotatedTypeMetadata metadata) {
		if (metadata instanceof AnnotationMetadata) {
			return ((AnnotationMetadata) metadata).getClassName();
		}
		if (metadata instanceof MethodMetadata) {
			MethodMetadata methodMetadata = (MethodMetadata) metadata;
			return methodMetadata.getDeclaringClassName() + "."
					+ methodMetadata.getMethodName();
		}
		return metadata.toString();
	}
	private static String getClassOrMethodName(AnnotatedTypeMetadata metadata) {
		if (metadata instanceof ClassMetadata) {
			ClassMetadata classMetadata = (ClassMetadata) metadata;
			return classMetadata.getClassName();
		}
		MethodMetadata methodMetadata = (MethodMetadata) metadata;
		return methodMetadata.getDeclaringClassName() + "#"
				+ methodMetadata.getMethodName();
	}
	protected final void logOutcome(String classOrMethodName, ConditionOutcome outcome) {
		if (this.logger.isTraceEnabled()) {
			this.logger.trace(getLogMessage(classOrMethodName, outcome));
		}
	}
	private StringBuilder getLogMessage(String classOrMethodName,
			ConditionOutcome outcome) {
		StringBuilder message = new StringBuilder();
		message.append("Condition ");
		message.append(ClassUtils.getShortName(getClass()));
		message.append(" on ");
		message.append(classOrMethodName);
		message.append(outcome.isMatch() ? " matched" : " did not match");
		if (StringUtils.hasLength(outcome.getMessage())) {
			message.append(" due to ");
			message.append(outcome.getMessage());
		}
		return message;
	}
	/** springboot会在beanfactory内部构建一个autoConfigurationReport类,记录条件注册详情 */
	private void recordEvaluation(ConditionContext context, String classOrMethodName,
			ConditionOutcome outcome) {
		if (context.getBeanFactory() != null) {
			ConditionEvaluationReport.get(context.getBeanFactory())
					.recordConditionEvaluation(classOrMethodName, this, outcome);
		}
	}
	/** 模板方法,待子类实现具体逻辑 */
	public abstract ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata);
	// 工具方法
	protected final boolean anyMatches(ConditionContext context,
			AnnotatedTypeMetadata metadata, Condition... conditions) {
		for (Condition condition : conditions) {
			if (matches(context, metadata, condition)) {
				return true;
			}
		}
		return false;
	}
	protected final boolean matches(ConditionContext context,
			AnnotatedTypeMetadata metadata, Condition condition) {
		if (condition instanceof SpringBootCondition) {
			return ((SpringBootCondition) condition).getMatchOutcome(context, metadata)
					.isMatch();
		}
		return condition.matches(context, metadata);
	}
}

找到一篇总结得比较好的文章,可点击这里

posted on 2020-06-05 18:10  小猩  阅读(506)  评论(0编辑  收藏  举报

导航