Spring_总结_04_高级配置之条件注解@Conditional

Posted shirui

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Spring_总结_04_高级配置之条件注解@Conditional相关的知识,希望对你有一定的参考价值。

一、前言

本文承接上一节:Spring_总结_04_高级配置(一)之Profile

在上一节,我们了解到 Profile 为不同环境下使用不同的配置提供了支持,那么Profile到底是如何实现的呢?其实Profile正是通过条件注解来实现的。

 

条件注解的应用场景举例:

(1)希望一个或多个 bean 只有在应用的类路径下包含特定的库时才创建 

(2)希望某个bean只有当另外某个特定bean也声明了之后才创建

(3)希望只有某个特定的环境变量设置之后,才会创建某个bean

上述场景能让我们联想到springboot的自动化配置、Profile以及配置文件等。

 

 

二、概述

1.定义

@Conditionnal 能根据特定条件来控制Bean的创建行为。

 

2.用法

@Conditional注解可以用到带有@Bean注解的方法上,如果给定的条件计算结果为true,就会创建这个bean,否则,忽略这个bean。

 

3.用处

可以用来进行一些自动化配置,如上述三个应用场景。

 

 

三、使用实例

1.创建Condition实现类

Condition接口如下,只有一个 matches 方法,用来判断是否满足条件。

技术分享图片
@FunctionalInterface
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 the annotated component‘s registration
     */
    boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);

}
View Code

Condition实现类 MagicExistsCondition
技术分享图片
public class MagicExistsCondition  implements Condition {

    /**
     * 1.判断是否满足条件
     * @param context
     * @param metadata
     * @return
     */
    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        Environment environment = context.getEnvironment();
        /**
         * 检查环境中是否存在magic属性,若存在则满足条件
         */
        return environment.containsProperty("magic");
    }
}
View Code

 

2.使用@Conditional注解

如以下代码,在装配Bean的时候,使用@conditional注解,以便只有在满足条件地情况下才创建此bean

技术分享图片
    /**
     * 条件化地创建Bean
     * 若满足MagicExistsCondition的条件,则创建此Bean,否则忽略此bean.
     * @return
     */
    @Conditional(MagicExistsCondition.class)
    @Bean
    public MagicBean magicBean(){
        return new MagicBean();
    }
View Code

 

 

四、Condition接口分析

Condition接口如下,只有一个 matches 方法,用来判断是否满足条件。

技术分享图片
@FunctionalInterface
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 the annotated component‘s registration
     */
    boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);

}
View Code

 

在设置条件时,可用 ConditionContext  和 AnnotatedTypeMetadata 来进行条件地设置。

1.ConditionContext  

源码如下:

技术分享图片
/**
 * Context information for use by {@link Condition}s.
 *
 * @author Phillip Webb
 * @author Juergen Hoeller
 * @since 4.0
 */
public interface ConditionContext {

    /**
     * Return the {@link BeanDefinitionRegistry} that will hold the bean definition
     * should the condition match.
     * @throws IllegalStateException if no registry is available (which is unusual:
     * only the case with a plain {@link ClassPathScanningCandidateComponentProvider})
     */
    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 (or not downcastable to {@code ConfigurableListableBeanFactory}).
     */
    @Nullable
    ConfigurableListableBeanFactory getBeanFactory();

    /**
     * Return the {@link Environment} for which the current application is running.
     */
    Environment getEnvironment();

    /**
     * Return the {@link ResourceLoader} currently being used.
     */
    ResourceLoader getResourceLoader();

    /**
     * Return the {@link ClassLoader} that should be used to load additional classes
     * (only {@code null} if even the system ClassLoader isn‘t accessible).
     * @see org.springframework.util.ClassUtils#forName(String, ClassLoader)
     */
    @Nullable
    ClassLoader getClassLoader();

}
View Code

 

作用如下:

 

1
getRegistry
利用返回的 BeanDefinitionRegistry 来检查bean定义
2
getBeanFactory
利用返回的 ConfigurableListableBeanFactory来检查bean是否存在,甚至探查bean的属性
3
getEnvironment
利用返回的 Environment 检查环境变量是否存在以及它的值是什么
4
getResourceLoader
读取并探查返回的 ResourceLoader 所加载的资源
5
getClassLoader
借助返回的ClassLoader加载并检查类是否存在

 

 

2.AnnotatedTypeMetadata

源码如下:

技术分享图片
/**
 * Defines access to the annotations of a specific type ({@link AnnotationMetadata class}
 * or {@link MethodMetadata method}), in a form that does not necessarily require the
 * class-loading.
 *
 * @author Juergen Hoeller
 * @author Mark Fisher
 * @author Mark Pollack
 * @author Chris Beams
 * @author Phillip Webb
 * @author Sam Brannen
 * @since 4.0
 * @see AnnotationMetadata
 * @see MethodMetadata
 */
public interface AnnotatedTypeMetadata {

    /**
     * Determine whether the underlying element has an annotation or meta-annotation
     * of the given type defined.
     * <p>If this method returns {@code true}, then
     * {@link #getAnnotationAttributes} will return a non-null Map.
     * @param annotationName the fully qualified class name of the annotation
     * type to look for
     * @return whether a matching annotation is defined
     */
    boolean isAnnotated(String annotationName);

    /**
     * Retrieve the attributes of the annotation of the given type, if any (i.e. if
     * defined on the underlying element, as direct annotation or meta-annotation),
     * also taking attribute overrides on composed annotations into account.
     * @param annotationName the fully qualified class name of the annotation
     * type to look for
     * @return a Map of attributes, with the attribute name as key (e.g. "value")
     * and the defined attribute value as Map value. This return value will be
     * {@code null} if no matching annotation is defined.
     */
    @Nullable
    Map<String, Object> getAnnotationAttributes(String annotationName);

    /**
     * Retrieve the attributes of the annotation of the given type, if any (i.e. if
     * defined on the underlying element, as direct annotation or meta-annotation),
     * also taking attribute overrides on composed annotations into account.
     * @param annotationName the fully qualified class name of the annotation
     * type to look for
     * @param classValuesAsString whether to convert class references to String
     * class names for exposure as values in the returned Map, instead of Class
     * references which might potentially have to be loaded first
     * @return a Map of attributes, with the attribute name as key (e.g. "value")
     * and the defined attribute value as Map value. This return value will be
     * {@code null} if no matching annotation is defined.
     */
    @Nullable
    Map<String, Object> getAnnotationAttributes(String annotationName, boolean classValuesAsString);

    /**
     * Retrieve all attributes of all annotations of the given type, if any (i.e. if
     * defined on the underlying element, as direct annotation or meta-annotation).
     * Note that this variant does <i>not</i> take attribute overrides into account.
     * @param annotationName the fully qualified class name of the annotation
     * type to look for
     * @return a MultiMap of attributes, with the attribute name as key (e.g. "value")
     * and a list of the defined attribute values as Map value. This return value will
     * be {@code null} if no matching annotation is defined.
     * @see #getAllAnnotationAttributes(String, boolean)
     */
    @Nullable
    MultiValueMap<String, Object> getAllAnnotationAttributes(String annotationName);

    /**
     * Retrieve all attributes of all annotations of the given type, if any (i.e. if
     * defined on the underlying element, as direct annotation or meta-annotation).
     * Note that this variant does <i>not</i> take attribute overrides into account.
     * @param annotationName the fully qualified class name of the annotation
     * type to look for
     * @param classValuesAsString  whether to convert class references to String
     * @return a MultiMap of attributes, with the attribute name as key (e.g. "value")
     * and a list of the defined attribute values as Map value. This return value will
     * be {@code null} if no matching annotation is defined.
     * @see #getAllAnnotationAttributes(String)
     */
    @Nullable
    MultiValueMap<String, Object> getAllAnnotationAttributes(String annotationName, boolean classValuesAsString);

}
View Code

通过 AnnotatedTypeMetadata 可以检查 @Bean 注解的方法上还有什么其他注解。

 

五、@Profile注解的实现原理

@Profile注解是基于@Conditional 和 condition 实现的

1.Profile注解类

技术分享图片
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional({ProfileCondition.class})
public @interface Profile {
    String[] value();
}
View Code

 

可以看到,@Profile 注解本身也使用了 @Conditional 注解,而Condition实现类为ProfileCondition

 

2.ProfileCondition

技术分享图片
/**
 * {@link Condition} that matches based on the value of a {@link Profile @Profile}
 * annotation.
 *
 * @author Chris Beams
 * @author Phillip Webb
 * @author Juergen Hoeller
 * @since 4.0
 */
class ProfileCondition implements Condition {

    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        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;
    }

}
View Code

 

可以看到, 此类主要是

(1)通过 AnnotatedTypeMetadata  获取到了 @Profile  的所有属性。

(2)然后检查value属性值,该属性包含了bean的profile名称

(3)根据 通过 ConditionContext 得到的Environment来检查 该profile是否处于激活状态【借助 acceptsProfiles方法】。

 

以上是关于Spring_总结_04_高级配置之条件注解@Conditional的主要内容,如果未能解决你的问题,请参考以下文章

Spring_总结_04_高级配置_Bean的初始化和销毁

Spring_总结_04_高级配置_运行时注入值

Spring_总结_03_装配Bean之导入与混合配置

阶段3 2.Spring_04.Spring的常用注解_4 由Component衍生的注解

阶段3 2.Spring_04.Spring的常用注解_3 用于创建的Component注解

Mybatis_总结_04_用_注解