1. 程式人生 > >Spring_總結_04_高級配置(二)之條件註解@Conditional

Spring_總結_04_高級配置(二)之條件註解@Conditional

fin 路徑 class annotate over inf clas wid onclick

一、前言

本文承接上一節: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