1.SpringBoot中的共同點
在springBoot中有很多這種标簽
@ConditionalOnXXX
标簽讓springBoot的代碼更加标簽化配置更加靈活。這些标簽都有共同點,這裡例舉兩個标簽的源碼
1.1``@ConditionalOnXXX`
......
@Conditional({OnClassCondition.class})
public @interface ConditionalOnClass {
......
}
......
@Conditional({OnBeanCondition.class})
public @interface ConditionalOnBean {
......
}
發現這些标簽的共同點是上面都貼有
@Conditional
标簽,然後在進入到這個标簽裡面的值的類看看
class OnClassCondition extends FilteringSpringBootCondition {
......
}
class OnBeanCondition extends FilteringSpringBootCondition implements ConfigurationCondition {
......
}
這裡發現又有一個共同點這兩個類都是
FilteringSpringBootCondition
的子類,但是這個類是springBoot擴充實作的我們要找的是源頭在spring中類,網上找可以發現這個類最後間接實作了spring的
Condition
。是以我們後面要找的就是
Condition
類。
2. Conditional
标簽的解析
Conditional
2.1 Condition
介紹
Condition
在
Condition
類中隻有一個方法
matches
方法。這類的作用是,在bean的定義即将被注冊之前,會檢查條件是否比對,然後根據比對的結果決定是否注冊bean。
/**
* 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
*/
@FunctionalInterface
public interface Condition {
boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);
}
檢查條件是否比對的方法就是
matches
,在源碼的類描述中提到了一點。如果實作的是這個類,那麼必須注意,在實作方法也就是
matches
中不能與bean的執行個體互動。之是以要注意這一點是因為這個方法的調用時間在bean的執行個體化之前的,此時如果跟執行個體互動就會提前執行個體化bean,可能會引起錯誤。如果想要對貼有
@Configuration
标簽的bean更細粒度的控制可以通過實作
ConfigurationCondition
來完成。
2.2 ConditionEvaluator
類處理 match
方法
ConditionEvaluator
match
通過檢視
Condition
的
matches
在哪裡被調用。發現整個spring中隻有在
ConditionEvaluator
中調用了這個方法。這個類的作用是評估一個貼了
Conditional
注解的類是否需要跳過。通過類上面的注解來判斷。進入到類方法
//metadata是AnnotationMetadataReadingVisitor類型的,在5.2版本被SimpleAnnotationMetadataReadingVisitor代替
public boolean shouldSkip(@Nullable AnnotatedTypeMetadata metadata, @Nullable ConfigurationPhase phase) {
//檢查注解中是否包含@Conditional類型的注解
if (metadata == null || !metadata.isAnnotated(Conditional.class.getName())) {
return false;
}
//如果沒有指定了目前bean是解析還是注冊
if (phase == null) {
//bean的注解資訊封裝對象是AnnotationMetadata類型并且,類上有@Component,@ComponentScan,@Import,@ImportResource,則表示為解析類型
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<>();
//從bean的注解資訊封裝對象中擷取所有的Conditional類型或者Conditional的派生注解
for (String[] conditionClasses : getConditionClasses(metadata)) {
for (String conditionClass : conditionClasses) {
//執行個體化Conditional中的條件判斷類(Condition的子類)
Condition condition = getCondition(conditionClass, this.context.getClassLoader());
//添加到條件集合中
conditions.add(condition);
}
}
//根據Condition的優先級進行排序
AnnotationAwareOrderComparator.sort(conditions);
for (Condition condition : conditions) {
ConfigurationPhase requiredPhase = null;
//如果是ConfigurationCondition類型的Condition
if (condition instanceof ConfigurationCondition) {
//擷取需要對bean進行的操作,是解析還是注冊
requiredPhase = ((ConfigurationCondition) condition).getConfigurationPhase();
}
//(如果requiredPhase==null或者指定的操作類型是目前階段的操作類型)并且不符合設定的條件則跳過
if ((requiredPhase == null || requiredPhase == phase) && !condition.matches(this.context, metadata)) {
return true;
}
}
return false;
}
上面這個方法作用就是判斷目前bean處于解析還是注冊,如果處于解析階段則跳過,如果處于注冊階段則不跳過。其中
Condition
的
matches
方法就起到了判斷的是否符合的作用,進而覺得是否跳過目前bean。
2.3 ConfigurationClassPostProcessor
的 processConfigBeanDefinitions
ConfigurationClassPostProcessor
processConfigBeanDefinitions
還是通過查找
ConditionEvaluator
類的
match
方法調用鍊的方式,發現最後都是在
ConfigurationClassPostProcessor
的
processConfigBeanDefinitions
中進行調用的。一共有兩個調用的位置,這裡用調用的位置的代碼進行展示
public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
List<BeanDefinitionHolder> configCandidates = new ArrayList<>();
//擷取registry中定義的所有的bean的name
String[] candidateNames = registry.getBeanDefinitionNames();
......
do {
//第一個會調用shouldSkip的位置,這裡是解析能夠直接擷取的候選配置bean。可能是Component,ComponentScan,Import,ImportResource或者有Bean注解的bean
parser.parse(candidates);
parser.validate();
//擷取上面封裝已經解析過的配置bean的ConfigurationClass集合
Set<ConfigurationClass> configClasses = new LinkedHashSet<>(parser.getConfigurationClasses());
//移除前面已經處理過的
configClasses.removeAll(alreadyParsed);
//第二個會調用shouldSkip的位置,這裡是加載configurationClasse中内部可能存在配置bean,比如方法上加了@Bean或者@Configuration标簽的bean
this.reader.loadBeanDefinitions(configClasses);
alreadyParsed.addAll(configClasses);
}
......
}
這裡的
parse
方法解析
BeanDefinitionRegistry
中能直接擷取到的候選bean,并解析儲存到
ConfigurationClassParser
類的儲存解析過的配置類的集合
configurationClasses
中。
loadBeanDefinitions
則是對上面解析的集合
configurationClasses
中的bean内部的進一步的處理,處理類内部定義的bean。
2.4 ConfigurationClassParser
的 parse
方法
ConfigurationClassParser
parse
public void parse(Set<BeanDefinitionHolder> configCandidates) {
......
try {
if (bd instanceof AnnotatedBeanDefinition) {
parse(((AnnotatedBeanDefinition) bd).getMetadata(), holder.getBeanName());
}
}
......
}
protected final void parse(AnnotationMetadata metadata, String beanName) throws IOException {
processConfigurationClass(new ConfigurationClass(metadata, beanName));
}
在
ConfigurationClassParser
的
parse
方法中有三個分支,分别是對不同類型的
BeanDefinition
進行解析,這裡進入
AnnotatedBeanDefinition
類型的。
進入到
parse
方法後在進入裡面調用的
processConfigurationClass
方法,這裡隻需要分析開頭就知道了
protected void processConfigurationClass(ConfigurationClass configClass) throws IOException {
//檢查目前解析的配置bean是否包含Conditional注解,如果不包含則不需要跳過
// 如果包含了則進行match方法得到比對結果,如果是符合的并且設定的配置解析政策是解析階段不需要調過
if (this.conditionEvaluator.shouldSkip(configClass.getMetadata(), ConfigurationPhase.PARSE_CONFIGURATION)) {
return;
}
......
//後面的步驟就是解析配置bean,然後進行注冊的操作
}
可以看到這裡就是對是否跳過bean解析的位置。在這裡
Conditional
标簽的作用就完了。主要就是覺得目前的配置bean是否符合我們規定的規則,不符合就不會注冊。
2.5 ConfigurationClassBeanDefinitionReader
的 loadBeanDefinitions
方法
ConfigurationClassBeanDefinitionReader
loadBeanDefinitions
這裡對上面已經解析過的bean類集合的内部進行處理的步驟。是一個循環疊代處理的過程
public void loadBeanDefinitions(Set<ConfigurationClass> configurationModel) {
//對import标簽處理的類
TrackedConditionEvaluator trackedConditionEvaluator = new TrackedConditionEvaluator();
//循環處理
for (ConfigurationClass configClass : configurationModel) {
loadBeanDefinitionsForConfigurationClass(configClass, trackedConditionEvaluator);
}
}
TrackedConditionEvaluator
主要是處理
@import
标簽同時也you的。舉個例子:如果A類通過
@import
類引入了另外的一個B類,如果A類需要跳過解析,那麼B類也肯定需要調過解析。如果A類需要進行解析,那麼B類也需要進行解析。後面會分析内部的方法,現在進入到
loadBeanDefinitionsForConfigurationClass
方法。
private void loadBeanDefinitionsForConfigurationClass(
ConfigurationClass configClass, TrackedConditionEvaluator trackedConditionEvaluator) {
//檢查目前的bean是否是通過@import注解引入的,如果是的則循環解析到原始的貼有@import标簽的bean,檢查是否有@conditionl标簽并檢查是否需要跳過
if (trackedConditionEvaluator.shouldSkip(configClass)) {
//擷取目前配置bean的beanName
String beanName = configClass.getBeanName();
//如果目前beanName在BeanDefinitionRegistry中需要注冊的bean的清單中則移除,因為這個bean需要被跳過
if (StringUtils.hasLength(beanName) && this.registry.containsBeanDefinition(beanName)) {
this.registry.removeBeanDefinition(beanName);
}
//移除在ImportRegistry中imports清單中的該beanName,因為這個bean需要被跳過
this.importRegistry.removeImportingClass(configClass.getMetadata().getClassName());
return;
}
//如果目前配置bean是通過@import注解進行注入的則進行注冊
if (configClass.isImported()) {
registerBeanDefinitionForImportedConfigurationClass(configClass);
}
//擷取目前配置類的BeanMethod,就是在方法上面貼了@Bean注解的方法
for (BeanMethod beanMethod : configClass.getBeanMethods()) {
//進行加載注入
loadBeanDefinitionsForBeanMethod(beanMethod);
}
//加載configClass中的配置的resource
loadBeanDefinitionsFromImportedResources(configClass.getImportedResources());
//加載configClass中的配置的ImportBeanDefinitionRegistrar
loadBeanDefinitionsFromRegistrars(configClass.getImportBeanDefinitionRegistrars());
}
這裡了解還是比較簡單的,就是判斷目前的配置類是否需要跳過,因為目前的配置類也可能是通過
@import
标簽進行引入的,是以有必要進行對最原始的進行引入的類進行分析,決定目前的類是否需要跳過。如果需要調過,則一處對應的需要進行注冊bean清單中的改bean,如果不需要則進行注冊處理。這裡關鍵在于目前的bean是否需要調過,就在
TrackedConditionEvaluator
類的
shouldSkip
方法中,而這個類也隻有這一個方法。這裡比較難以了解,我也是看來半天才了解的。可以多看幾遍,debug更好。
private class TrackedConditionEvaluator {
private final Map<ConfigurationClass, Boolean> skipped = new HashMap<>();
public boolean shouldSkip(ConfigurationClass configClass) {
//檢查目前的配置類是否需要調過,因為這裡是前面的直接擷取到的配置類,如果目前類需要跳過那麼,内部的也必定需要跳過
//如果是null則說明這個類不是需要跳過的,但是也不代表是不需要跳過的,因為如果是被引入的則決定于最外面的一層bean是否需要跳過
Boolean skip = this.skipped.get(configClass);
if (skip == null) {
//目前的配置bean是不是通過@import注解引入的,不是的則不需要跳過,因為隻有是在bean内部定義的bean才需要判斷外面的一層bean是否需要跳過
if (configClass.isImported()) {
boolean allSkipped = true;
//擷取通過@import标簽引入這個配置類的bean
for (ConfigurationClass importedBy : configClass.getImportedBy()) {
//檢查引入配置類的bean的是否需要跳過(這裡一直會檢查到最終的引入類,來決定是否需要全部跳過)
if (!shouldSkip(importedBy)) {
allSkipped = false;
break;
}
}
//如果所有的bean(1.目前的配置bean,2.引入目前配置bean) 的bean 都是需要跳過的,則這個配置bean需要跳過
if (allSkipped) {
// The config classes that imported this one were all skipped, therefore we are skipped...
skip = true;
}
}
//能夠到這一步的是最層的bean,例如A引入了B,B引入了C,那麼A就是最外層的bean,檢查A對應的@condition決定是否需要跳過,
if (skip == null) {
//這裡就是對ConditionEvaluator方法的調用
skip = conditionEvaluator.shouldSkip(configClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN);
}
//将對應的配置bean記錄起來是否需要跳過
this.skipped.put(configClass, skip);
}
return skip;
}
}
其實最後目的還是很簡單的就是最終到最上級的bean,來決定目前bean是否需要進行注冊。前面舉得例子就是這個意思。
3. spring
中的總結
spring
對于
Conditional
标簽的解析上面就是全部的了。作用就是來決定貼了這個注解的bean,通過指定的
Condition
實作類實作
matches
方法來決定是否需要進行解析,需要進行注冊。代碼在調用鍊
AbstractApplicationContext
類
public void refresh() throws BeansException, IllegalStateException {
.......
invokeBeanFactoryPostProcessors(beanFactory);
......
}
protected void invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory beanFactory) {
PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(beanFactory, getBeanFactoryPostProcessors());
......
}
進入到
PostProcessorRegistrationDelegate
類,在這個類的
invokeBeanFactoryPostProcessors
方法中會調用到
ConfigurationClassPostProcessor
的
postProcessBeanDefinitionRegistry
跟
postProcessBeanFactory
方法。在這兩個方法中都會調用上面講解的
processConfigBeanDefinitions
方法進而處理
Condition
标簽。
4.springboot中的擴充
在spring中實作
Condition
接口的類很少,在springboot中才廣泛的運用到了。而springboot也在spring的基礎上做了一個基礎的擴充實作,然後再在這個基礎的擴充實作上進一步擴充的。這個擴充的實作類就是
SpringBootCondition
類。現在進入到這個類
public abstract class SpringBootCondition implements Condition {
@Override
public final boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
//從封裝condition注解資訊的類中擷取指定的Condition類的實作類
String classOrMethodName = getClassOrMethodName(metadata);
try {
//确定比對結果以及日志輸出對象
ConditionOutcome outcome = getMatchOutcome(context, metadata);
//列印比對的情況,如果是不比對會列印不比對的原因
logOutcome(classOrMethodName, outcome);
//将比對結果進行存儲
recordEvaluation(context, classOrMethodName, outcome);
//傳回比對的結果
return outcome.isMatch();
}
catch (NoClassDefFoundError ex) {
throw new IllegalStateException("Could not evaluate condition on " + classOrMethodName + " due to "
+ ex.getMessage() + " not found. Make sure your own configuration does not rely on "
+ "that class. This can also happen if you are "
+ "@ComponentScanning a springframework package (e.g. if you "
+ "put a @ComponentScan in the default package by mistake)", ex);
}
catch (RuntimeException ex) {
throw new IllegalStateException("Error processing condition on " + getName(metadata), ex);
}
}
public abstract ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata);
}
可以看到這裡的擴充的就是對比對的結果進行封裝,然後列印以及存儲。其中
getMatchOutcome
是由各個
SpringBootCondition
的實作類去實作的。作用就是判斷各自按照各自的使用條件來判斷是否符合來傳回比對的結果。