spring源码分析系列 (17) spring条件注册@Conditional 以及 springboot对条件注册的拓展
主要分析内容
一、注解@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);
}
}
找到一篇总结得比较好的文章,可点击这里