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处理类 | 处理逻辑 |
---|---|---|
@ConditionalOnBean | OnBeanCondition | Spring容器中是否存在对应的实例。可以通过实例的类型、类名、注解、昵称去容器中查找(可以配置从当前容器中查找或者父容器中查找或者两者一起查找)这些属性都是数组,通过”与”的关系进行查找 |
@ConditionalOnClass | OnClassCondition | 类加载器中是否存在对应的类。可以通过Class指定(value属性)或者Class的全名指定(name属性)。如果是多个类或者多个类名的话,关系是”与”关系,也就是说这些类或者类名都必须同时在类加载器中存在 |
@ConditionalOnExpression | OnExpressionCondition | 判断SpEL 表达式是否成立 |
@ConditionalOnJava | OnJavaCondition | 指定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 |
@ConditionalOnMissingBean | OnBeanCondition | Spring容器中是否缺少对应的实例。可以通过实例的类型、类名、注解、昵称去容器中查找(可以配置从当前容器中查找或者父容器中查找或者两者一起查找)这些属性都是数组,通过”与”的关系进行查找。还多了2个属性ignored(类名)和ignoredType(类名),匹配的过程中会忽略这些bean |
@ConditionalOnMissingClass | OnClassCondition | 跟ConditionalOnClass的处理逻辑一样,只是条件相反,在类加载器中不存在对应的类 |
@ConditionalOnNotWebApplication | OnWebApplicationCondition | 应用程序是否是非Web程序,没有提供属性,只是一个标识。会从判断Web程序特有的类是否存在,环境是否是Servlet环境,容器是否是Web容器等 |
@ConditionalOnProperty | OnPropertyCondition | 应用环境中的屬性是否存在。提供prefix、name、havingValue以及matchIfMissing属性。prefix表示属性名的前缀,name是属性名,havingValue是具体的属性值,matchIfMissing是个boolean值,如果属性不存在,这个matchIfMissing为true的话,会继续验证下去,否则属性不存在的话直接就相当于匹配不成功 |
@ConditionalOnResource | OnResourceCondition | 是否存在指定的资源文件。只有一个属性resources,是个String数组。会从类加载器中去查询对应的资源文件是否存在 |
@ConditionalOnSingleCandidate | OnBeanCondition | Spring容器中是否存在且只存在一个对应的实例。只有3个属性value、type、search。跟ConditionalOnBean中的这3种属性值意义一样 |
@ConditionalOnWebApplication | OnWebApplicationCondition | 应用程序是否是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 Boot源码分析@EnableAutoConfiguration注解@AutoConfigurationImportSelector注解的处理