• 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方法】。

  • 相关阅读:
    pytest简介
    python @property的用法及含义全面解析
    python的各种推导式(列表推导式、字典推导式、集合推导式)
    python--random库基本介绍
    整理一下python中with的用法
    Python之路:进程、线程
    Python代码风格的良好养成
    Ubuntu 部署Python开发环境
    Python面向对象编程
    Python文件操作
  • 原文地址:https://www.cnblogs.com/shirui/p/9427045.html
Copyright © 2020-2023  润新知