@Conditional && Condition

Posted 恒奇恒毅

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了@Conditional && Condition相关的知识,希望对你有一定的参考价值。

一、条件注解@Conditional

@Conditional是Spring4.0提供的一个用于条件装配的注解,其定义了一个Condition的数组,只有当数组所有的条件都满足的时候,组件才会被导入容器。

/**
 * Indicates that a component is only eligible for registration when all
 * @linkplain #value specified conditions match.
 *
 * <p>A <em>condition</em> is any state that can be determined programmatically
 * before the bean definition is due to be registered (see @link Condition for details).
 *
 * <p>The @code @Conditional annotation may be used in any of the following ways:
 * <ul>
 * <li>as a type-level annotation on any class directly or indirectly annotated with
 * @code @Component, including @link Configuration @Configuration classes</li>
 * <li>as a meta-annotation, for the purpose of composing custom stereotype
 * annotations</li>
 * <li>as a method-level annotation on any @link Bean @Bean method</li>
 * </ul>
 *
 * <p>If a @code @Configuration class is marked with @code @Conditional,
 * all of the @code @Bean methods, @link Import @Import annotations, and
 * @link ComponentScan @ComponentScan annotations associated with that
 * class will be subject to the conditions.
 *
 * <p><strong>NOTE</strong>: Inheritance of @code @Conditional annotations
 * is not supported; any conditions from superclasses or from overridden
 * methods will not be considered. In order to enforce these semantics,
 * @code @Conditional itself is not declared as
 * @link java.lang.annotation.Inherited @Inherited; furthermore, any
 * custom <em>composed annotation</em> that is meta-annotated with
 * @code @Conditional must not be declared as @code @Inherited.
 *
 * @author Phillip Webb
 * @author Sam Brannen
 * @since 4.0
 * @see Condition
 */
@Target(ElementType.TYPE, ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Conditional 

	/**
	 * All @link Conditions that must @linkplain Condition#matches match
	 * in order for the component to be registered.
	 */
	Class<? extends Condition>[] value();


@Conditional注解可以有两种使用方法:

  1. 类型级别,任意直接或者间接标注了@Conponent注解的类或者注解,比如@Configuration或者@Profile
  2. 方法级别,任意标注了@Bean注解的方法

如果一个@Configuration类标注了@Conditional,那么这个类所有的@Bean方法,@ComponentScan@Import的结果都受@Conditional注解的条件约束。
特别要注意的是:@Conditional是不支持继承的,任何父类的条件注解或者方法继承的条件注解都不会生效。为了强化这些语义,@Conditional本身并没有标注@Inherited。另外,任何使用了@Conditional注解的组合注解都不能声明为@Inherited

二、条件判断接口Condition

@Conditional注解依赖于Condition接口,该接口提供真正的条件判断逻辑。

/**
 * A single @code condition that must be @linkplain #matches matched in order
 * for a component to be registered.
 *
 * <p>Conditions are checked immediately before the bean-definition is due to be
 * registered and are free to veto registration based on any criteria that can
 * be determined at that point.
 *
 * <p>Conditions must follow the same restrictions as @link BeanFactoryPostProcessor
 * and take care to never interact with bean instances. For more fine-grained control
 * of conditions that interact with @code @Configuration beans consider the
 * @link ConfigurationCondition interface.
 *
 * @author Phillip Webb
 * @since 4.0
 * @see ConfigurationCondition
 * @see Conditional
 * @see ConditionContext
 */
public interface Condition 

	/**
	 * Determine if the condition matches.
	 * @param context the condition context
	 * @param metadata metadata of the @link org.springframework.core.type.AnnotationMetadata class
	 * or @link org.springframework.core.type.MethodMetadata method being checked.
	 * @return @code true if the condition matches and the component can be registered
	 * or @code false to veto registration.
	 */
	boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);


Condition接口传递两个参数ConditionContextAnnotatedTypeMetadata,在Condition实现类中可以直接使用这两个参数,获取环境、容器、类等相关信息。

1. ConditionContext

/**
 * Context information for use by @link Conditions.
 *
 * @author Phillip Webb
 * @since 4.0
 */
public interface ConditionContext 

	/**
	 * Return the @link BeanDefinitionRegistry that will hold the bean definition
	 * should the condition match or @code null if the registry is not available.
	 * @return the registry or @code null
	 */
	BeanDefinitionRegistry getRegistry();

	/**
	 * Return the @link ConfigurableListableBeanFactory that will hold the bean
	 * definition should the condition match or @code null if the bean factory
	 * is not available.
	 * @return the bean factory or @code null
	 */
	ConfigurableListableBeanFactory getBeanFactory();

	/**
	 * Return the @link Environment for which the current application is running
	 * or @code null if no environment is available.
	 * @return the environment or @code null
	 */
	Environment getEnvironment();

	/**
	 * Return the @link ResourceLoader currently being used or @code null
	 * if the resource loader cannot be obtained.
	 * @return a resource loader or @code null
	 */
	ResourceLoader getResourceLoader();

	/**
	 * Return the @link ClassLoader that should be used to load additional
	 * classes or @code null if the default classloader should be used.
	 * @return the class loader or @code null
	 */
	ClassLoader getClassLoader();


CondtitionContext可以获取到BeanDefinitionRegistryConfigurableListableBeanFactoryEnvironmentResourceLoaderClassLoader这些环境相关的信息。

2. AnnotatedTypeMetadata

参考 类和方法元信息、注解信息体系(AnnotatedTypeMetadata、AnnotationMetadata、ClassMetadata、MethodMetadata)
可以获取类及相关注解的相关信息。

三、@Conditional如何被解析,Condition方法何时调用?

@ConditionalCondition的相关逻辑是在类ConditionEvaluator#中实现的。

class ConditionEvaluator 

	private final ConditionContextImpl context;


	/**
	 * Create a new @link ConditionEvaluator instance.
	 */
	public ConditionEvaluator(BeanDefinitionRegistry registry, Environment environment, ResourceLoader resourceLoader) 
		this.context = new ConditionContextImpl(registry, environment, resourceLoader);
	


	/**
	 * Determine if an item should be skipped based on @code @Conditional annotations.
	 * The @link ConfigurationPhase will be deduced from the type of item (i.e. a
	 * @code @Configuration class will be @link ConfigurationPhase#PARSE_CONFIGURATION)
	 * @param metadata the meta data
	 * @return if the item should be skipped
	 */
	public boolean shouldSkip(AnnotatedTypeMetadata metadata) 
		return shouldSkip(metadata, null);
	

	/**
	 * Determine if an item should be skipped based on @code @Conditional annotations.
	 * @param metadata the meta data
	 * @param phase the phase of the call
	 * @return if the item should be skipped
	 */
	public boolean shouldSkip(AnnotatedTypeMetadata metadata, ConfigurationPhase phase) 
		if (metadata == null || !metadata.isAnnotated(Conditional.class.getName())) 
			return false;
		

		if (phase == null) 
			if (metadata instanceof AnnotationMetadata &&
					ConfigurationClassUtils.isConfigurationCandidate((AnnotationMetadata) metadata)) 
				return shouldSkip(metadata, ConfigurationPhase.PARSE_CONFIGURATION);
			
			return shouldSkip(metadata, ConfigurationPhase.REGISTER_BEAN);
		

		List<Condition> conditions = new ArrayList<Condition>();
		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) 
				if (!condition.matches(this.context, metadata)) 
					return true;
				
			
		

		return false;
	

	@SuppressWarnings("unchecked")
	private List<String[]> getConditionClasses(AnnotatedTypeMetadata metadata) 
		MultiValueMap<String, Object> attributes = metadata.getAllAnnotationAttributes(Conditional.class.getName(), true);
		Object values = (attributes != null ? attributes.get("value") : null);
		return (List<String[]>) (values != null ? values : Collections.emptyList());
	

	private Condition getCondition(String conditionClassName, ClassLoader classloader) 
		Class<?> conditionClass = ClassUtils.resolveClassName(conditionClassName, classloader);
		return (Condition) BeanUtils.instantiateClass(conditionClass);
	


	/**
	 * Implementation of a @link ConditionContext.
	 */
	private static class ConditionContextImpl implements ConditionContext 

		private final BeanDefinitionRegistry registry;

		private final ConfigurableListableBeanFactory beanFactory;

		private final Environment environment;

		private final ResourceLoader resourceLoader;

		public ConditionContextImpl(BeanDefinitionRegistry registry, Environment environment, ResourceLoader resourceLoader) 
			this.registry = registry;
			this.beanFactory = deduceBeanFactory(registry);
			this.environment = (environment != null ? environment : deduceEnvironment(registry));
			this.resourceLoader = (resourceLoader != null ? resourceLoader : deduceResourceLoader(registry));
		

		private ConfigurableListableBeanFactory deduceBeanFactory(BeanDefinitionRegistry source) 
			if (source instanceof ConfigurableListableBeanFactory) 
				return (ConfigurableListableBeanFactory) source;
			
			if (source instanceof ConfigurableApplicationContext) 
				return (((ConfigurableApplicationContext) source).getBeanFactory());
			
			return null;
		

		private Environment deduceEnvironment(BeanDefinitionRegistry source) 
			if (source instanceof EnvironmentCapable) 
				return ((EnvironmentCapable) source).getEnvironment();
			
			return null;
		

		private ResourceLoader deduceResourceLoader(BeanDefinitionRegistry source) 
			if (source instanceof ResourceLoader) 
				return (ResourceLoader) source;
			
			return null;
		

		@Override
		public BeanDefinitionRegistry getRegistry() 
			return this.registry;
		

		@Override
		public ConfigurableListableBeanFactory getBeanFactory() 
			return this.beanFactory;
		

		@Override
		public Environment getEnvironment() 
			return this.environment;
		

		@Override
		public ResourceLoader getResourceLoader() 
			return this.resourceLoader;
		

		@Override
		public ClassLoader getClassLoader() 
			if (this.resourceLoader != null) 
				return this.resourceLoader.getClassLoader();
			
			if (this.beanFactory != null) 
				return this.beanFactory.getBeanClassLoader();
			
			return null;
		
	


而该类根据构造方法的调用点,可知以下几个类会使用到。

  • AnnotatedBeanDefinitionReader 注解标注时候
  • ClassPathScanningCandidateComponentProvider注解扫描时候
  • ConfigurationClassBeanDefinitionReaderConfigurationClassParser(ConfigurationClassPostProcessor) 解析Configuration注解的过程中

四、典型应用

@Profile

@Profile就是典型地基于@Conditional的扩展,其条件逻辑封装在ProfileCondition

class ProfileCondition implements Condition 

	@Override
	public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) 
		if (context.getEnvironment() != null) 
			MultiValueMap<String, Object> attrs = metadata.getAllAnnotationAttributes(Profile.class.getName());
			if (attrs != null) 
				for (Object value : attrs.get("value")) 
					if (context.getEnvironment().acceptsProfiles(((String[]) value))) 
						return true;
					
				
				return false;
			
		
		return true;
	


Springboot中的应用

以上是关于@Conditional && Condition的主要内容,如果未能解决你的问题,请参考以下文章

[SpringBoot2]容器功能_底层注解&配置绑定_@Configuration&@Import&@Conditional&@ImportResource&

《CondLaneNet:a Top-to-down Lane Detection Framework Based on Conditional Convolution》论文笔记

C--Conditional Statements

C# 条件编译 (#if 和 Conditional)

C# 条件编译 (#if 和 Conditional)

C# 条件编译 (#if 和 Conditional)