zoukankan      html  css  js  c++  java
  • 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:projectlearning-srcspring-src argetclassescom ancyiocBeanFactoryPostProcessorconditionalConditionalConfiguration.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);
    	}
    }
    

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

  • 相关阅读:
    LIN总线学习-总线逻辑
    使用万用表测量CAN总线电压及实际电压与逻辑电瓶关系
    汽车网络和控制单元的安全威胁研究
    [CANopen] SDO的命令字
    新起点,新开始
    Git Commands
    Obsessive String
    The E-pang Palace
    最长递增子序列(LIS)
    Valid Sets
  • 原文地址:https://www.cnblogs.com/xiaoxing/p/13026423.html
Copyright © 2011-2022 走看看