Spring中的@Conditional注解實現(xiàn)分析
@Conditional注解實現(xiàn)分析
@Conditional是Spring 4出現(xiàn)的注解,但是真正露出價值的是Spring Boot的擴展@ConditionalOnBean等。但是任然使用的是Spring框架進行處理,并沒有做太多定制的東西,所以還是先看看@Conditional注解的實現(xiàn)。
@Target({ElementType.TYPE, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface Conditional { /** * All {@link Condition Conditions} that must {@linkplain Condition#matches match} * in order for the component to be registered. */ Class<? extends Condition>[] value(); }
先看看@Conditional注解的結(jié)構(gòu)比較簡單,只需要定義一個Condition子類即可,并且說明只有滿足了當(dāng)前Condition的matches方法時才會將當(dāng)前@Component注冊成Bean。那么再看看Condition接口和體系。
/** * @since 4.0 * @see ConfigurationCondition * @see Conditional * @see ConditionContext */ @FunctionalInterface public interface Condition { boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata); }
當(dāng)前會傳入ConditionContext和AnnotatedTypeMetadata進行回調(diào),返回是否匹配,如果不匹配則不會注冊成Bean。但是這是在哪里進行回調(diào)的呢?
ConfigurationClassParser # processConfigurationClass (ConfigurationClassParser # doProcessConfigurationClass等)
ConditionEvaluator # shouldSkip
比較清楚了,又是在處理@Import、@ComponentScan、ImportSelector等的處理類ConfigurationClassParser執(zhí)行時機比較清楚了
再看看Condition的結(jié)構(gòu)體系:
大致有四類
1)、ProfileCondition,項目啟動后Profile信息存放在ApplicationContext的Environment中,能拿到兩者之一就可以判斷。
2)、ConfigurationCondition
public interface ConfigurationCondition extends Condition { // 定義了子類必須實現(xiàn),返回下面枚舉的一種 ConfigurationPhase getConfigurationPhase(); // 判斷階段 enum ConfigurationPhase { // Conponent階段,即將@Component加入到BeanFactory PARSE_CONFIGURATION, // 只有通過getBean才能正在將Bean注冊到Ioc容器中。前提是要將BeanDefinition添加到 // BeanFactory中 REGISTER_BEAN } }
3)、ConditionEvalutionReport,Spring Boot報表相關(guān)
4)、SpringBootCondition,直接是Spring Boot中擴展的。下一篇博客,具體分析 @ConditionalOnBean等再具體分析。
幾個的角色比較清楚了,只要一個@Conditional注解,注解的屬性為@Condition或其子類。根據(jù)回調(diào)@Condition的matches方法,即可判斷是否將注冊成Bean。
先看看回調(diào)時機,都是在處理@Component、@ComponentSacn、ImportSelector等情況注冊Bean時。
由于情況比較復(fù)雜,可能@Component上有@ComponentScan,則會遞歸進行處理,總之都會先調(diào)用ConfigurationClassParser的ConditionEvaluator conditionEvaluator的shouldSkip方法判斷是否跳過。
比如:
protected void processConfigurationClass(ConfigurationClass configClass) throws IOException { if (this.conditionEvaluator.shouldSkip(configClass.getMetadata(), ConfigurationPhase.PARSE_CONFIGURATION)) { return; } // 省略其余代碼 }
而conditionEvaluator在ConfigurationClassParser的構(gòu)造器中被初始化,如下:
this.conditionEvaluator = new ConditionEvaluator(registry, environment, resourceLoader);
先看看ConditionEvaluator的類結(jié)構(gòu)
class ConditionEvaluator { private final ConditionContextImpl context; public ConditionEvaluator(@Nullable BeanDefinitionRegistry registry, @Nullable Environment environment, @Nullable ResourceLoader resourceLoader) { this.context = new ConditionContextImpl(registry, environment, resourceLoader); } // 省略其他方法 private static class ConditionContextImpl implements ConditionContext { private final BeanDefinitionRegistry registry; private final ConfigurableListableBeanFactory beanFactory; private final Environment environment; private final ResourceLoader resourceLoader; private final ClassLoader classLoader; public ConditionContextImpl(@Nullable BeanDefinitionRegistry registry, @Nullable Environment environment, @Nullable ResourceLoader resourceLoader) { this.registry = registry; this.beanFactory = deduceBeanFactory(registry); this.environment = (environment != null ? environment : deduceEnvironment(registry)); this.resourceLoader = (resourceLoader != null ? resourceLoader : deduceResourceLoader(registry)); this.classLoader = deduceClassLoader(resourceLoader, this.beanFactory); } } }
也就是說,在ConfigurationClassParser構(gòu)造器中初始化ConditionEvaluator時候,就初始化了內(nèi)部類ConditionContextImpl,并且傳入了BeanFactory(Bean是否存在可以通過工廠進行判斷)、Environment(環(huán)境配置、Profile等存放在其中)、ResourceLoader(Spring的Resource加載器)、ClassLoader(類加載器)等。
到這里就比較清楚了,回調(diào)Condition的matches接口時傳入這些組件的類ConditionContextImpl,要實現(xiàn)@ConditionalOnBean、@OnPropertyCondition、@Profile、@ConditionalOnClass等都比較簡單了。
在調(diào)用Condition的matches時,不僅傳入了ConditionContextImpl,還出入了AnnotatedTypeMetadata,這是當(dāng)前注解結(jié)合被Spring封裝的注解元信息。理解比較抽象,比如自動裝配EmbeddedTomcat時需要同時存在Servlet、Tomcat、upgradeProtocol類;并且沒有將ServletWebServerFactory注冊成Bean,此時Component才能真正生效,如下:
@Configuration(proxyBeanMethods = false) @ConditionalOnClass({ Servlet.class, Tomcat.class, UpgradeProtocol.class }) @ConditionalOnMissingBean(value = ServletWebServerFactory.class, search = SearchStrategy.CURRENT) public static class EmbeddedTomcat { // 省略其他代碼 }
具體再看看ConditionEvaluator的shouldSkip方法的實現(xiàn):
public boolean shouldSkip(@Nullable AnnotatedTypeMetadata metadata, @Nullable ConfigurationPhase phase) { // 注解信息不能為空, 并且必須有Conditional或者其子類 if (metadata == null || !metadata.isAnnotated(Conditional.class.getName())) { return false; } // 如果判斷階段為空,進行類型判斷再遞歸調(diào)用該方法 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類型,如上面的EmbeddedTomcat同時需要多個條件成立 for (String[] conditionClasses : getConditionClasses(metadata)) { for (String conditionClass : conditionClasses) { Condition condition = getCondition(conditionClass, this.context.getClassLoader()); conditions.add(condition); } } // 條件排序(根據(jù)Spring的那三個排序方式定義) AnnotationAwareOrderComparator.sort(conditions); for (Condition condition : conditions) { ConfigurationPhase requiredPhase = null; if (condition instanceof ConfigurationCondition) { requiredPhase = ((ConfigurationCondition) condition).getConfigurationPhase(); } // 判斷階段為空(非ConfigurationCondition的子類)、不需要判斷階段,則直接返回true // 否則才調(diào)用matches接口判斷 if ((requiredPhase == null || requiredPhase == phase) && !condition.matches(this.context, metadata)) { return true; } } return false; }
1)、注解信息不能為空, 并且必須有Conditional或者其子類
2)、階段為null,則根據(jù)情況設(shè)置階段后再遞歸調(diào)用該方法
3)、獲取所有的Condition列表
4)、進行排序
5)、遍歷Condition,是否在該階段進行判斷。需要則再調(diào)用該Condition的matches方法
總結(jié):添加了@Conditional或者@ConditionXXX注解,其value值會對應(yīng)一個Condition或者子類的Class。在處理@ComponentScan、ImportSelector等時會根據(jù)判斷階段,調(diào)用Condition的matches方法判斷是否進行注冊成Bean。從而實現(xiàn)各種復(fù)雜的動態(tài)判斷注冊成Bean的情況。
到此這篇關(guān)于Spring中的@Conditional注解實現(xiàn)分析的文章就介紹到這了,更多相關(guān)@Conditional注解實現(xiàn)分析內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
SpringBoot基于MyBatis-Plus實現(xiàn)Lambda Query查詢的示例代碼
MyBatis-Plus 是 MyBatis 的增強工具,簡化了數(shù)據(jù)庫操作,并提高了開發(fā)效率,它提供了多種查詢方式,包括常規(guī)的 SQL 查詢、Lambda Query 查詢、分頁查詢、條件查詢等,在本篇博客中,我們將詳細講解如何使用 MyBatis-Plus 的各種查詢方式,需要的朋友可以參考下2025-01-01SpringBoot使用Hibernate攔截器實現(xiàn)時間自動注入的操作代碼
這篇文章主要介紹了SpringBoot使用Hibernate攔截器實現(xiàn)時間自動注入的操作代碼,主要包括hibernate攔截器的相關(guān)知識,結(jié)合實例代碼給大家講解的非常詳細,對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2022-10-10java使用JDBC動態(tài)創(chuàng)建數(shù)據(jù)表及SQL預(yù)處理的方法
這篇文章主要介紹了java使用JDBC動態(tài)創(chuàng)建數(shù)據(jù)表及SQL預(yù)處理的方法,涉及JDBC操作數(shù)據(jù)庫的連接、創(chuàng)建表、添加數(shù)據(jù)、查詢等相關(guān)實現(xiàn)技巧,需要的朋友可以參考下2017-08-08java基于雙向環(huán)形鏈表解決丟手帕問題的方法示例
這篇文章主要介紹了java基于雙向環(huán)形鏈表解決丟手帕問題的方法,簡單描述了丟手帕問題,并結(jié)合實例形式給出了Java基于雙向環(huán)形鏈表解決丟手帕問題的步驟與相關(guān)操作技巧,需要的朋友可以參考下2017-11-11spring-AOP 及 AOP獲取request各項參數(shù)操作
這篇文章主要介紹了spring-AOP 及 AOP獲取request各項參數(shù)的操作,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-07-07