SpringBoot條件注解@Conditional詳細(xì)解析
一、條件注解@Conditional
@Conditional是Spring4.0提供的一個(gè)用于條件裝配的注解,其定義了一個(gè)Condition的數(shù)組,只有當(dāng)數(shù)組所有的條件都滿足的時(shí)候,組件才會(huì)被導(dǎo)入容器。
/** * 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 Condition}s that must {@linkplain Condition#matches match} * in order for the component to be registered. */ Class<? extends Condition>[] value(); }
@Conditional注解可以有兩種使用方法:
- 類型級(jí)別,任意直接或者間接標(biāo)注了@Conponent注解的類或者注解,比如@Configuration或者@Profile
- 方法級(jí)別,任意標(biāo)注了@Bean注解的方法
如果一個(gè)@Configuration類標(biāo)注了@Conditional,那么這個(gè)類所有的@Bean方法,@ComponentScan和@Import的結(jié)果都受@Conditional注解的條件約束。 特別要注意的是:@Conditional是不支持繼承的,任何父類的條件注解或者方法繼承的條件注解都不會(huì)生效。為了強(qiáng)化這些語(yǔ)義,@Conditional本身并沒(méi)有標(biāo)注@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接口傳遞兩個(gè)參數(shù)ConditionContext和AnnotatedTypeMetadata,在Condition實(shí)現(xiàn)類中可以直接使用這兩個(gè)參數(shù),獲取環(huán)境、容器、類等相關(guān)信息。
1. ConditionContext
/** * Context information for use by {@link Condition}s. * * @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可以獲取到BeanDefinitionRegistry、ConfigurableListableBeanFactory、Environment、ResourceLoader、ClassLoader這些環(huán)境相關(guān)的信息。
2. AnnotatedTypeMetadata
public interface AnnotatedTypeMetadata { // 根據(jù)“全類名”判斷是否被指定 直接注解或元注解 標(biāo)注 boolean isAnnotated(String annotationName); // 根據(jù)”全類名“獲取所有注解屬性(包括元注解) @Nullable Map<String, Object> getAnnotationAttributes(String annotationName); @Nullable // 同上,但是第二個(gè)參數(shù)傳 true 時(shí)會(huì)把屬性中對(duì)應(yīng)值為 Class 的值 // 轉(zhuǎn)為 字符串,避免需要預(yù)先加載對(duì)應(yīng) Class Map<String, Object> getAnnotationAttributes(String annotationName, boolean classValuesAsString); @Nullable // 同上,MultiValueMap 是一個(gè) key 可以對(duì)應(yīng)多個(gè) value 的變種 map MultiValueMap<String, Object> getAllAnnotationAttributes(String annotationName); @Nullable MultiValueMap<String, Object> getAllAnnotationAttributes(String annotationName, boolean classValuesAsString); }
頂層接口,可被注解標(biāo)注類型(類、方法)元數(shù)據(jù)的抽象,提供了兩個(gè)核心方法:
- 根據(jù) 全類名 判斷是否被指定注解標(biāo)注
- 根據(jù) 全類名 返回指定注解的屬性集合(包括元注解)
三、@Conditional如何被解析,Condition方法何時(shí)調(diào)用?
@Conditional和Condition的相關(guān)邏輯是在類ConditionEvaluator#中實(shí)現(xiàn)的。
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; } } }
而該類根據(jù)構(gòu)造方法的調(diào)用點(diǎn),可知以下幾個(gè)類會(huì)使用到。
- AnnotatedBeanDefinitionReader 注解標(biāo)注時(shí)候
- ClassPathScanningCandidateComponentProvider注解掃描時(shí)候
- ConfigurationClassBeanDefinitionReader、ConfigurationClassParser(ConfigurationClassPostProcessor) 解析Configuration注解的過(guò)程中
四、典型應(yīng)用 @Profile
@Profile就是典型地基于@Conditional的擴(kuò)展,其條件邏輯封裝在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中的應(yīng)用
到此這篇關(guān)于SpringBoot條件注解@Conditional詳細(xì)解析的文章就介紹到這了,更多相關(guān)SpringBoot條件注解@Conditional內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java爬蟲(chóng)框架之WebMagic實(shí)戰(zhàn)
這篇文章主要介紹了Java爬蟲(chóng)框架之WebMagic實(shí)戰(zhàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-12-12Java?Request獲取請(qǐng)求頭數(shù)據(jù)實(shí)例詳解
在開(kāi)發(fā)中我們經(jīng)常需要獲取用戶IP地址,通過(guò)地址來(lái)實(shí)現(xiàn)一些功能,下面這篇文章主要給大家介紹了關(guān)于Java中Request獲取請(qǐng)求頭數(shù)據(jù)的相關(guān)資料,需要的朋友可以參考下2024-01-01java實(shí)現(xiàn)動(dòng)態(tài)上傳多個(gè)文件并解決文件重名問(wèn)題
這篇文章主要為大家詳細(xì)介紹了java實(shí)現(xiàn)動(dòng)態(tài)上傳多個(gè)文件,并解決文件重名問(wèn)題的方法,感興趣的小伙伴們可以參考一下2016-03-03Java中的InputStreamReader和OutputStreamWriter源碼分析_動(dòng)力節(jié)點(diǎn)Java學(xué)院整理
本文通過(guò)示例代碼給大家解析了Java中的InputStreamReader和OutputStreamWriter知識(shí),需要的的朋友參考下吧2017-05-05解決mybatis 中collection嵌套collection引發(fā)的bug
這篇文章主要介紹了解決mybatis 中collection嵌套collection引發(fā)的bug,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-12-12Java報(bào)錯(cuò):UnsupportedOperationException in Collection
在Java編程中,UnsupportedOperationException是一種常見(jiàn)的運(yùn)行時(shí)異常,通常在試圖對(duì)不支持的操作執(zhí)行修改時(shí)發(fā)生,它表示當(dāng)前操作不被支持,本文將深入探討UnsupportedOperationException的產(chǎn)生原因,并提供具體的解決方案和最佳實(shí)踐,需要的朋友可以參考下2024-06-06list轉(zhuǎn)tree和list中查找某節(jié)點(diǎn)下的所有數(shù)據(jù)操作
這篇文章主要介紹了list轉(zhuǎn)tree和list中查找某節(jié)點(diǎn)下的所有數(shù)據(jù)操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-09-09實(shí)現(xiàn)quartz定時(shí)器及quartz定時(shí)器原理介紹
Quartz是一個(gè)大名鼎鼎的Java版開(kāi)源定時(shí)調(diào)度器,功能強(qiáng)悍,使用方便,下面我們看看如何使用它2013-12-12Java 8 Lambda 表達(dá)式比較器使用示例代碼
這篇文章主要介紹了Java 8 Lambda 表達(dá)式比較器使用示例代碼,非常不錯(cuò),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2019-08-08