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 { /**View Code* 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 {@codetrue} 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); }
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