Back to Javatutorial

SpringBoot自动装配(二):条件注解

docs/Spring全家桶/SpringBoot源码解析/SpringBoot自动装配(二):条件注解.md

1.0.032.7 KB
Original Source

1. ע⼰ж

springboot Զװ֮ԶװһУǷ springboot META-INF/spring.factories ļжԶװ࣬صЩԶװЩе bean һʼ𣿲ǣǿڶӦ Bean ɷʹעǷгʼ

springboot ṩע£

оٲ£

עע˵
class ע@ConditionalOnClass/@ConditionalOnMissingClassָ** / ȱʧ**ʱʼ bean
bean ע@ConditionalOnBean/@ConditionalOnMissingBeanָ bean ** / ȱʧ**ʱʼ bean
ע@ConditionalOnPropertyָԴڳʼ bean
Resource ע@ConditionalOnResourceָԴڳʼ bean
Web Ӧע@ConditionalOnWebApplication / @ConditionalOnNotWebApplicationǰӦΪ / Ϊ web Ӧʱʼ bean
spring ʽע@ConditionalOnExpressionʽΪ true ʱʼ bean

ǽ @ConditionalOnClass עݣ

...
@Conditional(OnClassCondition.class)
public @interface ConditionalOnClass {
    ...
}

ԿConditionalOnClass @Conditional עĹܣ OnClassCondition.class

@Conditional עԲο ConfigurationClassPostProcessor ֮ @Conditional עֱ˵ @Conditional ʹ÷ʽ

  1. @Conditional spring ע⣻

  2. @Conditional ṩһ valueΪ Class Condition

    Class<? extends Condition>[] value();
    
    
  3. Condition һӿڣһ matches(...)

    public interface Condition {
    
        boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);
    
    }
    
    

    ֻ matches(...) true ʱConfigurationClassPostProcessor ŻὫӦ bean עᵽ beanFactory beanDefinitionMap .

ܽ @Conditional ʹ÷ʽǾˣOnClassCondition.class Conditionmatches(...) ҪͬעĴʽҲƣܽעжࣺ

עעж
class ע@ConditionalOnClass/@ConditionalOnMissingClassOnClassCondition
bean ע@ConditionalOnBean/@ConditionalOnMissingBeanOnBeanCondition
ע@ConditionalOnPropertyOnPropertyCondition
Resource ע@ConditionalOnResourceOnResourceCondition
Web Ӧע@ConditionalOnWebApplication / @ConditionalOnNotWebApplicationOnWebApplicationCondition
spring ʽע@ConditionalOnExpressionOnExpressionCondition

ĿľͺȷˣҪЩעж߼ֻҪӦж matches(...) Ϳˡ

2. SpringBootCondition#matches

OnClassCondition#matches SpringBootConditionط£

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);
            // ﷵսtrue  false
            return outcome.isMatch();
        }
        catch (NoClassDefFoundError ex) {
            throw new IllegalStateException(...);
        }
        catch (RuntimeException ex) {
            throw new IllegalStateException(...);
        }
    }

    /**
     * Ǹʽʵ
     */
    public abstract ConditionOutcome getMatchOutcome(
        ConditionContext context, AnnotatedTypeMetadata metadata);

    ...

}

SpringBootCondition matches(...) ؼУ

...
ConditionOutcome outcome = getMatchOutcome(context, metadata);
...
return outcome.isMatch();

SpringBootCondition getMatchOutcome(...) Ǹ󷽷߼ṩOnClassCondition ʵ֮һʵϣж඼ SpringBootCondition ࣬Ǿֱӽ getMatchOutcome(...) ˡ

getMatchOutcome(...) صĽ ConditionOutcome ConditionOutcome Ǹɶ

public class ConditionOutcome {

    private final boolean match;

    private final ConditionMessage message;

    /**
     * 췽
     */
    public ConditionOutcome(boolean match, String message) {
        this(match, ConditionMessage.of(message));
    }

    /**
     * 췽
     */
    public ConditionOutcome(boolean match, ConditionMessage message) {
        Assert.notNull(message, "ConditionMessage must not be null");
        this.match = match;
        this.message = message;
    }

    /**
     * ƥĽ
     */
    public boolean isMatch() {
        return this.match;
    }

    ...
}

ӴװȽϽģڲԣmatch message:

  • match booleanƥɹʧܵıʶ
  • message ConditionMessageʾƥ˵

ConditionMessage:

public final class ConditionMessage {

    private String message;

    private ConditionMessage() {
        this(null);
    }

    private ConditionMessage(String message) {
        this.message = message;
    }

    ...
}

һԣmessageǶ˵Ϣİװ

3. @ConditionalOnClass: OnClassCondition#getMatchOutcome

OnClassCondition ƥ߼ֱӽ getMatchOutcome

public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
    ClassLoader classLoader = context.getClassLoader();
    ConditionMessage matchMessage = ConditionMessage.empty();
    // 1\.  @ConditionalOnClass ע
    List<String> onClasses = getCandidates(metadata, ConditionalOnClass.class);
    if (onClasses != null) {
        // 1.1 ж
        List<String> missing = filter(onClasses, ClassNameFilter.MISSING, classLoader);
        if (!missing.isEmpty()) {
            // 1.2 ؽƥ
            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, filter(onClasses, ClassNameFilter.PRESENT, classLoader));
    }

    // 2\.  @ConditionalOnMissingClass ע
    List<String> onMissingClasses = getCandidates(metadata, ConditionalOnMissingClass.class);
    if (onMissingClasses != null) {
        // 2.1 ж
        List<String> present = filter(onMissingClasses, ClassNameFilter.PRESENT, classLoader);
        if (!present.isEmpty()) {
            // 2.2 ؽƥ
            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, filter(onMissingClasses, ClassNameFilter.MISSING, classLoader));
    }
    // 󷵻ƥĽ
    return ConditionOutcome.match(matchMessage);
}

ͬʱ @ConditionalOnClass @ConditionalOnMissingClass ע⣬̼ƣעж϶ͨ FilteringSpringBootCondition#filter £

protected final List<String> filter(Collection<String> classNames, ClassNameFilter classNameFilter,
        ClassLoader classLoader) {
    if (CollectionUtils.isEmpty(classNames)) {
        return Collections.emptyList();
    }
    List<String> matches = new ArrayList<>(classNames.size());
    for (String candidate : classNames) {
        // ƥ
        if (classNameFilter.matches(candidate, classLoader)) {
            matches.add(candidate);
        }
    }
    return matches;
}

ɴ˿ɼ classNameFilter ˹ؼ

  • @ConditionalOnClass ʱclassNameFilter Ϊ ClassNameFilter.MISSING
  • @ConditionalOnMissingClass ʱclassNameFilter Ϊ ClassNameFilter.PRESENT

ǽ ClassNameFilter һ̽ FilteringSpringBootCondition ࣬£

abstract class FilteringSpringBootCondition extends SpringBootCondition
        implements AutoConfigurationImportFilter, BeanFactoryAware, BeanClassLoaderAware {
    ...
    /**
     *  classLoader ڣ ClassLoader#loadClass 
     *  Class#forName 
     */
    protected static Class<?> resolve(String className, ClassLoader classLoader) 
            throws ClassNotFoundException {
        if (classLoader != null) {
            return classLoader.loadClass(className);
        }
        return Class.forName(className);
    }

    /**
     * ƥ
     */
    protected enum ClassNameFilter {

        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);
            }

        };

        abstract boolean matches(String className, ClassLoader classLoader);

        /**
         * Class Ƿ
         * ͨʱ쳣жǷڣδ׳쳣ʾ
         */
        static boolean isPresent(String className, ClassLoader classLoader) {
            if (classLoader == null) {
                classLoader = ClassUtils.getDefaultClassLoader();
            }
            try {
                // ͨ쳣жǷڸclass
                resolve(className, classLoader);
                return true;
            }
            catch (Throwable ex) {
                return false;
            }
        }
    }
    ...
}

Ǿˣж Class Ƿڣspring ͨ ClassLoader.load(String) Class.forName(String) 쳣ģ׳쳣ͱ Class ڡ

ܽ @ConditionalOnClass/@ConditionalOnMissingClass Ĵʽ**ߵĴ඼Ϊ OnClassConditionͨ ClassLoader.load(String) Class.forName(String) 쳣ж Class Ƿڣ׳쳣ͱ Class **

4. @ConditionalOnBean: OnBeanCondition#getMatchOutcome

@ConditionalOnBean ֱӽ OnBeanCondition#getMatchOutcome

public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
    ConditionMessage matchMessage = ConditionMessage.empty();
    MergedAnnotations annotations = metadata.getAnnotations();
    //  @ConditionalOnBean
    if (annotations.isPresent(ConditionalOnBean.class)) {
        Spec<ConditionalOnBean> spec = new Spec<>(context, metadata, 
                annotations, ConditionalOnBean.class);
        // ƥ
        MatchResult matchResult = getMatchingBeans(context, spec);
        // עж
        if (!matchResult.isAllMatched()) {
            String reason = createOnBeanNoMatchReason(matchResult);
            return ConditionOutcome.noMatch(spec.message().because(reason));
        }
        matchMessage = spec.message(matchMessage).found("bean", "beans").items(Style.QUOTE,
                matchResult.getNamesOfAllMatches());
    }

    //  @ConditionalOnSingleCandidate
    if (metadata.isAnnotated(ConditionalOnSingleCandidate.class.getName())) {
        Spec<ConditionalOnSingleCandidate> spec 
                = new SingleCandidateSpec(context, metadata, annotations);
        // ƥ
        MatchResult matchResult = getMatchingBeans(context, spec);
        // עж
        if (!matchResult.isAllMatched()) {
            return ConditionOutcome.noMatch(spec.message().didNotFind("any beans").atAll());
        }
        else if (!hasSingleAutowireCandidate(context.getBeanFactory(), 
                matchResult.getNamesOfAllMatches(), spec.getStrategy() == SearchStrategy.ALL)) {
            return ConditionOutcome.noMatch(spec.message().didNotFind("a primary bean from beans")
                    .items(Style.QUOTE, matchResult.getNamesOfAllMatches()));
        }
        matchMessage = spec.message(matchMessage).found("a primary bean from beans")
                .items(Style.QUOTE, matchResult.getNamesOfAllMatches());
    }

    //  @ConditionalOnMissingBean
    if (metadata.isAnnotated(ConditionalOnMissingBean.class.getName())) {
        Spec<ConditionalOnMissingBean> spec = new Spec<>(context, metadata, annotations,
                ConditionalOnMissingBean.class);
        // ƥ
        MatchResult matchResult = getMatchingBeans(context, spec);
        // עж
        if (matchResult.isAnyMatched()) {
            String reason = createOnMissingBeanNoMatchReason(matchResult);
            return ConditionOutcome.noMatch(spec.message().because(reason));
        }
        matchMessage = spec.message(matchMessage).didNotFind("any beans").atAll();
    }
    return ConditionOutcome.match(matchMessage);
}

Կһעƥ䣺@ConditionalOnBean``@ConditionalOnSingleCandidate @ConditionalOnMissingBean߶ͬһ getMatchingBeans(...) ȡƥȻʹ matchResult.isAllMatched() matchResult.isAnyMatched() յĽжϡ

OnBeanCondition#getMatchingBeans

getMatchingBeans(...) Ĵ£

protected final MatchResult getMatchingBeans(ConditionContext context, Spec<?> spec) {
    ClassLoader classLoader = context.getClassLoader();
    ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
    boolean considerHierarchy = spec.getStrategy() != SearchStrategy.CURRENT;
    Set<Class<?>> parameterizedContainers = spec.getParameterizedContainers();
    if (spec.getStrategy() == SearchStrategy.ANCESTORS) {
        BeanFactory parent = beanFactory.getParentBeanFactory();
        Assert.isInstanceOf(ConfigurableListableBeanFactory.class, parent,
                "Unable to use SearchStrategy.ANCESTORS");
        beanFactory = (ConfigurableListableBeanFactory) parent;
    }
    MatchResult result = new MatchResult();
    // 1\. ȡ ignoreTypeֻ @ConditionalOnMissingBean 
    Set<String> beansIgnoredByType = getNamesOfBeansIgnoredByType(classLoader, beanFactory, 
            considerHierarchy, spec.getIgnoredTypes(), parameterizedContainers);

    // 2\.  types
    for (String type : spec.getTypes()) {
        Collection<String> typeMatches = getBeanNamesForType(classLoader, considerHierarchy, 
                beanFactory, type, parameterizedContainers);
        typeMatches.removeAll(beansIgnoredByType);
        if (typeMatches.isEmpty()) {
            result.recordUnmatchedType(type);
        }
        else {
            result.recordMatchedType(type, typeMatches);
        }
    }

    // 3\. ϵע @ConditionalOnMissingBean 
    for (String annotation : spec.getAnnotations()) {
        Set<String> annotationMatches = getBeanNamesForAnnotation(classLoader, beanFactory, 
                annotation, considerHierarchy);
        annotationMatches.removeAll(beansIgnoredByType);
        if (annotationMatches.isEmpty()) {
            result.recordUnmatchedAnnotation(annotation);
        }
        else {
            result.recordMatchedAnnotation(annotation, annotationMatches);
        }
    }

    // 4\.  beanName
    for (String beanName : spec.getNames()) {
        if (!beansIgnoredByType.contains(beanName) && containsBean(beanFactory, beanName, 
                considerHierarchy)) {
            result.recordMatchedName(beanName);
        }
        else {
            result.recordUnmatchedName(beanName);
        }
    }
    return result;
}

Ҫ˵ǣᴦ 3 עƥ@ConditionalOnBean``@ConditionalOnSingleCandidate @ConditionalOnMissingBean£

  1. ȡ ignoreTypeֻ @ConditionalOnMissingBean
  2. types ƥ
  3. ע⣨ϵע⣩ƥ ֻ @ConditionalOnMissingBean
  4. beanName ƥ

ϲľϸڣľͲչˣṩ̣

  1. ȡ ignoreType
    1. ʹ ListableBeanFactory#getBeanNamesForType(Class, boolean, boolean) ȡе ignoreType beanName
    2. Ϊ beansIgnoredByType( Set<String>)
  2. types ƥ 1. ʹ ListableBeanFactory#getBeanNamesForType(Class, boolean, boolean) ȡе type Ӧ beanNameΪ typeMatches 2. typeMatches еֵȥ ignoreType 3. жϵڶõ typeMatchesΪգǰ TypeunmatchedTypes У򱣴浽 matchedTypes namesOfAllMatches
  3. עƥ
    1. ʹ ListableBeanFactory#getBeanNamesForAnnotation ȡе annotation Ӧ beanNameΪ annotationMatches
    2. annotationMatches еֵȥ ignoreType
    3. жϵڶõ annotationMatchesΪգǰ AnnotationunmatchedAnnotations У򱣴浽 matchedAnnotations namesOfAllMatches
  4. beanName ƥ 1. ж beansIgnoredByType Ƿ beanName 2. ʹ BeanFactory#containsBean жи beanName 3. 2 Ϊ falseڶΪ true򽫵ǰ beanNamematchedNames namesOfAllMatches򱣴浽 unmatchedNames

õ matchedTypes``unmatchedNames ݺmatchResult.isAllMatched() matchResult.isAnyMatched() յжϽжЩṹǷգ

boolean isAllMatched() {
    return this.unmatchedAnnotations.isEmpty() && this.unmatchedNames.isEmpty()
            && this.unmatchedTypes.isEmpty();
}

boolean isAnyMatched() {
    return (!this.matchedAnnotations.isEmpty()) || (!this.matchedNames.isEmpty())
            || (!this.matchedTypes.isEmpty());
}

@ConditionalOnBean/@ConditionalOnMissingBean Ĺؼʹ ListableBeanFactory#getBeanNamesForType BeanFactory#containsBean ж beanName``beanType Ƿˡ

ʹ @ConditionalOnBean/@ConditionalOnMissingBean ʱһҪرע⣺עִʱ spring ConfigurationClassPostProcessor еģȷе˵ڽ beanbeanFactory beanDefinitionMap ֮ǰжϵģӵ beanDefinitionMap УͲӡ͵һ⣺ @ConditionalOnBean/@ConditionalOnMissingBean bean ڸ bean ֮뵽 beanDefinitionMap УпܳУ˵

@Component
@ConditionalOnMissingBean("b")
public class A {

}

@Component
public class B {

}

A B @Component spring beanȻ A ע @ConditionalOnMissingBean("b") b ʱA ŽгʼЩǰᣬ

  1. b ӵ beanDefinitionMap Уڽ a ӵ beanDefinitionMap ʱ b ѾˣǾͲˣǵԤڣ
  2. a ȱʱ beanDefinitionMap вû b a ӵ beanDefinitionMap Уٴ b``b Ҳᱻӵ beanDefinitionMapһa b ͬʱ beanDefinitionMap Уնᱻʼ spring beanǵԤڲ

ô springboot νأ @ConditionalOnBean/@ConditionalOnMissingBean ˵

΢£

ֻƥ䵽ĿǰΪֹӦóе bean ˣǿҽԶʹáѡ bean ҪһԶ´ȷʹôڴ֮С

ݣҵĽ£

  • @ConditionalOnBean/@ConditionalOnMissingBean beanbeanDefinitionMap һ̣ƥĿǰΪֹ beanDefinitionMap Ѵڵ bean֮ bean ǣпУԲοٵ a b
  • ǿҽԶʹ @ConditionalOnBean/@ConditionalOnMissingBean ע⣬Ҳ˵ԶʹõĻȷƥ
  • a b a b ֱλڲͬԶУô a Ҫ b ֮ص beanDefinitionMap Уͨ @AutoConfigureAfter``@AutoConfigureBefore``@AutoConfigureOrder עָ

Զļ˳򣬺ɡ

ƪľȵˣƪʣµע⡣


springboot עĵڶƪܽ springboot ļܽ᣺

עעж
class ע@ConditionalOnClass/@ConditionalOnMissingClassOnClassCondition
bean ע@ConditionalOnBean/@ConditionalOnMissingBeanOnBeanCondition
ע@ConditionalOnPropertyOnPropertyCondition
Resource ע@ConditionalOnResourceOnResourceCondition
Web Ӧע@ConditionalOnWebApplication / @ConditionalOnNotWebApplicationOnWebApplicationCondition
spring ʽע@ConditionalOnExpressionOnExpressionCondition

ļжϡ

5. @ConditionalOnProperty``OnPropertyCondition#getMatchOutcome

@ConditionalOnProperty Ĵ OnPropertyCondition#getMatchOutcome

class OnPropertyCondition extends SpringBootCondition {

    @Override
    public ConditionOutcome getMatchOutcome(ConditionContext context, 
            AnnotatedTypeMetadata metadata) {
        // ȡ @ConditionalOnProperty ֵ
        List<AnnotationAttributes> allAnnotationAttributes = annotationAttributesFromMultiValueMap(
                metadata.getAllAnnotationAttributes(ConditionalOnProperty.class.getName()));
        List<ConditionMessage> noMatch = new ArrayList<>();
        List<ConditionMessage> match = new ArrayList<>();
        for (AnnotationAttributes annotationAttributes : allAnnotationAttributes) {
            //  determineOutcome(...) нжϣעcontext.getEnvironment()
            ConditionOutcome outcome = determineOutcome(annotationAttributes, 
                    context.getEnvironment());
            (outcome.isMatch() ? match : noMatch).add(outcome.getConditionMessage());
        }
        if (!noMatch.isEmpty()) {
            return ConditionOutcome.noMatch(ConditionMessage.of(noMatch));
        }
        return ConditionOutcome.match(ConditionMessage.of(match));
    }

    ...

}

DZȽϼ򵥵ģǻȡ @ConditionalOnProperty ֵٵ determineOutcome(...) дٽ OnPropertyCondition#determineOutcome

/**
 * 
 * ע⣺resolver ĵ Environment applicationContext е Environment
 */
private ConditionOutcome determineOutcome(AnnotationAttributes annotationAttributes, 
        PropertyResolver resolver) {
    Spec spec = new Spec(annotationAttributes);
    List<String> missingProperties = new ArrayList<>();
    List<String> nonMatchingProperties = new ArrayList<>();
    // 
    spec.collectProperties(resolver, missingProperties, nonMatchingProperties);
    // жϽ
    if (!missingProperties.isEmpty()) {
        return ConditionOutcome.noMatch(ConditionMessage
            .forCondition(ConditionalOnProperty.class, spec)
            .didNotFind("property", "properties").items(Style.QUOTE, missingProperties));
    }
    // жϽ
    if (!nonMatchingProperties.isEmpty()) {
        return ConditionOutcome.noMatch(ConditionMessage
            .forCondition(ConditionalOnProperty.class, spec)
            .found("different value in property", "different value in properties")
            .items(Style.QUOTE, nonMatchingProperties));
    }
    // жϽ
    return ConditionOutcome.match(ConditionMessage
        .forCondition(ConditionalOnProperty.class, spec).because("matched"));
}

/**
 * 
 */
private void collectProperties(PropertyResolver resolver, List<String> missing, 
        List<String> nonMatching) {
    for (String name : this.names) {
        String key = this.prefix + name;
        // resolver  environment
        // properties жϾж environment ûӦ
        if (resolver.containsProperty(key)) {
            if (!isMatch(resolver.getProperty(key), this.havingValue)) {
                nonMatching.add(name);
            }
        }
        else {
            if (!this.matchIfMissing) {
                missing.add(name);
            }
        }
    }
}

Կ@ConditionalOnProperty ͨж environment Ƿижϵġ

6. @ConditionalOnResource``OnResourceCondition#getMatchOutcome

@ConditionalOnResource Ĵһʹã

@Bean
@ConditionalOnResource(resources = "classpath:config.properties")
public Config config() {
    return config;
}

ʾ classpath д config.properties ʱconfig Żᱻʼ springbean

ٽ OnResourceCondition#getOutcomes

@Override
public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
    MultiValueMap<String, Object> attributes = metadata
            .getAllAnnotationAttributes(ConditionalOnResource.class.getName(), true);
    // ȡ ResourceLoader
    ResourceLoader loader = context.getResourceLoader();
    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) {
        // location пռλﴦ
        String resource = context.getEnvironment().resolvePlaceholders(location);
        // ж resource Ƿ
        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));
}

ͨ OnResourceCondition#getOutcomes ȡ ResourceLoaderͨԷʽֵǰ ResourceLoader Ϊ AnnotationConfigServletWebServerApplicationContext

ȡ ResourceLoader 󣬵 ResourceLoader#getResource(String) ȡԴȻ Resource#exists жԴǷڣƥ

̵Ĺؼ ResourceLoader#getResource(String)÷Ĵ뵽 GenericApplicationContext#getResource

@Override
public Resource getResource(String location) {
    if (this.resourceLoader != null) {
        return this.resourceLoader.getResource(location);
    }
    return super.getResource(location);
}

this.resourceLoader Ϊ null븸ķ DefaultResourceLoader#getResource

public Resource getResource(String location) {
    Assert.notNull(location, "Location must not be null");
    for (ProtocolResolver protocolResolver : getProtocolResolvers()) {
        Resource resource = protocolResolver.resolve(location, this);
        if (resource != null) {
            return resource;
        }
    }
    // /ͷԴ
    if (location.startsWith("/")) {
        return getResourceByPath(location);
    }
    else if (location.startsWith(CLASSPATH_URL_PREFIX)) {
        // classpathͷԴ
        return new ClassPathResource(
            location.substring(CLASSPATH_URL_PREFIX.length()), getClassLoader());
    }
    else {
        try {
            // ϶㣬ʹ url 
            URL url = new URL(location);
            return (ResourceUtils.isFileURL(url) 
                ? new FileUrlResource(url) : new UrlResource(url));
        }
        catch (MalformedURLException ex) {
            // url⣬ջ getResourceByPath(...) 
            return getResourceByPath(location);
        }
    }
}

/**
 * ͨ·õ Resource
 */
protected Resource getResourceByPath(String path) {
    return new ClassPathContextResource(path, getClassLoader());
}

ԿDefaultResourceLoader#getResource ͨж location ǰ׺õ 4 Resource

  • ClassPathContextResource
  • FileUrlResource
  • UrlResource

õ Resource 󣬽žжϸ Resource Ƿˣ ClassPathContextResource#exist ÷ ClassPathResource#exists

/**
 * ж Resource Ƿ
 */
@Override
public boolean exists() {
    return (resolveURL() != null);
}

/**
 * Դܻȡ򷵻ԴӦurl򷵻null
 */
@Nullable
protected URL resolveURL() {
    if (this.clazz != null) {
        // ʹõǰ class Ӧ classLoader ȡ
        return this.clazz.getResource(this.path);
    }
    else if (this.classLoader != null) {
        // ʹָ classLoader ȡ
        return this.classLoader.getResource(this.path);
    }
    else {
        // ȡϵͳȡ
        return ClassLoader.getSystemResource(this.path);
    }
}

ӴԿͨ classLoader ȡļ urlͨжļ url ǷΪ null ж resource Ƿڡ

FileUrlResource жϣʵ FileUrlResource UrlResource exist() AbstractFileResolvingResource#existsͳһͿˣ÷£

public boolean exists() {
    try {
        URL url = getURL();
        if (ResourceUtils.isFileURL(url)) {
            // ļֱжļǷ
            return getFile().exists();
        }
        else {
            // ʹļ
            URLConnection con = url.openConnection();
            customizeConnection(con);
            HttpURLConnection httpCon =
                    (con instanceof HttpURLConnection ? (HttpURLConnection) con : null);
            // httpжϿӷص״̬
            if (httpCon != null) {
                int code = httpCon.getResponseCode();
                if (code == HttpURLConnection.HTTP_OK) {
                    return true;
                }
                else if (code == HttpURLConnection.HTTP_NOT_FOUND) {
                    return false;
                }
            }
            //  contentLengthLong 0Ҳtrue
            if (con.getContentLengthLong() > 0) {
                return true;
            }
            if (httpCon != null) {
                httpCon.disconnect();
                return false;
            }
            else {
                getInputStream().close();
                return true;
            }
        }
    }
    catch (IOException ex) {
        return false;
    }
}

DZļֱʹ File#exists() жļǷڣжļǷڣжϷʽͲϸ˵ˡ

ܵ˵springboot @ConditionalOnResource жϻЩӵģܽ£

  1. classpath ļͨ classloader ȡļӦ url ǷΪ null жļǷڣ
  2. ͨļֱ File#exists() жļǷڣ
  3. ļȴһӣжļǷڡ

7. @ConditionalOnWebApplication``OnWebApplicationCondition#getMatchOutcome

@ConditionalOnWebApplication Ĵ OnWebApplicationCondition#getOutcomes

@Override
protected ConditionOutcome[] getOutcomes(String[] autoConfigurationClasses,
        AutoConfigurationMetadata autoConfigurationMetadata) {
    ConditionOutcome[] outcomes = new ConditionOutcome[autoConfigurationClasses.length];
    for (int i = 0; i < outcomes.length; i++) {
        String autoConfigurationClass = autoConfigurationClasses[i];
        if (autoConfigurationClass != null) {
            // 
            outcomes[i] = getOutcome(autoConfigurationMetadata.get(autoConfigurationClass, 
                "ConditionalOnWebApplication"));
        }
    }
    return outcomes;
}

/**
 * 
 * springbootֵ֧web֣SERVLETREACTIVE
 */
private ConditionOutcome getOutcome(String type) {
    if (type == null) {
        return null;
    }
    ConditionMessage.Builder message = ConditionMessage
            .forCondition(ConditionalOnWebApplication.class);
    // ָ SERVLET
    if (ConditionalOnWebApplication.Type.SERVLET.name().equals(type)) {
        if (!ClassNameFilter.isPresent(SERVLET_WEB_APPLICATION_CLASS, getBeanClassLoader())) {
            return ConditionOutcome.noMatch(
                message.didNotFind("servlet web application classes").atAll());
        }
    }
    // ָ REACTIVE
    if (ConditionalOnWebApplication.Type.REACTIVE.name().equals(type)) {
        if (!ClassNameFilter.isPresent(REACTIVE_WEB_APPLICATION_CLASS, getBeanClassLoader())) {
            return ConditionOutcome.noMatch(
                message.didNotFind("reactive web application classes").atAll());
        }
    }
    // ûָweb
    if (!ClassNameFilter.isPresent(SERVLET_WEB_APPLICATION_CLASS, getBeanClassLoader())
            && !ClassUtils.isPresent(REACTIVE_WEB_APPLICATION_CLASS, getBeanClassLoader())) {
        return ConditionOutcome.noMatch(
            message.didNotFind("reactive or servlet web application classes").atAll());
    }
    return null;
}

ܼ򵥣߼Ϊ @ConditionalOnWebApplication ָͣж϶ӦǷڣжϷʽ @ConditionalOnClass жǷһ£ͶӦ£

  • Servletorg.springframework.web.context.support.GenericWebApplicationContext
  • Reactiveorg.springframework.web.reactive.HandlerResult

8. @ConditionalOnExpression``OnExpressionCondition#getMatchOutcome

@ConditionalOnExpression Ĵ OnExpressionCondition#getOutcomes

/**
 * ƥ
 */
@Override
public ConditionOutcome getMatchOutcome(ConditionContext context, 
        AnnotatedTypeMetadata metadata) {
    // ȡʽ
    String expression = (String) metadata.getAnnotationAttributes(
            ConditionalOnExpression.class.getName()).get("value");
    expression = wrapIfNecessary(expression);
    ConditionMessage.Builder messageBuilder = ConditionMessage
            .forCondition(ConditionalOnExpression.class, "(" + expression + ")");
    // ռλ
    expression = context.getEnvironment().resolvePlaceholders(expression);
    ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
    if (beanFactory != null) {
        // ʽֵ
        boolean result = evaluateExpression(beanFactory, expression);
        return new ConditionOutcome(result, messageBuilder.resultedIn(result));
    }
    return ConditionOutcome.noMatch(messageBuilder.because("no BeanFactory available."));
}

/**
 * ʽֵ
 */
private Boolean evaluateExpression(ConfigurableListableBeanFactory beanFactory, 
        String expression) {
    BeanExpressionResolver resolver = beanFactory.getBeanExpressionResolver();
    if (resolver == null) {
        resolver = new StandardBeanExpressionResolver();
    }
    // ʽֵ
    BeanExpressionContext expressionContext = new BeanExpressionContext(beanFactory, null);
    Object result = resolver.evaluate(expression, expressionContext);
    return (result != null && (boolean) result);
}

Կspringboot ͨ BeanExpressionResolver#evaluate ʽ spring ʽľͲչˡ

ˣspring עķ͵ˣҪ˵ǣspringboot ע⣺

ЩעжϷʽ뱾ĵķʽƣͲһһзˡ


ԭӣhttps://my.oschina.net/funcy/blog/4921590 ߸ˮƽд֮ӭָԭףҵתϵ߻Ȩҵתע