1.SpringBoot中的共同點
?在springBoot中有很多這種標簽@ConditionalOnXXX
標簽讓springBoot的代碼更加標簽化配置更加靈活息罗。這些標簽都有共同點轻局,這里例舉兩個標簽的源碼
1.1``@ConditionalOnXXX`
......
@Conditional({OnClassCondition.class})
public @interface ConditionalOnClass {
......
}
......
@Conditional({OnBeanCondition.class})
public @interface ConditionalOnBean {
......
}
?發(fā)現(xiàn)這些標簽的共同點是上面都貼有@Conditional
標簽梧却,然后在進入到這個標簽里面的值的類看看
class OnClassCondition extends FilteringSpringBootCondition {
......
}
class OnBeanCondition extends FilteringSpringBootCondition implements ConfigurationCondition {
......
}
?這里發(fā)現(xiàn)又有一個共同點這兩個類都是FilteringSpringBootCondition
的子類迹蛤,但是這個類是springBoot擴展實現(xiàn)的我們要找的是源頭在spring中類民珍,網上找可以發(fā)現(xiàn)這個類最后間接實現(xiàn)了spring的Condition
。因此我們后面要找的就是Condition
類盗飒。
2. Conditional
標簽的解析
2.1Condition
介紹
?在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
宣渗,在源碼的類描述中提到了一點抖所。如果實現(xiàn)的是這個類,那么必須注意落包,在實現(xiàn)方法也就是matches
中不能與bean的實例交互部蛇。之所以要注意這一點是因為這個方法的調用時間在bean的實例化之前的,此時如果跟實例交互就會提前實例化bean咐蝇,可能會引起錯誤涯鲁。如果想要對貼有@Configuration
標簽的bean更細粒度的控制可以通過實現(xiàn)ConfigurationCondition
來完成。
2.2ConditionEvaluator
類處理match
方法
?通過查看Condition
的matches
在哪里被調用有序。發(fā)現(xiàn)整個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的優(yōu)先級進行排序
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
?還是通過查找ConditionEvaluator
類的match
方法調用鏈的方式予弧,發(fā)現(xiàn)最后都是在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.4ConfigurationClassParser
的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是否符合我們規(guī)定的規(guī)則奢啥,不符合就不會注冊。
2.5ConfigurationClassBeanDefinitionReader
的loadBeanDefinitions
方法
?這里對上面已經解析過的bean類集合的內部進行處理的步驟嘴拢。是一個循環(huán)迭代處理的過程
public void loadBeanDefinitions(Set<ConfigurationClass> configurationModel) {
//對import標簽處理的類
TrackedConditionEvaluator trackedConditionEvaluator = new TrackedConditionEvaluator();
//循環(huán)處理
for (ConfigurationClass configClass : configurationModel) {
loadBeanDefinitionsForConfigurationClass(configClass, trackedConditionEvaluator);
}
}
TrackedConditionEvaluator
主要是處理@import
標簽同時也you的桩盲。舉個例子:如果A類通過@import
類引入了另外的一個B類,如果A類需要跳過解析席吴,那么B類也肯定需要調過解析赌结。如果A類需要進行解析捞蛋,那么B類也需要進行解析。后面會分析內部的方法柬姚,現(xiàn)在進入到loadBeanDefinitionsForConfigurationClass
方法襟交。
private void loadBeanDefinitionsForConfigurationClass(
ConfigurationClass configClass, TrackedConditionEvaluator trackedConditionEvaluator) {
//檢查當前的bean是否是通過@import注解引入的,如果是的則循環(huán)解析到原始的貼有@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
中的總結
?對于Conditional
標簽的解析上面就是全部的了引润。作用就是來決定貼了這個注解的bean,通過指定的Condition
實現(xiàn)類實現(xiàn)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中實現(xiàn)Condition
接口的類很少,在springboot中才廣泛的運用到了草讶。而springboot也在spring的基礎上做了一個基礎的擴展實現(xiàn)洽糟,然后再在這個基礎的擴展實現(xiàn)上進一步擴展的。這個擴展的實現(xiàn)類就是SpringBootCondition
類《檎剑現(xiàn)在進入到這個類
public abstract class SpringBootCondition implements Condition {
@Override
public final boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
//從封裝condition注解信息的類中獲取指定的Condition類的實現(xiàn)類
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
的實現(xiàn)類去實現(xiàn)的薪介。作用就是判斷各自按照各自的使用條件來判斷是否符合來返回匹配的結果。