咱們知道自動裝配是SpringBoot微服務化的核心,它會把META-INF/spring.factoires裏配置的EnableAutoConfiguration註冊到IOC容器裏。可是,請你們考慮一個問題,根據需求咱們要配置一個tomcat的內嵌容器,但是當前的運行環境裏都沒有servlet的相關API或者說當前的ApplicationContext不是一個WebApplicationContext,若是這樣的話,那麼建立tomcat的內嵌容器還有什麼意義上呢?若是根據需求咱們想自動裝配一個Mybatis的SqlSessionFactory,但是運行環境裏連DataSource都沒有,恐怕要自動裝配Mybatis的願望也會落空吧!針對這種問題,SpringBoot早都考慮到了,下面咱們來看看SpringBoot是怎麼解決的。java
conditional中文的意思爲條件,其自己是Springframework提供的核心註解,一般狀況下該註解能夠加在類上或者方法上與@Configuration或者@Bean配合使用,當和@Configuration配合使用時,那麼該類下全部@Bean方法 或者@Import 或者 @ComponentScan都會受到其配置條件的影響,咱們先看一下其源碼:web
/* * Copyright 2002-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.springframework.context.annotation; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * Indicates that a component is only eligible for registration when all * {@linkplain #value specified conditions} match. * * <p>A <em>condition</em> is any state that can be determined programmatically * before the bean definition is due to be registered (see {@link Condition} for details). * * <p>The {@code @Conditional} annotation may be used in any of the following ways: * <ul> * <li>as a type-level annotation on any class directly or indirectly annotated with * {@code @Component}, including {@link Configuration @Configuration} classes</li> * <li>as a meta-annotation, for the purpose of composing custom stereotype * annotations</li> * <li>as a method-level annotation on any {@link Bean @Bean} method</li> * </ul> * * <p>If a {@code @Configuration} class is marked with {@code @Conditional}, * all of the {@code @Bean} methods, {@link Import @Import} annotations, and * {@link ComponentScan @ComponentScan} annotations associated with that * class will be subject to the conditions. * * <p><strong>NOTE</strong>: Inheritance of {@code @Conditional} annotations * is not supported; any conditions from superclasses or from overridden * methods will not be considered. In order to enforce these semantics, * {@code @Conditional} itself is not declared as * {@link java.lang.annotation.Inherited @Inherited}; furthermore, any * custom <em>composed annotation</em> that is meta-annotated with * {@code @Conditional} must not be declared as {@code @Inherited}. * * @author Phillip Webb * @author Sam Brannen * @since 4.0 * @see Condition */ @Target({ElementType.TYPE, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface Conditional { /** * All {@link Condition}s that must {@linkplain Condition#matches match} * in order for the component to be registered. */ Class<? extends Condition>[] value(); }
在這裏文檔註釋提醒咱們去看Condition接口:spring
/* * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.springframework.context.annotation; import org.springframework.beans.factory.config.BeanFactoryPostProcessor; import org.springframework.core.type.AnnotatedTypeMetadata; /** * 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 */ 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 registration. */ boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata); }
該接口就有一個方法:matches方法。它定義了最基本的匹配規則,該方法傳入兩個參數一個是ConditionContext ,該接口定義了若干個方法來獲取spring核心接口的方法:express
/* * Copyright 2002-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.springframework.context.annotation; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.core.env.Environment; import org.springframework.core.io.ResourceLoader; /** * Context information for use by {@link Condition}s. * * @author Phillip Webb * @author Juergen Hoeller * @since 4.0 */ public interface ConditionContext { /** * Return the {@link BeanDefinitionRegistry} that will hold the bean definition * should the condition match, or {@code null} if the registry is not available. */ 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. */ ConfigurableListableBeanFactory getBeanFactory(); /** * Return the {@link Environment} for which the current application is running, * or {@code null} if no environment is available. */ Environment getEnvironment(); /** * Return the {@link ResourceLoader} currently being used, or {@code null} if * the resource loader cannot be obtained. */ ResourceLoader getResourceLoader(); /** * Return the {@link ClassLoader} that should be used to load additional classes, * or {@code null} if the default classloader should be used. */ ClassLoader getClassLoader(); }
在這裏咱們能獲取到BeanFactory,ResourceLoader,Enviroment等。而另一個參數是AnnotatedTypeMetadata接口,該接口主要獲取該類上標記的註解。在這裏我先寫一個簡單的例子,來試驗一下:apache
MyTestConditional:tomcat
package com.hzgj.lyrk.autoconfigure; import org.springframework.context.annotation.ConditionContext; import org.springframework.context.annotation.ConfigurationCondition; import org.springframework.core.type.AnnotatedTypeMetadata; import java.util.Map; public class MyTestConditional implements ConfigurationCondition { @Override public ConfigurationPhase getConfigurationPhase() { return ConfigurationPhase.REGISTER_BEAN; } @Override public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { Map<String, Object> map = metadata.getAnnotationAttributes("org.springframework.context.annotation.Description"); System.out.println(map); return false; } }
這個是自定義的Conditional,該類實現了ConfigurationCondition接口,該接口繼承了Condition,只不過它多添加了一個用於設置解析Condition階段的方法,在這裏有兩個階段進行解析:session
1)PARSE_CONFIGURATION:會在解析@Configuration時進行condition的解析app
2)REGISTER_BEAN:會在註冊Bean的時候進行condition的解析less
ServerAutoConfiguration:ide
package com.hzgj.lyrk.autoconfigure; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Conditional; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Description; @Configuration public class ServerAutoConfiguration { @Configuration @Conditional(MyTestConditional.class) @Description(value = "student") public static class StudentAutoConfiguration { @Bean public Student student() { System.out.println("student create...."); return new Student(); } } @Configuration @Conditional(MyTestConditional.class) @Description(value = "teacher") public static class TeacherAutoConfiguration { @Bean public Teacher teacher() { System.out.println("teacher create....."); return new Teacher(); } } }
此時因爲自定義的Conditional的match方法返回值是false,所以不能註冊@Bean配置的對象:
當值改成true時,則能註冊@Bean配置的對象:
在SpringBoot中定義了一個SpringBootCondition類對Condition進行了擴展,該類源代碼以下:
/* * Copyright 2012-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.springframework.boot.autoconfigure.condition; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.context.annotation.Condition; import org.springframework.context.annotation.ConditionContext; import org.springframework.core.type.AnnotatedTypeMetadata; import org.springframework.core.type.AnnotationMetadata; import org.springframework.core.type.ClassMetadata; import org.springframework.core.type.MethodMetadata; import org.springframework.util.ClassUtils; import org.springframework.util.StringUtils; /** * Base of all {@link Condition} implementations used with Spring Boot. Provides sensible * logging to help the user diagnose what classes are loaded. * * @author Phillip Webb * @author Greg Turnquist */ public abstract class SpringBootCondition implements Condition { private final Log logger = LogFactory.getLog(getClass()); @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); } } private String getName(AnnotatedTypeMetadata metadata) { if (metadata instanceof AnnotationMetadata) { return ((AnnotationMetadata) metadata).getClassName(); } if (metadata instanceof MethodMetadata) { MethodMetadata methodMetadata = (MethodMetadata) metadata; return methodMetadata.getDeclaringClassName() + "." + methodMetadata.getMethodName(); } return metadata.toString(); } private static String getClassOrMethodName(AnnotatedTypeMetadata metadata) { if (metadata instanceof ClassMetadata) { ClassMetadata classMetadata = (ClassMetadata) metadata; return classMetadata.getClassName(); } MethodMetadata methodMetadata = (MethodMetadata) metadata; return methodMetadata.getDeclaringClassName() + "#" + methodMetadata.getMethodName(); } protected final void logOutcome(String classOrMethodName, ConditionOutcome outcome) { if (this.logger.isTraceEnabled()) { this.logger.trace(getLogMessage(classOrMethodName, outcome)); } } private StringBuilder getLogMessage(String classOrMethodName, ConditionOutcome outcome) { StringBuilder message = new StringBuilder(); message.append("Condition "); message.append(ClassUtils.getShortName(getClass())); message.append(" on "); message.append(classOrMethodName); message.append(outcome.isMatch() ? " matched" : " did not match"); if (StringUtils.hasLength(outcome.getMessage())) { message.append(" due to "); message.append(outcome.getMessage()); } return message; } private void recordEvaluation(ConditionContext context, String classOrMethodName, ConditionOutcome outcome) { if (context.getBeanFactory() != null) { ConditionEvaluationReport.get(context.getBeanFactory()) .recordConditionEvaluation(classOrMethodName, this, outcome); } } /** * Determine the outcome of the match along with suitable log output. * @param context the condition context * @param metadata the annotation metadata * @return the condition outcome */ public abstract ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata); /** * Return true if any of the specified conditions match. * @param context the context * @param metadata the annotation meta-data * @param conditions conditions to test * @return {@code true} if any condition matches. */ protected final boolean anyMatches(ConditionContext context, AnnotatedTypeMetadata metadata, Condition... conditions) { for (Condition condition : conditions) { if (matches(context, metadata, condition)) { return true; } } return false; } /** * Return true if any of the specified condition matches. * @param context the context * @param metadata the annotation meta-data * @param condition condition to test * @return {@code true} if the condition matches. */ protected final boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata, Condition condition) { if (condition instanceof SpringBootCondition) { return ((SpringBootCondition) condition).getMatchOutcome(context, metadata) .isMatch(); } return condition.matches(context, metadata); } }
在這裏,咱們須要重寫getMatchOutcome方法來進行,匹配結果的過濾,下面咱們列舉一下常見的Conditional:
常見的有ConditionalOnClass,ConditionalOnMissingClass
ConditionalOnClass:代表當前classpath有對應指定的類型纔去建立Bean,咱們來看一下源代碼:
/* * Copyright 2012-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.springframework.boot.autoconfigure.condition; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import org.springframework.context.annotation.Conditional; /** * {@link Conditional} that only matches when the specified classes are on the classpath. * * @author Phillip Webb */ @Target({ ElementType.TYPE, ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) @Documented @Conditional(OnClassCondition.class) public @interface ConditionalOnClass { /** * The classes that must be present. Since this annotation is parsed by loading class * bytecode, it is safe to specify classes here that may ultimately not be on the * classpath, only if this annotation is directly on the affected component and * <b>not</b> if this annotation is used as a composed, meta-annotation. In order to * use this annotation as a meta-annotation, only use the {@link #name} attribute. * @return the classes that must be present */ Class<?>[] value() default {}; /** * The classes names that must be present. * @return the class names that must be present. */ String[] name() default {}; }
根據註釋咱們去尋找一下:OnClassCondition這個類,我貼出部分代碼:
@Override public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) { ClassLoader classLoader = context.getClassLoader(); ConditionMessage matchMessage = ConditionMessage.empty(); List<String> onClasses = getCandidates(metadata, ConditionalOnClass.class); if (onClasses != null) { List<String> missing = getMatches(onClasses, MatchType.MISSING, classLoader); if (!missing.isEmpty()) { return ConditionOutcome .noMatch(ConditionMessage.forCondition(ConditionalOnClass.class) .didNotFind("required class", "required classes") .items(Style.QUOTE, missing)); } matchMessage = matchMessage.andCondition(ConditionalOnClass.class) .found("required class", "required classes").items(Style.QUOTE, getMatches(onClasses, MatchType.PRESENT, classLoader)); } List<String> onMissingClasses = getCandidates(metadata, ConditionalOnMissingClass.class); if (onMissingClasses != null) { List<String> present = getMatches(onMissingClasses, MatchType.PRESENT, classLoader); if (!present.isEmpty()) { return ConditionOutcome.noMatch( ConditionMessage.forCondition(ConditionalOnMissingClass.class) .found("unwanted class", "unwanted classes") .items(Style.QUOTE, present)); } matchMessage = matchMessage.andCondition(ConditionalOnMissingClass.class) .didNotFind("unwanted class", "unwanted classes").items(Style.QUOTE, getMatches(onMissingClasses, MatchType.MISSING, classLoader)); } return ConditionOutcome.match(matchMessage); }
在這裏咱們關注一下getMatches方法:
private List<String> getMatches(Collection<String> candidates, MatchType matchType, ClassLoader classLoader) { List<String> matches = new ArrayList<String>(candidates.size()); for (String candidate : candidates) { if (matchType.matches(candidate, classLoader)) { matches.add(candidate); } } return matches; } //..... private enum MatchType { PRESENT { @Override public boolean matches(String className, ClassLoader classLoader) { return isPresent(className, classLoader); } }, MISSING { @Override public boolean matches(String className, ClassLoader classLoader) { return !isPresent(className, classLoader); } }; private static boolean isPresent(String className, ClassLoader classLoader) { if (classLoader == null) { classLoader = ClassUtils.getDefaultClassLoader(); } try { forName(className, classLoader); return true; } catch (Throwable ex) { return false; } } private static Class<?> forName(String className, ClassLoader classLoader) throws ClassNotFoundException { if (classLoader != null) { return classLoader.loadClass(className); } return Class.forName(className); }
咱們能夠看到這裏是經過ClassLoader或者Class.forName來加載類的
在這裏常見的是ConditionalOnBean和ConditionalOnMissingBean,只有當BeanFactory裏(不)包含指定的Bean時,才能經過匹配。注意:官網建議咱們在AutoConfiguration裏使用此註解,由於受到bean裝配順序影響,頗有可能不能達到咱們的預期效果。
package com.hzgj.lyrk.autoconfigure; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Conditional; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Description; @Configuration public class ServerAutoConfiguration { @Configuration @ConditionalOnBean(Teacher.class) public static class StudentAutoConfiguration { @Bean public Student student() { System.out.println("student create...."); return new Student(); } } @Configuration public static class TeacherAutoConfiguration { @Bean public Teacher teacher() { System.out.println("teacher create....."); return new Teacher(); } } }
好比說如上代碼,運行後將獲得以下結果:
咱們能夠看到此時Student並未建立。由於受其順序影響當註冊Student時,IOC容器裏並無Teacher,我在這裏貼出OnBeanCondition的關鍵代碼:
@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); List<String> matching = getMatchingBeans(context, spec); if (matching.isEmpty()) { return ConditionOutcome.noMatch( ConditionMessage.forCondition(ConditionalOnBean.class, spec) .didNotFind("any beans").atAll()); } matchMessage = matchMessage.andCondition(ConditionalOnBean.class, spec) .found("bean", "beans").items(Style.QUOTE, matching); } if (metadata.isAnnotated(ConditionalOnSingleCandidate.class.getName())) { BeanSearchSpec spec = new SingleCandidateBeanSearchSpec(context, metadata, ConditionalOnSingleCandidate.class); List<String> matching = getMatchingBeans(context, spec); if (matching.isEmpty()) { return ConditionOutcome.noMatch(ConditionMessage .forCondition(ConditionalOnSingleCandidate.class, spec) .didNotFind("any beans").atAll()); } else if (!hasSingleAutowireCandidate(context.getBeanFactory(), matching, spec.getStrategy() == SearchStrategy.ALL)) { return ConditionOutcome.noMatch(ConditionMessage .forCondition(ConditionalOnSingleCandidate.class, spec) .didNotFind("a primary bean from beans") .items(Style.QUOTE, matching)); } matchMessage = matchMessage .andCondition(ConditionalOnSingleCandidate.class, spec) .found("a primary bean from beans").items(Style.QUOTE, matching); } if (metadata.isAnnotated(ConditionalOnMissingBean.class.getName())) { BeanSearchSpec spec = new BeanSearchSpec(context, metadata, ConditionalOnMissingBean.class); List<String> matching = getMatchingBeans(context, spec); if (!matching.isEmpty()) { return ConditionOutcome.noMatch(ConditionMessage .forCondition(ConditionalOnMissingBean.class, spec) .found("bean", "beans").items(Style.QUOTE, matching)); } matchMessage = matchMessage.andCondition(ConditionalOnMissingBean.class, spec) .didNotFind("any beans").atAll(); } return ConditionOutcome.match(matchMessage); } @SuppressWarnings("deprecation") private List<String> getMatchingBeans(ConditionContext context, BeanSearchSpec beans) { ConfigurableListableBeanFactory beanFactory = context.getBeanFactory(); if (beans.getStrategy() == SearchStrategy.PARENTS || beans.getStrategy() == SearchStrategy.ANCESTORS) { BeanFactory parent = beanFactory.getParentBeanFactory(); Assert.isInstanceOf(ConfigurableListableBeanFactory.class, parent, "Unable to use SearchStrategy.PARENTS"); beanFactory = (ConfigurableListableBeanFactory) parent; } if (beanFactory == null) { return Collections.emptyList(); } List<String> beanNames = new ArrayList<String>(); boolean considerHierarchy = beans.getStrategy() != SearchStrategy.CURRENT; for (String type : beans.getTypes()) { beanNames.addAll(getBeanNamesForType(beanFactory, type, context.getClassLoader(), considerHierarchy)); } for (String ignoredType : beans.getIgnoredTypes()) { beanNames.removeAll(getBeanNamesForType(beanFactory, ignoredType, context.getClassLoader(), considerHierarchy)); } for (String annotation : beans.getAnnotations()) { beanNames.addAll(Arrays.asList(getBeanNamesForAnnotation(beanFactory, annotation, context.getClassLoader(), considerHierarchy))); } for (String beanName : beans.getNames()) { if (containsBean(beanFactory, beanName, considerHierarchy)) { beanNames.add(beanName); } } return beanNames; }
在這裏咱們關注getMatchingBeans方法,此方法從當前的BeanFactory找所須要的Bean。因爲BeanFactory層次化的關係,所以在ConditionalOn(Missing)Bean裏有相關屬性來配置尋找策略:
/** * Strategy to decide if the application context hierarchy (parent contexts) should be * considered. * @return the search strategy */ SearchStrategy search() default SearchStrategy.ALL;
/* * Copyright 2012-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.springframework.boot.autoconfigure.condition; /** * Some named search strategies for beans in the bean factory hierarchy. * * @author Dave Syer */ public enum SearchStrategy { /** * Search only the current context. */ CURRENT, /** * Search all parents and ancestors, but not the current context. * @deprecated as of 1.5 in favor of {@link SearchStrategy#ANCESTORS} */ @Deprecated PARENTS, /** * Search all ancestors, but not the current context. */ ANCESTORS, /** * Search the entire hierarchy. */ ALL }
常見的註解爲@ConditionalOnProperty,該註解會在Spring的Environment裏面找對應的PropertySource,若是存在對應的屬性值而且對應的值不爲false時則匹配,我貼出關鍵代碼部分:
@Override public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) { //..... for (AnnotationAttributes annotationAttributes : allAnnotationAttributes) { ConditionOutcome outcome = determineOutcome(annotationAttributes, context.getEnvironment()); (outcome.isMatch() ? match : noMatch).add(outcome.getConditionMessage()); } //..... } private static class Spec { // ..... private void collectProperties(PropertyResolver resolver, List<String> missing, List<String> nonMatching) { if (this.relaxedNames) { resolver = new RelaxedPropertyResolver(resolver, this.prefix); } for (String name : this.names) { String key = (this.relaxedNames ? name : this.prefix + name); if (resolver.containsProperty(key)) { if (!isMatch(resolver.getProperty(key), this.havingValue)) { nonMatching.add(name); } } else { if (!this.matchIfMissing) { missing.add(name); } } } } private boolean isMatch(String value, String requiredValue) { if (StringUtils.hasLength(requiredValue)) { return requiredValue.equalsIgnoreCase(value); } return !"false".equalsIgnoreCase(value); } //..... }
在這裏經過ConditionContext拿到當前的Environment對象,在經過PropertyResover獲取其配置的值。isMatch方法代表若是值存在且不等於false的狀況下條件才生效
常見的註解爲:@ConditionalOnResource,只有存在指定的資源文件時才生效,默認狀況下是classpath,固然咱們也能夠指定其絕對路徑,例如:file:/home/user/test.dat,源代碼以下:
package org.springframework.boot.autoconfigure.condition; import java.util.ArrayList; import java.util.List; import org.springframework.boot.autoconfigure.condition.ConditionMessage.Style; import org.springframework.context.annotation.Condition; import org.springframework.context.annotation.ConditionContext; import org.springframework.core.Ordered; import org.springframework.core.annotation.Order; import org.springframework.core.io.DefaultResourceLoader; import org.springframework.core.io.ResourceLoader; import org.springframework.core.type.AnnotatedTypeMetadata; import org.springframework.util.Assert; import org.springframework.util.MultiValueMap; /** * {@link Condition} that checks for specific resources. * * @author Dave Syer * @see ConditionalOnResource */ @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 ? this.defaultResourceLoader : context.getResourceLoader(); List<String> locations = new ArrayList<String>(); collectValues(locations, attributes.get("resources")); Assert.isTrue(!locations.isEmpty(), "@ConditionalOnResource annotations must specify at " + "least one resource location"); List<String> missing = new ArrayList<String>(); 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); } } } }
在這裏咱們能夠看到,它是用DefaultResourceLoader來加載資源文件的,它會根據路徑前綴來判斷根據classpath加載或是url加載,其相關代碼以下:
@Override public Resource getResource(String location) { Assert.notNull(location, "Location must not be null"); for (ProtocolResolver protocolResolver : this.protocolResolvers) { Resource resource = protocolResolver.resolve(location, this); if (resource != null) { return resource; } } if (location.startsWith("/")) { return getResourceByPath(location); } else if (location.startsWith(CLASSPATH_URL_PREFIX)) { return new ClassPathResource(location.substring(CLASSPATH_URL_PREFIX.length()), getClassLoader()); } else { try { // Try to parse the location as a URL... URL url = new URL(location); return new UrlResource(url); } catch (MalformedURLException ex) { // No URL -> resolve as resource path. return getResourceByPath(location); } } }
最多見的是@ConditionalOnWebApplication或者@ConditionalOnNotWebApplication,此註解的含義爲:判斷當前的ApplicationContext是否爲WebApplicationContext。其關鍵代碼以下:
private ConditionOutcome isWebApplication(ConditionContext context, AnnotatedTypeMetadata metadata, boolean required) { ConditionMessage.Builder message = ConditionMessage.forCondition( ConditionalOnWebApplication.class, required ? "(required)" : ""); if (!ClassUtils.isPresent(WEB_CONTEXT_CLASS, context.getClassLoader())) { return ConditionOutcome .noMatch(message.didNotFind("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 StandardServletEnvironment) { return ConditionOutcome .match(message.foundExactly("StandardServletEnvironment")); } if (context.getResourceLoader() instanceof WebApplicationContext) { return ConditionOutcome.match(message.foundExactly("WebApplicationContext")); } return ConditionOutcome.noMatch(message.because("not a web application")); }
在此咱們能夠看到它的判斷依據有以下幾個方面:
1)是否爲WebApplicationContext
2) 是否包含session的scope
3) 當前的Environment是否爲StandardServletEnvironment
咱們知道解析@Configuration的最主要的類是ConfigurationClassPostProcessor,這個類下有一個屬性叫ConfigurationClassBeanDefinitionReader,與接口BeanDefiinitiaonReader相似,一看這個類咱們就能聯想到它的loadBeanDefinitions,其方法會調用loadBeanDefinitionsForConfigurationClass,咱們來看一看這個方法:
/** * Read a particular {@link ConfigurationClass}, registering bean definitions * for the class itself and all of its {@link Bean} methods. */ private void loadBeanDefinitionsForConfigurationClass(ConfigurationClass configClass, TrackedConditionEvaluator trackedConditionEvaluator) { if (trackedConditionEvaluator.shouldSkip(configClass)) { String beanName = configClass.getBeanName(); if (StringUtils.hasLength(beanName) && this.registry.containsBeanDefinition(beanName)) { this.registry.removeBeanDefinition(beanName); } this.importRegistry.removeImportingClass(configClass.getMetadata().getClassName()); return; } if (configClass.isImported()) { registerBeanDefinitionForImportedConfigurationClass(configClass); } for (BeanMethod beanMethod : configClass.getBeanMethods()) { loadBeanDefinitionsForBeanMethod(beanMethod); } loadBeanDefinitionsFromImportedResources(configClass.getImportedResources()); loadBeanDefinitionsFromRegistrars(configClass.getImportBeanDefinitionRegistrars()); }
在這裏你們關注一下loadBeanDefinitionsForBeanMethod,此方法是加載@Bean註解所配置的Bean,咱們來看一下其源碼,我貼出關鍵部分:
private void loadBeanDefinitionsForBeanMethod(BeanMethod beanMethod) { ConfigurationClass configClass = beanMethod.getConfigurationClass(); MethodMetadata metadata = beanMethod.getMetadata(); String methodName = metadata.getMethodName(); // Do we need to mark the bean as skipped by its condition? if (this.conditionEvaluator.shouldSkip(metadata, ConfigurationPhase.REGISTER_BEAN)) { configClass.skippedBeanMethods.add(methodName); return; } if (configClass.skippedBeanMethods.contains(methodName)) { return; } //.....省略後續代碼 }
在這裏會調用conditionEvaluator的shouldSkip方法 若是爲true,則return,那麼shuoldSkip方法又是怎麼樣的呢?咱們來追蹤一下:
/** * Determine if an item should be skipped based on {@code @Conditional} annotations. * @param metadata the meta data * @param phase the phase of the call * @return if the item should be skipped */ public boolean shouldSkip(AnnotatedTypeMetadata metadata, 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<Condition>(); 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) { if (!condition.matches(this.context, metadata)) { return true; } } } return false; }
那麼至此,終於和先前的Condition所關聯了。
SpringBoot經過擴展Conditional來設置裝配Bean的條件,經過Condition接口的matches方法的返回值來判斷是否向IOC容器裏註冊Bean