Spring中的@Conditional注解實現分析
@Conditional注解實現分析
@Conditional是Spring 4出現的注解,但是真正露出價值的是Spring Boot的擴展@ConditionalOnBean等。但是任然使用的是Spring框架進行處理,并沒有做太多定制的東西,所以還是先看看@Conditional注解的實現。
@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注解的結構比較簡單,只需要定義一個Condition子類即可,并且說明只有滿足了當前Condition的matches方法時才會將當前@Component注冊成Bean。那么再看看Condition接口和體系。
/**
* @since 4.0
* @see ConfigurationCondition
* @see Conditional
* @see ConditionContext
*/
@FunctionalInterface
public interface Condition {
boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);
}當前會傳入ConditionContext和AnnotatedTypeMetadata進行回調,返回是否匹配,如果不匹配則不會注冊成Bean。但是這是在哪里進行回調的呢?
ConfigurationClassParser # processConfigurationClass (ConfigurationClassParser # doProcessConfigurationClass等)
ConditionEvaluator # shouldSkip
比較清楚了,又是在處理@Import、@ComponentScan、ImportSelector等的處理類ConfigurationClassParser執(zhí)行時機比較清楚了
再看看Condition的結構體系:

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

