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);
    	}
    }
    

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

  • 相关阅读:
    JavaScript 为字符串添加样式 【每日一段代码80】
    JavaScript replace()方法 【每日一段代码83】
    JavaScript for in 遍历数组 【每日一段代码89】
    JavaScript 创建用于对象的模板【每日一段代码78】
    html5 css3 新元素简单页面布局
    JavaScript Array() 数组 【每日一段代码88】
    JavaScript toUTCString() 方法 【每日一段代码86】
    位运算
    POJ 3259 Wormholes
    POJ 3169 Layout
  • 原文地址:https://www.cnblogs.com/xiaoxing/p/13026423.html
Copyright © 2011-2022 走看看