注:该源码分析对应SpringBoot版本为2.1.0.RELEASEhtml
本篇接
如何分析SpringBoot源码模块及结构?--SpringBoot源码(二)java
上一篇分析了SpringBoot源码结构及各个模块pom之间的关系后,那么此篇开始就开始解开SpringBoot新特性之一--自动配置的神秘面纱了。由于SpringBoot自动配置原理是基于其大量的条件注解ConditionalOnXXX
,所以,本节咱们先来撸下Spring的条件注解的相关源码。linux
咱们都知道,SpringBoot自动配置是须要知足相应的条件才会自动配置,所以SpringBoot的自动配置大量应用了条件注解ConditionalOnXXX
。以下图:web
那么上图的条件注解如何使用呢?spring
举个栗子,咱们来看下如何使用
@ConditionalOnClass
和@ConditionalOnProperty
这两个注解,先看下图代码:HelloWorldEnableAutoConfiguration
这个自动配置类应用了@ConditionalOnClass
和ConditionalOnProperty
两个条件注解,那么只有在知足:classpath
中存在HelloWorldComponent.class
和配置了hello.world.name
和hello.world.age
属性这两个条件的状况下才会建立HelloWorldComponent
这个bean
。springboot
其实SpringBoot的@ConditionalOnXXX
等条件注解都是派生注解,那么什么是派生注解呢?
就拿上面的栗子来讲,以@ConditionalOnClass(HelloWorldComponent.class)
为例,咱们打开ConditionalOnClass
注解源码,以下:session
@Target({ ElementType.TYPE, ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) @Documented @Conditional(OnClassCondition.class) public @interface ConditionalOnClass { Class<?>[] value() default {}; String[] name() default {}; }
能够看到@ConditionalOnClass
注解上面又标注了@Conditional(OnClassCondition.class)
注解,所以@ConditionalOnClass
是@Conditional
的派生注解,@Conditional(OnClassCondition.class)
和@ConditionalOnClass
注解是等价的,即这两个注解标注在某个配置类上的效果是等价的。app
而SpringBoot的自动配置原理正是创建在这些大量的派生条件注解@ConditionalOnXXX
之上,而这些条件注解的原理跟Spring的Condition接口有关。所以咱们先来研究下Condition接口的相关源码。ide
分析Condition接口源码前先看下如何自定义ConditionalOnXXX
注解,举个栗子,好比自定义一个@ConditionalOnLinux
注解,该注解只有在其属性environment
是"linux"才会建立相关的bean。定义了如下代码:源码分析
/** * 实现spring 的Condition接口,而且重写matches()方法,若是@ConditionalOnLinux的注解属性environment是linux就返回true * */ public class LinuxCondition implements Condition { @Override public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { // 得到注解@ConditionalOnLinux的全部属性 List<AnnotationAttributes> allAnnotationAttributes = annotationAttributesFromMultiValueMap( metadata.getAllAnnotationAttributes( ConditionalOnLinux.class.getName())); for (AnnotationAttributes annotationAttributes : allAnnotationAttributes) { // 得到注解@ConditionalOnLinux的environment属性 String environment = annotationAttributes.getString("environment"); // 若environment等于linux,则返回true if ("linux".equals(environment)) { return true; } } return false; } }
@Target({ ElementType.TYPE, ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) @Documented @Conditional(LinuxCondition.class) public @interface ConditionalOnLinux { // 标注是哪一个环境 String environment() default ""; }
@Configuration public class ConditionConfig { // 只有`@ConditionalOnLinux`的注解属性`environment`是"linux"时才会建立bean @Bean @ConditionalOnLinux(environment = "linux") public Environment linuxEnvironment() { return new LinuxEnvironment(); } }
上面的代码咱们捋一下:
LinuxCondition
实现了Condition
接口并实现了matches
方法,而matches
方法则判断@ConditionalOnLinux
的注解属性environment
是否"linux",是则返回true,不然false。@ConditionalOnLinux
,这个注解是@Conditional
的派生注解,与@Conditional(LinuxCondition.class)
等价,注意@ConditionalOnLinux
注解定义了一个属性environment
。而咱们最终能够利用LinuxCondition
的matches
方法中的参数AnnotatedTypeMetadata
来获取@ConditionalOnLinux
的注解属性environment
的值,从而用来判断值是否为linux"。ConditionConfig
,在linuxEnvironment
方法上标注了@ConditionalOnLinux(environment = "linux")
。所以,这里只有 LinuxCondition
的matches
方法返回true才会建立bean
。学会了如何自定义@ConditionalOnXXX
注解后,咱们如今再来看下Condition
接口的源码:
@FunctionalInterface public interface Condition { boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata); }
Condition接口主要有一个matches
方法,该方法决定了是否要注册相应的bean
对象。其中matches
方法中有两个参数,参数类型分别是ConditionContext
和AnnotatedTypeMetadata
,这两个参数很是重要。它们分别用来获取一些环境信息和注解元数据从而用在matches
方法中判断是否符合条件。
ConditionContext
,顾名思义,主要是跟Condition
的上下文有关,主要用来获取Registry
,BeanFactory
,Environment
,ResourceLoader
和ClassLoader
等。那么获取这些用来干什么呢?举个栗子,好比OnResourceCondition
须要靠ConditionContext
来获取ResourceLoader
来加载指定资源,OnClassCondition
须要靠ConditionContext
来获取ClassLoader
来加载指定类等,下面看下其源码:public interface ConditionContext { BeanDefinitionRegistry getRegistry(); @Nullable ConfigurableListableBeanFactory getBeanFactory(); Environment getEnvironment(); ResourceLoader getResourceLoader(); @Nullable ClassLoader getClassLoader(); }
AnnotatedTypeMetadata
,这个跟注解元数据有关,利用AnnotatedTypeMetadata
能够拿到某个注解的一些元数据,而这些元数据就包含了某个注解里面的属性,好比前面的栗子,利用AnnotatedTypeMetadata
能够拿到@ConditionalOnLinux
的注解属性environment
的值。下面看下其源码:public interface AnnotatedTypeMetadata { boolean isAnnotated(String annotationName); Map<String, Object> getAnnotationAttributes(String annotationName); Map<String, Object> getAnnotationAttributes(String annotationName, boolean classValuesAsString); MultiValueMap<String, Object> getAllAnnotationAttributes(String annotationName); MultiValueMap<String, Object> getAllAnnotationAttributes(String annotationName, boolean classValuesAsString); }
回到刚才的栗子,咱们知道@ConditionalOnLinux
注解真正起做用的是Condition
接口的具体实现类LinuxCondition
的matches
方法,那么这个matches
方法是在什么时候被调用的呢?
经过idea调试看调用的栈帧,以下图:
发现是在ConditionEvaluator
的shouldSkip
方法中调用了LinuxCondition
的matches
方法,天然咱们再去看看ConditionEvaluator
的shouldSkip
的方法执行了什么逻辑。
// 这个方法主要是若是是解析阶段则跳过,若是是注册阶段则不跳过 public boolean shouldSkip(@Nullable AnnotatedTypeMetadata metadata, @Nullable ConfigurationPhase phase) { // 若没有被@Conditional或其派生注解所标注,则不会跳过 if (metadata == null || !metadata.isAnnotated(Conditional.class.getName())) { return false; } // 没有指定phase,注意phase能够分为PARSE_CONFIGURATION或REGISTER_BEAN类型 if (phase == null) { // 若标有@Component,@Import,@Bean或@Configuration等注解的话,则说明是PARSE_CONFIGURATION类型 if (metadata instanceof AnnotationMetadata && ConfigurationClassUtils.isConfigurationCandidate((AnnotationMetadata) metadata)) { return shouldSkip(metadata, ConfigurationPhase.PARSE_CONFIGURATION); } // 不然是REGISTER_BEAN类型 return shouldSkip(metadata, ConfigurationPhase.REGISTER_BEAN); } List<Condition> conditions = new ArrayList<>(); // TODO 得到全部标有@Conditional注解或其派生注解里面的Condition接口实现类并实例化成对象。 // 好比@Conditional(OnBeanCondition.class)则得到OnBeanCondition.class,OnBeanCondition.class每每实现了Condition接口 for (String[] conditionClasses : getConditionClasses(metadata)) { // 将类实例化成对象 for (String conditionClass : conditionClasses) { Condition condition = getCondition(conditionClass, this.context.getClassLoader()); conditions.add(condition); } } // 排序,即按照Condition的优先级进行排序 AnnotationAwareOrderComparator.sort(conditions); for (Condition condition : conditions) { ConfigurationPhase requiredPhase = null; if (condition instanceof ConfigurationCondition) { // 从condition中得到对bean是解析仍是注册 requiredPhase = ((ConfigurationCondition) condition).getConfigurationPhase(); } // 若requiredPhase为null或获取的阶段类型正是当前阶段类型且不符合condition的matches条件,则跳过 if ((requiredPhase == null || requiredPhase == phase) && !condition.matches(this.context, metadata)) { return true; } } return false; }
shouldSkip
这个方法执行的逻辑主要是若是是解析阶段则跳过,若是是注册阶段则不跳过;若是是在注册阶段即REGISTER_BEAN
阶段的话,此时会获得全部的Condition
接口的具体实现类并实例化这些实现类,而后再执行下面关键的代码进行判断是否须要跳过。
if ((requiredPhase == null || requiredPhase == phase) && !condition.matches(this.context, metadata)) { return true; }
上面代码最重要的逻辑是调用了Condition
接口的具体实现类的matches
方法,若matches
返回false
,则跳过,不进行注册bean
的操做;若matches
返回true
,则不跳过,进行注册bean
的操做;
好了,Condition
的源码分析就到此为止,再往上翻调用方法的话应该就是Spring加载bean
定义的相关源码了,不属于这里的分析范围。
前面咱们学会了如何自定义条件注解及Condition
的源码分析,那么咱们不由好奇,Spring究竟内置了哪些Condition
接口的实现类呢?
那么看下Spring的Condition
接口的具体实现类的类图:
发现Spring内置的Condition
接口的具体实现类虽然有多个,但只有ProfileCondition
不是测试相关的,所以能够说真正的内置的Condition
接口的具体实现类只有ProfileCondition
一个,很是很是少,这跟SpringBoot的大量派生条件注解造成了鲜明的对比。ProfileCondition
你们都知道,是跟环境有关,好比咱们平时通常有dev
,test
和prod
环境,而ProfileCondition
就是判断咱们项目配置了哪一个环境的。下面是ProfileCondition
的源码,很简单,这里就不分析了。
class ProfileCondition implements Condition { @Override public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { MultiValueMap<String, Object> attrs = metadata.getAllAnnotationAttributes(Profile.class.getName()); if (attrs != null) { for (Object value : attrs.get("value")) { if (context.getEnvironment().acceptsProfiles(Profiles.of((String[]) value))) { return true; } } return false; } return true; } }
前面看到Spring对Condition
的内置注解能够说只有ProfileCondition
一个,可是咱们都知道,SpringBoot则内置了大量的条件注解ConditionalOnXXX
。在分析前,咱们先来看一下SpringBootCondition
的总体类图来个总体的理解,以下图:
能够看到SpringBootCondition
做为SpringBoot条件注解的基类,处于整个类图的中心,它实现了Condition
接口,而后又有不少具体的子类OnXXXCondition
,这些OnXXXCondition
其实就是@ConditionalOnXXX
的条件类。
咱们先来看下SpringBootCondition
这个父类是主要作了哪些事情,抽象了哪些共有的逻辑?
SpringBootConditon
实现了Condition
接口,做为SpringBoot众多条件注解OnXXXCondtion
的父类,它的做用主要就是打印一些条件注解评估报告的日志,好比打印哪些配置类是符合条件注解的,哪些是不符合的。打印的日志形式以下图:
由于SpringBootConditon
实现了Condition
接口,也实现了matches
方法,所以该方法一样也是被ConditionEvaluator
的shouldSkip
方法中调用,所以咱们就以SpringBootConditon
的matches
方法为入口去进行分析。直接上代码:
// SpringBootCondition.java public final boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { // 获得metadata的类名或方法名 String classOrMethodName = getClassOrMethodName(metadata); try { // 判断每一个配置类的每一个条件注解@ConditionalOnXXX是否知足条件,而后记录到ConditionOutcome结果中 // 注意getMatchOutcome是一个抽象模板方法,交给OnXXXCondition子类去实现 ConditionOutcome outcome = getMatchOutcome(context, metadata); // 打印condition评估的日志,哪些条件注解@ConditionalOnXXX是知足条件的,哪些是不知足条件的,这些日志都打印出来 logOutcome(classOrMethodName, outcome); // 除了打印日志外,这些是否匹配的信息还要记录到ConditionEvaluationReport中 recordEvaluation(context, classOrMethodName, outcome); // 最后返回@ConditionalOnXXX是否知足条件 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); } }
上面代码的注释已经很是详细,咱们知道了SpringBootCondition
抽象了全部其具体实现类OnXXXCondition
的共有逻辑--condition
评估信息打印,最重要的是封装了一个模板方法getMatchOutcome(context, metadata)
,留给各个OnXXXCondition
具体子类去覆盖实现属于本身的判断逻辑,而后再返回相应的匹配结果给SpringBootCondition
用于日志打印。
所以咱们知道了SpringBootCondition
其实就是用来打印condition
评估信息的,对于其余枝节方法咱们没必要追究过深,省得丢了主线。咱们如今的重点是放在交给OnXXXCondition
子类实现的模板方法上getMatchOutcome(context, metadata);
,由于这个方法将会由不少OnXXXCondition
覆盖重写判断逻辑,这里是咱们接下来分析的重点。
由于SpringBootCondition
有众多具体实现类,下面只挑OnResourceCondition
,OnBeanCondition
和OnWebApplicationCondition
进行讲解,而AutoConfigurationImportFilter
跟自动配置有关,则留到自动配置源码解析的时候再进行分析。
如今先来看下一个逻辑及其简单的注解条件类OnResourceCondition
,OnResourceCondition
继承了SpringBootCondition
父类,覆盖了其getMatchOutcome
方法,用于@ConditionalOnResource
注解指定的资源存在与否。OnResourceCondition
的判断逻辑很是简单,主要拿到@ConditionalOnResource
注解指定的资源路径后,而后用ResourceLoader
根据指定路径去加载看资源存不存在。下面直接看代码:
先来看下@ConditionalOnResource
的代码,
@Target({ ElementType.TYPE, ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) @Documented @Conditional(OnResourceCondition.class) public @interface ConditionalOnResource { /** * The resources that must be present. * @return the resource paths that must be present. */ String[] resources() default {}; }
再来看OnResourceCondition
的代码:
@Order(Ordered.HIGHEST_PRECEDENCE + 20) class OnResourceCondition extends SpringBootCondition { private final ResourceLoader defaultResourceLoader = new DefaultResourceLoader(); @Override public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) { // 得到@ConditionalOnResource注解的属性元数据 MultiValueMap<String, Object> attributes = metadata .getAllAnnotationAttributes(ConditionalOnResource.class.getName(), true); // 得到资源加载器,若ConditionContext中有ResourceLoader则用ConditionContext中的,没有则用默认的 ResourceLoader loader = (context.getResourceLoader() != null) ? context.getResourceLoader() : this.defaultResourceLoader; List<String> locations = new ArrayList<>(); // 将@ConditionalOnResource中定义的resources属性值取出来装进locations集合 collectValues(locations, attributes.get("resources")); Assert.isTrue(!locations.isEmpty(), "@ConditionalOnResource annotations must specify at " + "least one resource location"); // missing集合是装不存在指定资源的资源路径的 List<String> missing = new ArrayList<>(); // 遍历全部的资源路径,若指定的路径的资源不存在则将其资源路径存进missing集合中 for (String location : locations) { // 这里针对有些资源路径是Placeholders的状况,即处理${} String resource = context.getEnvironment().resolvePlaceholders(location); if (!loader.getResource(resource).exists()) { missing.add(location); } } // 若是存在某个资源不存在,那么则报错 if (!missing.isEmpty()) { return ConditionOutcome.noMatch(ConditionMessage .forCondition(ConditionalOnResource.class) .didNotFind("resource", "resources").items(Style.QUOTE, missing)); } // 全部资源都存在,那么则返回能找到就提的资源 return ConditionOutcome .match(ConditionMessage.forCondition(ConditionalOnResource.class) .found("location", "locations").items(locations)); } // 将@ConditionalOnResource中定义的resources属性值取出来装进locations集合 private void collectValues(List<String> names, List<Object> values) { for (Object value : values) { for (Object item : (Object[]) value) { names.add((String) item); } } } }
能够看到OnResourceCondition
的getMatchOutcome
方法很是简单,这里再也不详述。
OnBeanCondition
一样继承了FilteringSpringBootCondition
父类,覆盖了父类FilteringSpringBootCondition
的getOutcomes
方法。而FilteringSpringBootCondition
又是SpringBootCondition
的子类,FilteringSpringBootCondition
跟自动配置类过滤有关,这里先不分析。值得注意的是OnBeanCondition
一样重写了SpringBootCondition
的getMatchOutcome
方法,用来判断Spring容器中是否存在指定条件的bean
。同时是OnBeanCondition
是@ConditionalOnBean
,@ConditionalOnSingleCandidate
和ConditionalOnMissingBean
的条件类。
一样,先来看OnBeanCondition
复写父类SpringBootCondition
的getMatchOutcome
方法的代码:
@Override public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) { ConditionMessage matchMessage = ConditionMessage.empty(); // (1),配置类(metadata)标注@ConditionalOnBean注解的状况 if (metadata.isAnnotated(ConditionalOnBean.class.getName())) { // 将@ConditionalOnBean注解属性封装进BeanSearchSpec对象中 // 注意BeanSearchSpec是一个静态内部类,用来存储@ConditionalOnBean和@ConditionalOnMissingBean注解的属性值 BeanSearchSpec spec = new BeanSearchSpec(context, metadata, ConditionalOnBean.class); // 调用getMatchingBeans获得符合条件的bean MatchResult matchResult = getMatchingBeans(context, spec); // 若是不匹配 if (!matchResult.isAllMatched()) { String reason = createOnBeanNoMatchReason(matchResult); return ConditionOutcome.noMatch(ConditionMessage .forCondition(ConditionalOnBean.class, spec).because(reason)); } // 若是匹配 matchMessage = matchMessage.andCondition(ConditionalOnBean.class, spec) .found("bean", "beans") .items(Style.QUOTE, matchResult.getNamesOfAllMatches()); } // (2),配置类(metadata)标注@ConditionalOnSingleCandidate注解的状况 if (metadata.isAnnotated(ConditionalOnSingleCandidate.class.getName())) { BeanSearchSpec spec = new SingleCandidateBeanSearchSpec(context, metadata, ConditionalOnSingleCandidate.class); MatchResult matchResult = getMatchingBeans(context, spec); if (!matchResult.isAllMatched()) { return ConditionOutcome.noMatch(ConditionMessage .forCondition(ConditionalOnSingleCandidate.class, spec) .didNotFind("any beans").atAll()); } else if (!hasSingleAutowireCandidate(context.getBeanFactory(), matchResult.getNamesOfAllMatches(), spec.getStrategy() == SearchStrategy.ALL)) { return ConditionOutcome.noMatch(ConditionMessage .forCondition(ConditionalOnSingleCandidate.class, spec) .didNotFind("a primary bean from beans") .items(Style.QUOTE, matchResult.getNamesOfAllMatches())); } matchMessage = matchMessage .andCondition(ConditionalOnSingleCandidate.class, spec) .found("a primary bean from beans") .items(Style.QUOTE, matchResult.getNamesOfAllMatches()); } // (3),配置类(metadata)标注@ConditionalOnMissingBean注解的状况 if (metadata.isAnnotated(ConditionalOnMissingBean.class.getName())) { BeanSearchSpec spec = new BeanSearchSpec(context, metadata, ConditionalOnMissingBean.class); MatchResult matchResult = getMatchingBeans(context, spec); if (matchResult.isAnyMatched()) { String reason = createOnMissingBeanNoMatchReason(matchResult); return ConditionOutcome.noMatch(ConditionMessage .forCondition(ConditionalOnMissingBean.class, spec) .because(reason)); } matchMessage = matchMessage.andCondition(ConditionalOnMissingBean.class, spec) .didNotFind("any beans").atAll(); } // 最终返回matchMessage return ConditionOutcome.match(matchMessage); }
咱们能够看到OnBeanCondition
类覆盖的getMatchOutcome
方法分别处理了标注@ConditionalOnBean
,@ConditionalOnSingleCandidate
和@ConditionalOnMissingBean
注解的状况,分别对应上面代码注释的(1)
,(2)
和(3)
处。
如今咱们只看针对@ConditionalOnBean
注解的处理逻辑,从上面代码中能够看到若配置类(metadata)标注@ConditionalOnBean
注解的话,主要作了如下事情:
BeanSearchSpec
对象中;getMatchingBeans(context, spec)
方法来获取是否有匹配的bean
;bean
的匹配状况;能够看到最重要的逻辑是第2步,那么咱们再来看下getMatchingBeans
方法,直接上代码:
protected final MatchResult getMatchingBeans(ConditionContext context, BeanSearchSpec beans) { // 得到Spring容器的beanFactory ConfigurableListableBeanFactory beanFactory = context.getBeanFactory(); // 判断bean的搜索策略是不是SearchStrategy.ANCESTORS策略 if (beans.getStrategy() == SearchStrategy.ANCESTORS) { BeanFactory parent = beanFactory.getParentBeanFactory(); Assert.isInstanceOf(ConfigurableListableBeanFactory.class, parent, "Unable to use SearchStrategy.PARENTS"); beanFactory = (ConfigurableListableBeanFactory) parent; } // MatchResult用来存储bean的匹配结果 MatchResult matchResult = new MatchResult(); // 若是bean的搜索策略不是SearchStrategy.CURRENT的话,则置considerHierarchy为true boolean considerHierarchy = beans.getStrategy() != SearchStrategy.CURRENT; // 获取TypeExtractor,TypeExtractor是用来判断bean的类型的 TypeExtractor typeExtractor = beans.getTypeExtractor(context.getClassLoader()); // 获取是否有被忽略bean类型,如有的话将该bean类型的名称装进beansIgnoredByType集合 // 这里主要是针对@ConditionalOnMissingBean的ignored属性 List<String> beansIgnoredByType = getNamesOfBeansIgnoredByType( beans.getIgnoredTypes(), typeExtractor, beanFactory, context, considerHierarchy); // 遍历bean的全部类型 for (String type : beans.getTypes()) { // 调用getBeanNamesForType方法根据bean类型获得全部符合条件的bean类型,并放到typeMatches集合 Collection<String> typeMatches = getBeanNamesForType(beanFactory, type, typeExtractor, context.getClassLoader(), considerHierarchy); // 移除掉Ignored的类型 typeMatches.removeAll(beansIgnoredByType); // 若typeMatches为空,那么则说明正在遍历的这个type类型不符合匹配条件,此时用matchResult记录一下这个不符合条件的类型 if (typeMatches.isEmpty()) { matchResult.recordUnmatchedType(type); } // 若typeMatches不为空,那么则说明正在遍历的这个type类型符合匹配条件,此时用matchResult记录一下这个符合条件的类型 else { matchResult.recordMatchedType(type, typeMatches); } } // 这里针对@ConditionalOnBean等注解的annotation属性的处理 for (String annotation : beans.getAnnotations()) { List<String> annotationMatches = Arrays .asList(getBeanNamesForAnnotation(beanFactory, annotation, context.getClassLoader(), considerHierarchy)); annotationMatches.removeAll(beansIgnoredByType); if (annotationMatches.isEmpty()) { matchResult.recordUnmatchedAnnotation(annotation); } else { matchResult.recordMatchedAnnotation(annotation, annotationMatches); } } // 这里针对@ConditionalOnBean等注解的name属性的处理 for (String beanName : beans.getNames()) { // beansIgnoredByType集合不包含beanName且beanFactory包含这个bean,则匹配 if (!beansIgnoredByType.contains(beanName) && containsBean(beanFactory, beanName, considerHierarchy)) { matchResult.recordMatchedName(beanName); } // 不然,不匹配 else { matchResult.recordUnmatchedName(beanName); } } // 最后返回匹配结果 return matchResult; }
上面的逻辑主要是从spring容器中搜索有无指定条件的bean
,搜索Spring容器搜索bean的话有三种搜索策略,分别是CURRENT
,ANCESTORS
和ALL
,分表表示只从当前的context
中搜索bean
,只从父context
中搜索bean
和从整个context
中搜索bean
;定义了搜索策略后,而后再根据BeanSearchSpec
对象封装的注解属性分别取指定的容器中查找有无符合条件的bean
,而后再进行一些过滤。好比@ConditionalOnMissingBean
注解有定义ignored
属性值,那么从容器中搜索到有符合条件的bean
时,此时还要移除掉ignored
指定的bean
。
好了,上面就已经分析了OnBeanCondition
这个条件类了,咱们坚持主线优先的原则,具体的细节代码不会深究。
OnWebApplicationCondition
一样继承了FilteringSpringBootCondition
父类,覆盖了父类FilteringSpringBootCondition
的getOutcomes
方法。而FilteringSpringBootCondition
又是SpringBootCondition
的子类,FilteringSpringBootCondition
跟自动配置类过滤有关,这里先不分析。值得注意的是OnWebApplicationCondition
一样重写了SpringBootCondition
的getMatchOutcome
方法,用来判断当前应用是否web应用。同时是OnWebApplicationCondition
是@ConditionalOnWebApplication
的条件类。
一样,先来看OnWebApplicationCondition
重写SpringBootCondition
的getMatchOutcome
方法:
public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) { // 配置类是否标注有@ConditionalOnWebApplication注解 boolean required = metadata .isAnnotated(ConditionalOnWebApplication.class.getName()); // 调用isWebApplication方法返回匹配结果 ConditionOutcome outcome = isWebApplication(context, metadata, required); // 如有标注@ConditionalOnWebApplication但不符合条件,则返回不匹配 if (required && !outcome.isMatch()) { return ConditionOutcome.noMatch(outcome.getConditionMessage()); } // 若没有标注@ConditionalOnWebApplication但符合条件,则返回不匹配 if (!required && outcome.isMatch()) { return ConditionOutcome.noMatch(outcome.getConditionMessage()); } // 这里返回匹配的状况,TODO 不过有个疑问:若是没有标注@ConditionalOnWebApplication注解,又不符合条件的话,也会执行到这里,返回匹配? return ConditionOutcome.match(outcome.getConditionMessage()); }
上面代码的逻辑很简单,主要是调用isWebApplication
方法来判断当前应用是不是web应用。所以,咱们再来看下isWebApplication
方法:
private ConditionOutcome isWebApplication(ConditionContext context, AnnotatedTypeMetadata metadata, boolean required) { // 调用deduceType方法判断是哪一种类型,其中有SERVLET,REACTIVE和ANY类型,其中ANY表示了SERVLET或REACTIVE类型 switch (deduceType(metadata)) { // SERVLET类型 case SERVLET: return isServletWebApplication(context); // REACTIVE类型 case REACTIVE: return isReactiveWebApplication(context); default: return isAnyWebApplication(context, required); } }
在isWebApplication
方法中,首先从@ConditionalOnWebApplication
注解中获取其定义了什么类型,而后根据不一样的类型进入不一样的判断逻辑。这里咱们只看下SERVLET
的状况判断处理,看代码:
private ConditionOutcome isServletWebApplication(ConditionContext context) { ConditionMessage.Builder message = ConditionMessage.forCondition(""); // 若classpath中不存在org.springframework.web.context.support.GenericWebApplicationContext.class,则返回不匹配 if (!ClassNameFilter.isPresent(SERVLET_WEB_APPLICATION_CLASS, context.getClassLoader())) { return ConditionOutcome.noMatch( message.didNotFind("servlet web application classes").atAll()); } // 若classpath中存在org.springframework.web.context.support.GenericWebApplicationContext.class,那么又分为如下几种匹配的状况 // session if (context.getBeanFactory() != null) { String[] scopes = context.getBeanFactory().getRegisteredScopeNames(); if (ObjectUtils.containsElement(scopes, "session")) { return ConditionOutcome.match(message.foundExactly("'session' scope")); } } // ConfigurableWebEnvironment if (context.getEnvironment() instanceof ConfigurableWebEnvironment) { return ConditionOutcome .match(message.foundExactly("ConfigurableWebEnvironment")); } // WebApplicationContext if (context.getResourceLoader() instanceof WebApplicationContext) { return ConditionOutcome.match(message.foundExactly("WebApplicationContext")); } // 若以上三种都不匹配的话,则说明不是一个servlet web application return ConditionOutcome.noMatch(message.because("not a servlet web application")); }
对因而SERVLET
的状况,首先根据classpath
中是否存在org.springframework.web.context.support.GenericWebApplicationContext.class
,若是不存在该类,则直接返回不匹配;若存在的话那么又分为如下几种匹配的状况:
若上面三种状况都不匹配,则说明不是一个servlet web application。
因为springboot的OnXXXCondition
类实现太多,不可能每一个条件类都分析一遍,所以上面只分析了OnResourceCondition
,OnBeanCondition
和onWebApplicationCondition
的源码。咱们分析源码不可能把全部代码都通读一遍的,阅读源码的话,只要理解了某个模块的类之间的关系及挑几个有表明性的类分析下就行,不可能一网打尽。
如有时间的话,推荐看下几个咱们经常使用的条件类的源码:OnPropertyCondition
,OnClassCondition
和OnExpressionCondition
等。
前文咱们知道了如何扩展Spring的Condition
接口,那么咱们该如何扩展SpringBoot的SpringBootCondition
类呢?
推荐阅读springboot之使用SpringBootCondition得到答案
好了,本篇文章是SpringBoot自动配置源码分析的前置文章,这里分析了条件注解源码,那么下篇文章咱们就来看看SpringBoot自动配置的源码了。
下节预告:
<font color=Blue>SpringBoot新特性:SpringBoot是如何自动配置的?--SpringBoot源码(四)</font>
原创不易,帮忙点个赞呗!
参考:
3,spring boot 系列之六:深刻理解spring boot的自动配置