springboot条件注解源码分析

Posted Small leaf

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了springboot条件注解源码分析相关的知识,希望对你有一定的参考价值。

上篇我们讲到springboot注解之:@EnableAutoConfiguration,主要讲到springboot如何自动加载配置,核心类是ConfigurationClassPostProcessor。在spring容器启动的时候去扫描有@Component的类,然后对扫描到的类进行判断是否有条件注解,有的话判断是否通过能进行类的进一步解析和注册。

这里就提到了条件注解,来看看具体使用到的地方:

ConfigurationClassPostProcessor.postProcessBeanDefinitionRegistry-->
ConfigurationClassPostProcessor.processConfigBeanDefinitions-->
processConfigBeanDefinitions.parse-->
processConfigBeanDefinitions.processConfigurationClass


protected void processConfigurationClass(ConfigurationClass configClass) throws IOException 
        if (this.conditionEvaluator.shouldSkip(configClass.getMetadata(), ConfigurationPhase.PARSE_CONFIGURATION)) 
            return;
        

this.conditionEvaluator.shouldSkip(configClass.getMetadata(), ConfigurationPhase.PARSE_CONFIGURATION)这里进行条件注解的判断。

ConditionEvaluator类就是用来进行条件注解处理的类

public boolean shouldSkip(AnnotatedTypeMetadata metadata, ConfigurationPhase phase) 
        //如果注解为空或者注解中没有使用Conditional则跳过
        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) 
                //使用注解使用的类进行匹配判断,如果匹配成功则返回true跳过
                if (!condition.matches(this.context, metadata)) 
                    return true;
                
            
        

        return false;
    

if (metadata == null || !metadata.isAnnotated(Conditional.class.getName()))
这句话的意思就是判断是有注解,并且注解是Conditional,才进行后面的处理,我们来看看几个常用的注解

条件注解对应的Condition处理类处理逻辑
@ConditionalOnBeanOnBeanConditionSpring容器中是否存在对应的实例。可以通过实例的类型、类名、注解、昵称去容器中查找(可以配置从当前容器中查找或者父容器中查找或者两者一起查找)这些属性都是数组,通过”与”的关系进行查找
@ConditionalOnClassOnClassCondition类加载器中是否存在对应的类。可以通过Class指定(value属性)或者Class的全名指定(name属性)。如果是多个类或者多个类名的话,关系是”与”关系,也就是说这些类或者类名都必须同时在类加载器中存在
@ConditionalOnExpressionOnExpressionCondition判断SpEL 表达式是否成立
@ConditionalOnJavaOnJavaCondition指定Java版本是否符合要求。内部有2个属性value和range。value表示一个枚举的Java版本,range表示比这个老或者新于等于指定的Java版本(默认是新于等于)。内部会基于某些jdk版本特有的类去类加载器中查询,比如如果是jdk9,类加载器中需要存在java.security.cert.URICertStoreParameters;如果是jdk8,类加载器中需要存在java.util.function.Function;如果是jdk7,类加载器中需要存在java.nio.file.Files;如果是jdk6,类加载器中需要存在java.util.ServiceLoader
@ConditionalOnMissingBeanOnBeanConditionSpring容器中是否缺少对应的实例。可以通过实例的类型、类名、注解、昵称去容器中查找(可以配置从当前容器中查找或者父容器中查找或者两者一起查找)这些属性都是数组,通过”与”的关系进行查找。还多了2个属性ignored(类名)和ignoredType(类名),匹配的过程中会忽略这些bean
@ConditionalOnMissingClassOnClassCondition跟ConditionalOnClass的处理逻辑一样,只是条件相反,在类加载器中不存在对应的类
@ConditionalOnNotWebApplicationOnWebApplicationCondition应用程序是否是非Web程序,没有提供属性,只是一个标识。会从判断Web程序特有的类是否存在,环境是否是Servlet环境,容器是否是Web容器等
@ConditionalOnPropertyOnPropertyCondition应用环境中的屬性是否存在。提供prefix、name、havingValue以及matchIfMissing属性。prefix表示属性名的前缀,name是属性名,havingValue是具体的属性值,matchIfMissing是个boolean值,如果属性不存在,这个matchIfMissing为true的话,会继续验证下去,否则属性不存在的话直接就相当于匹配不成功
@ConditionalOnResourceOnResourceCondition是否存在指定的资源文件。只有一个属性resources,是个String数组。会从类加载器中去查询对应的资源文件是否存在
@ConditionalOnSingleCandidateOnBeanConditionSpring容器中是否存在且只存在一个对应的实例。只有3个属性value、type、search。跟ConditionalOnBean中的这3种属性值意义一样
@ConditionalOnWebApplicationOnWebApplicationCondition应用程序是否是Web程序,没有提供属性,只是一个标识。会从判断Web程序特有的类是否存在,环境是否是Servlet环境,容器是否是Web容器等

所有的条件注解都是使用了@Conditional

 */
@Target(ElementType.TYPE, ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Conditional 

    /**
     * All @link Conditions that must @linkplain Condition#matches match
     * in order for the component to be registered.
     */
    Class<? extends Condition>[] value();

所有的对应的处理类都是实现了Condition接口。

针对接口编程,关闭开放原则,spring设计的非常巧妙

到这里如果要你实现一个条件注解,你该怎么做?

1.自己写一个注解使用@Condition注解。
2.实现一个对应的处理类实现Condition接口。

针对接口编程的好处体现出来了,spring设计的非常精妙处之一它的关闭开放原则,扩展起来非常容易

继续看shouldSkip方法,在使用condition.matches,我们的springboot中条件注解使用的类继承了SpringBootCondition,该类实现了Condition接口,来看看SpringBootCondition

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

ConditionOutcome outcome = getMatchOutcome(context, metadata);
其中getMatchOutcome是一个抽象类,将它的实现放在子类,这样可以让子类拥有自己的实现。

这里用到了模板模式,getMatchOutcome是一个模板方法,具体的实现延迟到子类,然后matches使用这个模板方法。

以ConditionalOnBean为例来进行分析
如果spring容器中有该类的时候就会进行注册,看它的处理类OnBeanCondition

@Override
    public ConditionOutcome getMatchOutcome(ConditionContext context,
            AnnotatedTypeMetadata metadata) 
        ConditionMessage matchMessage = ConditionMessage.empty();
        //如果注解为ConditionalOnBean处理逻辑
        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);
    

这里有很多注解类都使用了OnBeanCondition

private List<String> getMatchingBeans(ConditionContext context,
            BeanSearchSpec beans) 
        //获得spring容器
        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)));
        
        //下面就判断,spring容器当中是否有该类的实现,有的话就加入
        for (String beanName : beans.getNames()) 
            if (containsBean(beanFactory, beanName, considerHierarchy)) 
                beanNames.add(beanName);
            
        
        return beanNames;
    

如果beanNames存在则返回

matchMessage = matchMessage.andCondition(ConditionalOnBean.class, spec)
                    .found("bean", "beans").items(Style.QUOTE, matching);

否则返回

return ConditionOutcome.noMatch(
                        ConditionMessage.forCondition(ConditionalOnBean.class, spec)
                                .didNotFind("any beans").atAll());
public static ConditionOutcome match(ConditionMessage message) 
        return new ConditionOutcome(true, message);
    

    /**
     * Create a new @link ConditionOutcome instance for 'no match'. For more consistent
     * messages consider using @link #noMatch(ConditionMessage).
     * @param message the message
     * @return the @link ConditionOutcome
     */
    public static ConditionOutcome noMatch(String message) 
        return new ConditionOutcome(false, message);
    

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

在SpringBootCondition.matches中最终返回
return outcome.isMatch();
也就是匹配成不成功。
成功就能该类能进行解析注册。
其他的看下相应的处理类即可。

总结下,在扫描类时,先要判断该类是否能通过条件注解。
条件注解都使用了@Conditional,相应的处理类实现了Condition接口。在该接口matches方法中进行对应的逻辑处理。

上一讲是讲解自动配置,这一讲讲解的是条件注解。
下一讲讲解一个实例,手动实现一个类,使用springboot自动配置,并且结合条件注解。

需求:构建一个redis配置工具,如果存在jedis,redistemplate,redis.propterties才能实现自动配置。以后只需要导入jar,配置redis.propterties即可直接使用redisTemplate。


菜鸟不易,望有问题指出,共同进步

以上是关于springboot条件注解源码分析的主要内容,如果未能解决你的问题,请参考以下文章

SpringBoot是如何实现自动配置的?--SpringBoot源码

spring源码分析:spring生命周期

Spring Boot源码分析@EnableAutoConfiguration注解@AutoConfigurationImportSelector注解的处理

SpringBoot自动配置原理(源码分析)

Springboot源码分析之TypeFilter魔力

springboot情操陶冶-@Conditional和@AutoConfigureAfter注解解析