1.SpringBoot 内置条件注解 SpringBoot自动配置是需要满足相应的条件才会自动配置,因此SpringBoot的自动配置大量应用了条件注解ConditionalOnXXX。
@ConditionalOnBean:当SpringIoc容器内存在指定Bean的条件
@ConditionalOnClass:当SpringIoc容器内存在指定Class的条件
@ConditionalOnExpression:基于SpEL表达式作为判断条件
@ConditionalOnJava:基于JVM版本作为判断条件
@ConditionalOnJndi:在JNDI存在时查找指定的位置
@ConditionalOnMissingBean:当SpringIoc容器内不存在指定Bean的条件
@ConditionalOnMissingClass:当SpringIoc容器内不存在指定Class的条件
@ConditionalOnNotWebApplication:当前项目不是Web项目的条件
@ConditionalOnProperty:指定的属性是否有指定的值
@ConditionalOnResource:类路径是否有指定的值
@ConditionalOnSingleCandidate:当指定Bean在SpringIoc容器内只有一个,或者虽然有多个但是指定首选的Bean
@ConditionalOnWebApplication:当前项目是Web项目的条件
SpringBoot的@ConditionalOnXXX等条件注解都是派生注解,SpringBoot的自动配置原理是建立在大量的派生条件注解@ConditionalOnXXX之上,而这些条件注解的原理跟Spring的Condition接口有关。因此接下来先来看看Condition接口的相关源码。
2.Condition接口 2.1 Condition接口demo 比如自定义一个@ConditionalOnLinux注解,该注解只有在其属性environment是”linux”才会创建相关的bean。
LinuxCondition.java:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 public class LinuxCondition implements Condition { @Override public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { List<AnnotationAttributes> allAnnotationAttributes = annotationAttributesFromMultiValueMap( metadata.getAllAnnotationAttributes(ConditionalOnLinux.class.getName())); for (AnnotationAttributes annotationAttributes : allAnnotationAttributes) { String environment = annotationAttributes.getString("environment" ); if ("linux" .equals(environment)) { return true ; } } return false ; } private List<AnnotationAttributes> annotationAttributesFromMultiValueMap( MultiValueMap<String , Object > multiValueMap) { List<Map<String , Object >> maps = new ArrayList<>(); multiValueMap.forEach((key , value) -> { for (int i = 0 ; i < value.size (); i++) { Map<String , Object > map ; if (i < maps.size ()) { map = maps.get (i); } else { map = new HashMap <>(); maps.add (map ); } map .put(key , value.get (i)); } }); List<AnnotationAttributes> annotationAttributes = new ArrayList<>(maps.size ()); for (Map<String , Object > map : maps) { annotationAttributes.add (AnnotationAttributes.fromMap(map )); } return annotationAttributes; } }
ConditionalOnLinux.java
1 2 3 4 5 6 7 8 9 @Target({ ElementType.TYPE, ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) @Documented @Conditional(LinuxCondition.class) public @interface ConditionalOnLinux { String environment () default "" ; }
ConditionConfig.java
1 2 3 4 5 6 7 8 9 @Configuration public class ConditionConfig { @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。
2.2 Condition接口源码分析 Condition接口的源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 @FunctionalInterface public interface Condition { /** * Determine if the condition matches. * @param context the condition context * @param metadata metadata of the {@link org.springframework.core.type .AnnotationMetadata class} * or {@link org.springframework.core.type .MethodMetadata method} being checked * @return {@code true } if the condition matches and the component can be registered , * or {@code false } to veto the annotated component 's registration */ 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来加载指定类等,下面看下其源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 public interface ConditionContext { /** * Return the {@link BeanDefinitionRegistry} that will hold the bean definition * should the condition match. * @throws IllegalStateException if no registry is available (which is unusual: * only the case with a plain {@link ClassPathScanningCandidateComponentProvider}) */ BeanDefinitionRegistry getRegistry(); /** * Return the {@link ConfigurableListableBeanFactory} that will hold the bean * definition should the condition match, or {@code null} if the bean factory is * not available (or not downcastable to {@code ConfigurableListableBeanFactory}). */ @Nullable ConfigurableListableBeanFactory getBeanFactory(); /** * Return the {@link Environment} for which the current application is running . */ Environment getEnvironment(); /** * Return the {@link ResourceLoader} currently being used . */ ResourceLoader getResourceLoader(); /** * Return the {@link ClassLoader} that should be used to load additional classes * (only {@code null} if even the system ClassLoader isn 't accessible). * @see org.springframework.util.ClassUtils */ @Nullable ClassLoader getClassLoader(); }
AnnotatedTypeMetadata,这个跟注解元数据有关,利用AnnotatedTypeMetadata可以拿到某个注解的一些元数据,而这些元数据就包含了某个注解里面的属性,比如上面的demo,利用AnnotatedTypeMetadata可以拿到@ConditionalOnLinux的注解属性environment的值。下面看下其源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public interface AnnotatedTypeMetadata { boolean isAnnotated(String var1); @Nullable Map <String , Object > getAnnotationAttributes(String var1); @Nullable Map <String , Object > getAnnotationAttributes(String var1, boolean var2); @Nullable MultiValueMap<String , Object > getAllAnnotationAttributes(String var1); @Nullable MultiValueMap<String , Object > getAllAnnotationAttributes(String var1, boolean var2); }
@ConditionalOnLinux注解真正起作用的是Condition接口的具体实现类LinuxCondition的matches方法。
这个matches方法是在何时被调用的呢?通过idea调试看调用的栈帧,如下图:
从图片上可以看到,在ConditionEvaluator的shouldSkip方法中调用了LinuxCondition的matches方法,分析下ConditionEvaluator的shouldSkip方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 public boolean shouldSkip(@Nullable AnnotatedTypeMetadata metadata , @Nullable ConfigurationPhase phase ) { if (metadata == null || !metadata.isAnnotated(Conditional.class .getName () )) { return false ; } if (phase == null) { 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<>() ; for (String[] conditionClasses : getConditionClasses(metadata ) ) { for (String conditionClass : conditionClasses) { Condition condition = getCondition(conditionClass , this .context .getClassLoader () ); conditions.add(condition); } } AnnotationAwareOrderComparator . sort(conditions); for (Condition condition : conditions) { ConfigurationPhase requiredPhase = null; if (condition instanceof ConfigurationCondition) { requiredPhase = ((ConfigurationCondition) condition).getConfigurationPhase() ; } if ((requiredPhase == null || requiredPhase == phase) && !condition.matches(this.context, metadata)) { return true ; } } return false ; }
shouldSkip这个方法执行的逻辑主要是如果是解析阶段(PARSE_CONFIGURATION)则跳过,如果是注册阶段(REGISTER_BEAN)则不跳过;如果是在注册阶段的话,此时会得到所有的Condition接口的具体实现类并实例化这些实现类,然后再执行下面关键的代码进行判断是否需要跳过。
1 2 3 if ((requiredPhase == null || requiredPhase == phase) && !condition.matches(this .context, metadata)) { return true ; }
上面代码最重要的逻辑是调用了Condition接口的具体实现类的matches方法,若matches返回false,则跳过,不进行注册bean的操作;若matches返回true,则不跳过,进行注册bean的操作;
2.3 Spring的内置Condition接口实现类 Spring的Condition接口的具体实现类的关系图:
发现Spring内置的Condition接口的具体实现类虽然有多个,但只有ProfileCondition不是测试相关的,因此真正内置的Condition接口的具体实现类只有ProfileCondition一个。
ProfileCondition 是跟环境有关, 一般有dev,test和prod环境,而ProfileCondition就是判断 项目配置了哪个环境的。下面是ProfileCondition的源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 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 ; } }
3.SpringBootCondition源码解析 前面Spring对Condition的内置注解只有ProfileCondition一个,但是,SpringBoot内置了大量的条件注解ConditionalOnXXX。
SpringBootCondition的整体类图:
可以看到SpringBootCondition作为SpringBoot条件注解的基类,它实现了Condition接口,然后又有很多具体的子类OnXXXCondition,这些OnXXXCondition其实就是@ConditionalOnXXX的条件类。
先来看下SpringBootCondition这个父类是主要做了哪些事情,抽象了哪些共有的逻辑?
SpringBootConditon实现了Condition接口,作为SpringBoot众多条件注解OnXXXCondtion的父类,它的作用主要就是打印一些条件注解评估报告的日志,比如打印哪些配置类是符合条件注解的,哪些是不符合的。
因为SpringBootConditon实现了Condition接口,也实现了matches方法,因此该方法同样也是被ConditionEvaluator的shouldSkip方法中调用,因此我们就以SpringBootConditon的matches方法为入口去进行分析。直接上代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 @Override public final boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { 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); } }
上面代码的注释已经非常详细,知道了SpringBootCondition抽象了所有其具体实现类OnXXXCondition的共有逻辑–condition评估信息打印,最重要的是封装了一个模板方法getMatchOutcome(context, metadata),留给各个OnXXXCondition具体子类去覆盖实现属于自己的判断逻辑,然后再返回相应的匹配结果给SpringBootCondition用于日志打印。
SpringBootCondition其实就是用来打印condition评估信息的,对于其他枝节方法暂时先不追究过深,免得丢了主线。现在的重点是放在交给OnXXXCondition子类实现的模板方法上getMatchOutcome(context, metadata),因为这个方法将会由很多OnXXXCondition覆盖重写判断逻辑,这里是接下来分析的重点。
因为SpringBootCondition有众多具体实现类,下面只挑OnResourceCondition,OnBeanCondition和OnWebApplicationCondition进行讲解,而AutoConfigurationImportFilter跟自动配置有关,则留到自动配置源码解析的时候再进行分析。
3.1 OnResourceCondition源码分析 先来看一下逻辑及其简单的注解条件类OnResourceCondition
,OnResourceCondition
继承了SpringBootCondition
父类,覆盖了其getMatchOutcome
方法,用于@ConditionalOnResource
注解指定的资源存在与否。OnResourceCondition
的判断逻辑非常简单,主要拿到@ConditionalOnResource
注解指定的资源路径后,然后用ResourceLoader
根据指定路径去加载看资源存不存在。源码如下:
@ConditionalOnResource 代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 @Target ({ ElementType.TYPE, ElementType.METHOD })@Retention (RetentionPolicy.RUNTIME)@Documented @Conditional (OnResourceCondition.class)public @interface ConditionalOnResource { String [] resources () default {}; }
OnResourceCondition 代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 @Order (Ordered.HIGHEST_PRECEDENCE + 20 )class OnResourceCondition extends SpringBootCondition { private final ResourceLoader defaultResourceLoader = new DefaultResourceLoader(); @Override public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) { MultiValueMap<String , Object > attributes = metadata .getAllAnnotationAttributes(ConditionalOnResource.class .getName(), true ); ResourceLoader loader = (context.getResourceLoader() != null ) ? context.getResourceLoader() : this .defaultResourceLoader; List <String > locations = new ArrayList<>(); collectValues(locations, attributes.get ("resources" )); Assert.isTrue(!locations.isEmpty(), "@ConditionalOnResource annotations must specify at " + "least one resource location" ); List <String > missing = new ArrayList<>(); for (String location : locations) { 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)); } private void collectValues(List <String > names, List <Object > values) { for (Object value : values) { for (Object item : (Object []) value) { names.add((String ) item); } } } }
3.2 OnBeanCondition源码分析 OnBeanCondition
继承了FilteringSpringBootCondition
父类,覆盖了父类FilteringSpringBootCondition
的getOutcomes
方法。而FilteringSpringBootCondition
又是SpringBootCondition
的子类,FilteringSpringBootCondition
跟自动配置类过滤有关。值得注意的是OnBeanCondition
同样重写了SpringBootCondition
的getMatchOutcome
方法 ,用来判断Spring容器中是否存在指定条件的bean
。同时OnBeanCondition
是@ConditionalOnBean
,@ConditionalOnSingleCandidate
和ConditionalOnMissingBean
的条件类。
OnBeanCondition
复写父类SpringBootCondition
的getMatchOutcome
方法的代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 @Override public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) { ConditionMessage matchMessage = ConditionMessage.empty (); if (metadata.isAnnotated(ConditionalOnBean.class .getName())) { BeanSearchSpec spec = new BeanSearchSpec(context, metadata, ConditionalOnBean.class ); 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()); } 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()); } 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(); } return ConditionOutcome.match (matchMessage); }
可以看到OnBeanCondition
类覆盖的getMatchOutcome
方法分别处理了标注@ConditionalOnBean
,@ConditionalOnSingleCandidate
和@ConditionalOnMissingBean
注解的情况,分别对应上面代码注释的(1)
,(2)
和(3)
处。
只看针对@ConditionalOnBean
注解的处理逻辑,从上面代码中可以看到若配置类(metadata)标注@ConditionalOnBean
注解的话,主要做了以下事情:
将该注解属性提取出来封装进BeanSearchSpec
对象中;
然后调用getMatchingBeans(context, spec)
方法来获取是否有匹配的bean
;
最后返回bean
的匹配情况;
可以看到最重要的逻辑是第2步,那么再来看下getMatchingBeans
方法,直接看代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 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
:表示只从当前的context
中搜索bean
ANCESTORS
:表示只从父context
中搜索bean
ALL
:表示从整个context
中搜索bean
定义了搜索策略后,然后再根据BeanSearchSpec
对象封装的注解属性分别取指定的容器中查找有无符合条件的bean
,然后再进行一些过滤。比如@ConditionalOnMissingBean
注解有定义ignored
属性值,那么从容器中搜索到有符合条件的bean
时,此时还要移除掉ignored
指定的bean
。
3.3 OnWebApplicationCondition源码分析 OnWebApplicationCondition
继承了FilteringSpringBootCondition
父类,覆盖了父类FilteringSpringBootCondition
的getOutcomes
方法。而FilteringSpringBootCondition
又是SpringBootCondition
的子类,FilteringSpringBootCondition
跟自动配置类过滤有关。值得注意的是OnWebApplicationCondition
同样重写了SpringBootCondition
的getMatchOutcome
方法 ,用来判断当前应用是否web应用。同时是OnWebApplicationCondition
是@ConditionalOnWebApplication
的条件类。
OnWebApplicationCondition
重写SpringBootCondition
的getMatchOutcome
方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 @Override public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) { boolean required = metadata .isAnnotated(ConditionalOnWebApplication.class .getName()); ConditionOutcome outcome = isWebApplication(context, metadata, required ); if (required && !outcome.isMatch()) { return ConditionOutcome.noMatch(outcome.getConditionMessage()); } if (!required && outcome.isMatch()) { return ConditionOutcome.noMatch(outcome.getConditionMessage()); } return ConditionOutcome.match(outcome.getConditionMessage()); }
主要是调用isWebApplication
方法来判断当前应用是否是web应用。因此,再来看下isWebApplication
方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 private ConditionOutcome isWebApplication(ConditionContext context, AnnotatedTypeMetadata metadata, boolean required) { switch (deduceType(metadata)) { case SERVLET: return isServletWebApplication(context); case REACTIVE: return isReactiveWebApplication(context); default : return isAnyWebApplication(context, required); } }
在isWebApplication
方法中,首先从@ConditionalOnWebApplication
注解中获取其定义了什么类型,然后根据不同的类型进入不同的判断逻辑。这里看下SERVLET
的情况判断处理,看isServletWebApplication
代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 private ConditionOutcome isServletWebApplication(ConditionContext context) { ConditionMessage.Builder message = ConditionMessage.forCondition("" ); if (!ClassNameFilter.isPresent(SERVLET_WEB_APPLICATION_CLASS, context.getClassLoader())) { return ConditionOutcome.noMatch( message.didNotFind("servlet web application classes" ).atAll()); } if (context.getBeanFactory() != null ) { String [] scopes = context.getBeanFactory().getRegisteredScopeNames(); if (ObjectUtils.containsElement(scopes, "session" )) { return ConditionOutcome.match (message.foundExactly("'session' scope" )); } } if (context.getEnvironment() instanceof ConfigurableWebEnvironment) { return ConditionOutcome .match (message.foundExactly("ConfigurableWebEnvironment" )); } if (context.getResourceLoader() instanceof WebApplicationContext) { return ConditionOutcome.match (message.foundExactly("WebApplicationContext" )); } return ConditionOutcome.noMatch(message.because("not a servlet web application" )); }
对于是SERVLET
的情况,首先根据classpath
中是否存在org.springframework.web.context.support.GenericWebApplicationContext.class
,如果不存在该类,则直接返回不匹配;若存在的话那么又分为以下几种匹配的情况:
session
ConfigurableWebEnvironment
WebApplicationContext
若上面三种情况都不匹配,则说明不是一个servlet web application。
3.4 其他 由于springboot的OnXXXCondition
类实现太多,不可能每个条件类都分析一遍,因此上面只分析了OnResourceCondition
,OnBeanCondition
和onWebApplicationCondition
的源码。分析源码不可能把所有代码都通读一遍的,阅读源码的话,只要理解了某个模块的类之间的关系及挑几个有代表性的类分析下就行,不可能一网打尽。
若有时间的话,推荐看下几个我们常用的条件类的源码:OnPropertyCondition
,OnClassCondition
和OnExpressionCondition
等。
参考资料
https://blog.csdn.net/zhanglu1236789/article/details/78999496
https://www.cnblogs.com/sam-uncle/p/9111281.html
评论加载中