條件註冊
Spring 4.0 引入條件註冊機制,暴露給用戶的API是@Conditional
和Condition
接口,把@Conditional
聲明在一個 @Component
類上,並接受一組條件(Condition實現),容器初始化期間,會調用每一個Condition實現的matches
方法進行測試, 只有全部Condition的matches方法都測試通過,纔會在容器中註冊當前實例,這和 @Profile 註解的功能有些類似,但是@Profile 是粗粒度的控制註冊範圍,而使用 @Conditional 能夠能精細實現控制每一個對象的註冊。
如何使用
@Conditional
和 @Component
一起使用, 這其中也就包括 @Configuration
註解,控制當前@Component聲明的組件 是否需要註冊。當和 @Configuration 配置類聯用時,對 @Import
和 @ComponentScan
進一步導入的組件也會有效。
@Conditional
也可以當作元註解使用,聲明更具體的條件註解,例如 @ConditionalOn...
這類註解。
例如需求是隻有程序運行在Linux類系統上,才應該在容器中註冊某個對象,可以參考下面的Condition實現:
public class ConditionOnLinux implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
try {
Process process = Runtime.getRuntime().exec(new String[]{"uname", "-s"});
try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()))) {
String ostype = reader.readLine();
if ("Linux".equalsIgnoreCase(ostype))
return true;
}
return false;
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
然後在配置中把ConditionOnLinux實現傳遞給@Conditional:
@Configuration
public class AppConfig {
//這樣配置後,程序只有運行在Linux類系統中,纔會註冊LinuxObject
@Bean
@Conditional(ConditionOnLinux.class)
public LinuxObject linuxObject() {
return new LinuxObject();
}
}
原理分析
當容器註冊BeanDefinition之前,Spring會先檢查@Conditional條件是否成立,關鍵就是@Conditional註解在什麼時機發生作用, 這個過程可以跟蹤AnnotationConfigApplicationContext 容器的啓動流程,涉及到兩處實現對@Conditional的處理。
(1)對@Configuration配置類的註冊
第一點是對@Configuration類本身的註冊時機,這在我們調用AnnotationConfigApplicationContext#register(annotatetClasses)
方法的流程中發生,這個動作是委託到AnnotatedBeanDefinitionReader
對象的register方法實現的,下面是這個方法的關鍵代碼:
public void registerBean(Class<?> annotatedClass, String name, Class<? extends Annotation>... qualifiers) {
//對配置類創建BeanDefinition對象
AnnotatedGenericBeanDefinition abd = new AnnotatedGenericBeanDefinition(annotatedClass);
//檢測是否跳過註冊,shouldSkip方法返回true就跳過註冊,下面有進一步說明
if (this.conditionEvaluator.shouldSkip(abd.getMetadata())) {
return;
}
//這裏省略掉一些解析BeanDefinition的其他屬性的代碼,包括名稱、scope等
//最後註冊BeanDefinition:
BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(abd, beanName);
definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
BeanDefinitionReaderUtils.registerBeanDefinition(definitionHolder, this.registry);
}
那繼續看一看ConditionEvaluator#shouldSkip
方法的內部細節處理,添加了註釋說明流程細節:
public boolean shouldSkip(AnnotatedTypeMetadata metadata, ConfigurationPhase phase) {
//表示如果類沒有@Conditional註解修飾,則返回false,返回false則上層調用處不跳過註冊
if (metadata == null || !metadata.isAnnotated(Conditional.class.getName())) {
return false;
}
//按照註冊所處的階段進行分發,遞歸調用shouldSkip
if (phase == null) {
if (metadata instanceof AnnotationMetadata &&
ConfigurationClassUtils.isConfigurationCandidate((AnnotationMetadata) metadata)) {
return shouldSkip(metadata, ConfigurationPhase.PARSE_CONFIGURATION);
}
return shouldSkip(metadata, ConfigurationPhase.REGISTER_BEAN);
}
//從@Conditional中獲取並創建所有的Condition對象,放到conditions列表中
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);
}
}
//對Condition對象排序,因此用戶在實現Condition接口時也可以實現Order接口,維護Condition的順序
AnnotationAwareOrderComparator.sort(conditions);
//對每一個Condition對象進行校驗
for (Condition condition : conditions) {
ConfigurationPhase requiredPhase = null;
if (condition instanceof ConfigurationCondition) {
requiredPhase = ((ConfigurationCondition) condition).getConfigurationPhase();
}
if (requiredPhase == null || requiredPhase == phase) {
//這裏看到,對於所有Condition對象,只要有一個的matches方法校驗不成立,shouldSkip方法就返回true,
//這樣調用處就會跳過註冊
if (!condition.matches(this.context, metadata)) {
return true;
}
}
}
return false;
}
(2)ConfigurationClassPostProcessor後處理器工作階段
上述是說明的是@Configuration配置類如何被註冊到容器中,這個階段在容器refresh之前。
還有 @Import、@ComponentScan、@Bean 等這些註解導入的bean是怎麼處理註冊的呢,這是在第二個階段容器refresh過程中發生, 這個過程中會調用容器中所有的BeanFactoryPostProcessor,其中有一個叫做ConfigurationClassPostProcessor
的後處理器 會解析已註冊的@Configuration配置類,這個過程中會處理 @Import、@ComponentScan、@Bean 這些註解。
ConfigurationClassPostProcessor
實現的是BeanDefinitionRegistryPostProcessor
,因此可以在 postProcessBeanDefinitionRegistry方法中跟蹤是如何解析並註冊由@Configuration導入的那些bean的,重點關注在 ConfigurationClassBeanDefinitionReader#loadBeanDefinitions()
方法中對@Conditional的處理。
(3)ConfigurationClassPostProcessor本身的註冊機制
最後有一點,ConfigurationClassPostProcessor
這個後處理器本身是如何註冊到容器中的呢,它是Spring的一個內置後處理器, 在最開始創建AnnotationConfigApplicationContext的過程中,就在底層beanFacntory容器中註冊了,具體在構造方法創建 其內部的reader
對象的過程中。