Spring中的@Conditional注解實(shí)現(xiàn)分析
@Conditional注解實(shí)現(xiàn)分析
@Conditional是Spring 4出現(xiàn)的注解,但是真正露出價(jià)值的是Spring Boot的擴(kuò)展@ConditionalOnBean等。但是任然使用的是Spring框架進(jìn)行處理,并沒有做太多定制的東西,所以還是先看看@Conditional注解的實(shí)現(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)比較簡單,只需要定義一個(gè)Condition子類即可,并且說明只有滿足了當(dāng)前Condition的matches方法時(shí)才會(huì)將當(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)前會(huì)傳入ConditionContext和AnnotatedTypeMetadata進(jìn)行回調(diào),返回是否匹配,如果不匹配則不會(huì)注冊成Bean。但是這是在哪里進(jìn)行回調(diào)的呢?
ConfigurationClassParser # processConfigurationClass (ConfigurationClassParser # doProcessConfigurationClass等)
ConditionEvaluator # shouldSkip
比較清楚了,又是在處理@Import、@ComponentScan、ImportSelector等的處理類ConfigurationClassParser執(zhí)行時(shí)機(jī)比較清楚了
再看看Condition的結(jié)構(gòu)體系:

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

