1.SpringBoot中的共同點(diǎn)
?在springBoot中有很多這種標(biāo)簽@ConditionalOnXXX標(biāo)簽讓springBoot的代碼更加標(biāo)簽化配置更加靈活。這些標(biāo)簽都有共同點(diǎn),這里例舉兩個(gè)標(biāo)簽的源碼
1.1``@ConditionalOnXXX`
......
@Conditional({OnClassCondition.class})
public @interface ConditionalOnClass {
......
}
......
@Conditional({OnBeanCondition.class})
public @interface ConditionalOnBean {
......
}
?發(fā)現(xiàn)這些標(biāo)簽的共同點(diǎn)是上面都貼有@Conditional標(biāo)簽,然后在進(jìn)入到這個(gè)標(biāo)簽里面的值的類看看
class OnClassCondition extends FilteringSpringBootCondition {
......
}
class OnBeanCondition extends FilteringSpringBootCondition implements ConfigurationCondition {
......
}
?這里發(fā)現(xiàn)又有一個(gè)共同點(diǎn)這兩個(gè)類都是FilteringSpringBootCondition的子類,但是這個(gè)類是springBoot擴(kuò)展實(shí)現(xiàn)的我們要找的是源頭在spring中類,網(wǎng)上找可以發(fā)現(xiàn)這個(gè)類最后間接實(shí)現(xiàn)了spring的Condition。因此我們后面要找的就是Condition類。
2. Conditional標(biāo)簽的解析
2.1Condition介紹
?在Condition類中只有一個(gè)方法matches方法。這類的作用是,在bean的定義即將被注冊(cè)之前,會(huì)檢查條件是否匹配,然后根據(jù)匹配的結(jié)果決定是否注冊(cè)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,在源碼的類描述中提到了一點(diǎn)。如果實(shí)現(xiàn)的是這個(gè)類,那么必須注意,在實(shí)現(xiàn)方法也就是matches中不能與bean的實(shí)例交互。之所以要注意這一點(diǎn)是因?yàn)檫@個(gè)方法的調(diào)用時(shí)間在bean的實(shí)例化之前的,此時(shí)如果跟實(shí)例交互就會(huì)提前實(shí)例化bean,可能會(huì)引起錯(cuò)誤。如果想要對(duì)貼有@Configuration標(biāo)簽的bean更細(xì)粒度的控制可以通過實(shí)現(xiàn)ConfigurationCondition來完成。
2.2ConditionEvaluator類處理match方法
?通過查看Condition的matches在哪里被調(diào)用。發(fā)現(xiàn)整個(gè)spring中只有在ConditionEvaluator中調(diào)用了這個(gè)方法。這個(gè)類的作用是評(píng)估一個(gè)貼了Conditional注解的類是否需要跳過。通過類上面的注解來判斷。進(jìn)入到類方法
//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;
}
//如果沒有指定了當(dāng)前bean是解析還是注冊(cè)
if (phase == null) {
//bean的注解信息封裝對(duì)象是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的注解信息封裝對(duì)象中獲取所有的Conditional類型或者Conditional的派生注解
for (String[] conditionClasses : getConditionClasses(metadata)) {
for (String conditionClass : conditionClasses) {
//實(shí)例化Conditional中的條件判斷類(Condition的子類)
Condition condition = getCondition(conditionClass, this.context.getClassLoader());
//添加到條件集合中
conditions.add(condition);
}
}
//根據(jù)Condition的優(yōu)先級(jí)進(jìn)行排序
AnnotationAwareOrderComparator.sort(conditions);
for (Condition condition : conditions) {
ConfigurationPhase requiredPhase = null;
//如果是ConfigurationCondition類型的Condition
if (condition instanceof ConfigurationCondition) {
//獲取需要對(duì)bean進(jìn)行的操作,是解析還是注冊(cè)
requiredPhase = ((ConfigurationCondition) condition).getConfigurationPhase();
}
//(如果requiredPhase==null或者指定的操作類型是目前階段的操作類型)并且不符合設(shè)置的條件則跳過
if ((requiredPhase == null || requiredPhase == phase) && !condition.matches(this.context, metadata)) {
return true;
}
}
return false;
}
?上面這個(gè)方法作用就是判斷當(dāng)前bean處于解析還是注冊(cè),如果處于解析階段則跳過,如果處于注冊(cè)階段則不跳過。其中Condition的matches方法就起到了判斷的是否符合的作用,進(jìn)而覺得是否跳過當(dāng)前bean。
2.3 ConfigurationClassPostProcessor的processConfigBeanDefinitions
?還是通過查找ConditionEvaluator類的match方法調(diào)用鏈的方式,發(fā)現(xiàn)最后都是在ConfigurationClassPostProcessor的processConfigBeanDefinitions中進(jìn)行調(diào)用的。一共有兩個(gè)調(diào)用的位置,這里用調(diào)用的位置的代碼進(jìn)行展示
public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
List<BeanDefinitionHolder> configCandidates = new ArrayList<>();
//獲取registry中定義的所有的bean的name
String[] candidateNames = registry.getBeanDefinitionNames();
......
do {
//第一個(gè)會(huì)調(diào)用shouldSkip的位置,這里是解析能夠直接獲取的候選配置bean。可能是Component,ComponentScan,Import,ImportResource或者有Bean注解的bean
parser.parse(candidates);
parser.validate();
//獲取上面封裝已經(jīng)解析過的配置bean的ConfigurationClass集合
Set<ConfigurationClass> configClasses = new LinkedHashSet<>(parser.getConfigurationClasses());
//移除前面已經(jīng)處理過的
configClasses.removeAll(alreadyParsed);
//第二個(gè)會(huì)調(diào)用shouldSkip的位置,這里是加載configurationClasse中內(nèi)部可能存在配置bean,比如方法上加了@Bean或者@Configuration標(biāo)簽的bean
this.reader.loadBeanDefinitions(configClasses);
alreadyParsed.addAll(configClasses);
}
......
}
?這里的parse方法解析BeanDefinitionRegistry中能直接獲取到的候選bean,并解析保存到ConfigurationClassParser類的保存解析過的配置類的集合configurationClasses中。
?loadBeanDefinitions則是對(duì)上面解析的集合configurationClasses中的bean內(nèi)部的進(jìn)一步的處理,處理類內(nèi)部定義的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方法中有三個(gè)分支,分別是對(duì)不同類型的BeanDefinition進(jìn)行解析,這里進(jìn)入AnnotatedBeanDefinition類型的。
?進(jìn)入到parse方法后在進(jìn)入里面調(diào)用的processConfigurationClass方法,這里只需要分析開頭就知道了
protected void processConfigurationClass(ConfigurationClass configClass) throws IOException {
//檢查當(dāng)前解析的配置bean是否包含Conditional注解,如果不包含則不需要跳過
// 如果包含了則進(jìn)行match方法得到匹配結(jié)果,如果是符合的并且設(shè)置的配置解析策略是解析階段不需要調(diào)過
if (this.conditionEvaluator.shouldSkip(configClass.getMetadata(), ConfigurationPhase.PARSE_CONFIGURATION)) {
return;
}
......
//后面的步驟就是解析配置bean,然后進(jìn)行注冊(cè)的操作
}
?可以看到這里就是對(duì)是否跳過bean解析的位置。在這里Conditional標(biāo)簽的作用就完了。主要就是覺得當(dāng)前的配置bean是否符合我們規(guī)定的規(guī)則,不符合就不會(huì)注冊(cè)。
2.5ConfigurationClassBeanDefinitionReader的loadBeanDefinitions方法
?這里對(duì)上面已經(jīng)解析過的bean類集合的內(nèi)部進(jìn)行處理的步驟。是一個(gè)循環(huán)迭代處理的過程
public void loadBeanDefinitions(Set<ConfigurationClass> configurationModel) {
//對(duì)import標(biāo)簽處理的類
TrackedConditionEvaluator trackedConditionEvaluator = new TrackedConditionEvaluator();
//循環(huán)處理
for (ConfigurationClass configClass : configurationModel) {
loadBeanDefinitionsForConfigurationClass(configClass, trackedConditionEvaluator);
}
}
TrackedConditionEvaluator主要是處理@import標(biāo)簽同時(shí)也you的。舉個(gè)例子:如果A類通過@import類引入了另外的一個(gè)B類,如果A類需要跳過解析,那么B類也肯定需要調(diào)過解析。如果A類需要進(jìn)行解析,那么B類也需要進(jìn)行解析。后面會(huì)分析內(nèi)部的方法,現(xiàn)在進(jìn)入到loadBeanDefinitionsForConfigurationClass方法。
private void loadBeanDefinitionsForConfigurationClass(
ConfigurationClass configClass, TrackedConditionEvaluator trackedConditionEvaluator) {
//檢查當(dāng)前的bean是否是通過@import注解引入的,如果是的則循環(huán)解析到原始的貼有@import標(biāo)簽的bean,檢查是否有@conditionl標(biāo)簽并檢查是否需要跳過
if (trackedConditionEvaluator.shouldSkip(configClass)) {
//獲取當(dāng)前配置bean的beanName
String beanName = configClass.getBeanName();
//如果當(dāng)前beanName在BeanDefinitionRegistry中需要注冊(cè)的bean的列表中則移除,因?yàn)檫@個(gè)bean需要被跳過
if (StringUtils.hasLength(beanName) && this.registry.containsBeanDefinition(beanName)) {
this.registry.removeBeanDefinition(beanName);
}
//移除在ImportRegistry中imports列表中的該beanName,因?yàn)檫@個(gè)bean需要被跳過
this.importRegistry.removeImportingClass(configClass.getMetadata().getClassName());
return;
}
//如果當(dāng)前配置bean是通過@import注解進(jìn)行注入的則進(jìn)行注冊(cè)
if (configClass.isImported()) {
registerBeanDefinitionForImportedConfigurationClass(configClass);
}
//獲取當(dāng)前配置類的BeanMethod,就是在方法上面貼了@Bean注解的方法
for (BeanMethod beanMethod : configClass.getBeanMethods()) {
//進(jìn)行加載注入
loadBeanDefinitionsForBeanMethod(beanMethod);
}
//加載configClass中的配置的resource
loadBeanDefinitionsFromImportedResources(configClass.getImportedResources());
//加載configClass中的配置的ImportBeanDefinitionRegistrar
loadBeanDefinitionsFromRegistrars(configClass.getImportBeanDefinitionRegistrars());
}
?這里理解還是比較簡單的,就是判斷當(dāng)前的配置類是否需要跳過,因?yàn)楫?dāng)前的配置類也可能是通過@import標(biāo)簽進(jìn)行引入的,所以有必要進(jìn)行對(duì)最原始的進(jìn)行引入的類進(jìn)行分析,決定當(dāng)前的類是否需要跳過。如果需要調(diào)過,則一處對(duì)應(yīng)的需要進(jìn)行注冊(cè)bean列表中的改bean,如果不需要?jiǎng)t進(jìn)行注冊(cè)處理。這里關(guān)鍵在于當(dāng)前的bean是否需要調(diào)過,就在TrackedConditionEvaluator類的shouldSkip方法中,而這個(gè)類也只有這一個(gè)方法。這里比較難以理解,我也是看來半天才理解的??梢远嗫磶妆?,debug更好。
private class TrackedConditionEvaluator {
private final Map<ConfigurationClass, Boolean> skipped = new HashMap<>();
public boolean shouldSkip(ConfigurationClass configClass) {
//檢查當(dāng)前的配置類是否需要調(diào)過,因?yàn)檫@里是前面的直接獲取到的配置類,如果當(dāng)前類需要跳過那么,內(nèi)部的也必定需要跳過
//如果是null則說明這個(gè)類不是需要跳過的,但是也不代表是不需要跳過的,因?yàn)槿绻潜灰氲膭t決定于最外面的一層bean是否需要跳過
Boolean skip = this.skipped.get(configClass);
if (skip == null) {
//當(dāng)前的配置bean是不是通過@import注解引入的,不是的則不需要跳過,因?yàn)橹挥惺窃赽ean內(nèi)部定義的bean才需要判斷外面的一層bean是否需要跳過
if (configClass.isImported()) {
boolean allSkipped = true;
//獲取通過@import標(biāo)簽引入這個(gè)配置類的bean
for (ConfigurationClass importedBy : configClass.getImportedBy()) {
//檢查引入配置類的bean的是否需要跳過(這里一直會(huì)檢查到最終的引入類,來決定是否需要全部跳過)
if (!shouldSkip(importedBy)) {
allSkipped = false;
break;
}
}
//如果所有的bean(1.當(dāng)前的配置bean,2.引入當(dāng)前配置bean) 的bean 都是需要跳過的,則這個(gè)配置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對(duì)應(yīng)的@condition決定是否需要跳過,
if (skip == null) {
//這里就是對(duì)ConditionEvaluator方法的調(diào)用
skip = conditionEvaluator.shouldSkip(configClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN);
}
//將對(duì)應(yīng)的配置bean記錄起來是否需要跳過
this.skipped.put(configClass, skip);
}
return skip;
}
}
?其實(shí)最后目的還是很簡單的就是最終到最上級(jí)的bean,來決定當(dāng)前bean是否需要進(jìn)行注冊(cè)。前面舉得例子就是這個(gè)意思。
3. spring中的總結(jié)
?對(duì)于Conditional標(biāo)簽的解析上面就是全部的了。作用就是來決定貼了這個(gè)注解的bean,通過指定的Condition實(shí)現(xiàn)類實(shí)現(xiàn)matches方法來決定是否需要進(jìn)行解析,需要進(jìn)行注冊(cè)。代碼在調(diào)用鏈
AbstractApplicationContext類
public void refresh() throws BeansException, IllegalStateException {
.......
invokeBeanFactoryPostProcessors(beanFactory);
......
}
protected void invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory beanFactory) {
PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(beanFactory, getBeanFactoryPostProcessors());
......
}
?進(jìn)入到PostProcessorRegistrationDelegate類,在這個(gè)類的invokeBeanFactoryPostProcessors方法中會(huì)多次調(diào)用到ConfigurationClassPostProcessor的postProcessBeanDefinitionRegistry跟postProcessBeanFactory方法。在這兩個(gè)方法中都會(huì)調(diào)用上面講解的processConfigBeanDefinitions方法進(jìn)而處理Condition標(biāo)簽。
4.springboot中的擴(kuò)展
?在spring中實(shí)現(xiàn)Condition接口的類很少,在springboot中才廣泛的運(yùn)用到了。而springboot也在spring的基礎(chǔ)上做了一個(gè)基礎(chǔ)的擴(kuò)展實(shí)現(xiàn),然后再在這個(gè)基礎(chǔ)的擴(kuò)展實(shí)現(xiàn)上進(jìn)一步擴(kuò)展的。這個(gè)擴(kuò)展的實(shí)現(xiàn)類就是SpringBootCondition類?,F(xiàn)在進(jìn)入到這個(gè)類
public abstract class SpringBootCondition implements Condition {
@Override
public final boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
//從封裝condition注解信息的類中獲取指定的Condition類的實(shí)現(xiàn)類
String classOrMethodName = getClassOrMethodName(metadata);
try {
//確定匹配結(jié)果以及日志輸出對(duì)象
ConditionOutcome outcome = getMatchOutcome(context, metadata);
//打印匹配的情況,如果是不匹配會(huì)打印不匹配的原因
logOutcome(classOrMethodName, outcome);
//將匹配結(jié)果進(jìn)行存儲(chǔ)
recordEvaluation(context, classOrMethodName, outcome);
//返回匹配的結(jié)果
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);
}
?可以看到這里的擴(kuò)展的就是對(duì)匹配的結(jié)果進(jìn)行封裝,然后打印以及存儲(chǔ)。其中getMatchOutcome是由各個(gè)SpringBootCondition的實(shí)現(xiàn)類去實(shí)現(xiàn)的。作用就是判斷各自按照各自的使用條件來判斷是否符合來返回匹配的結(jié)果。