博客索引java
Spring提供了一个基于条件的bean建立(@Conditional)。@Conditional
根据知足的某一特定条件建立一个特定的bean。条件注解的注解太多了,好比@ConditionalOnBean
,@ConditionalOnMissingBean
,@ConditionalOnProperty
等等都在包org.springframework.boot.autoconfigure.condition
下面。spring
我就以这个@ConditionalOnProperty
为例子来说解,只要这个注解原理弄清楚了,一通百通。bash
建立一个类以下,若是applicaiton.properties
里面包含study.enable=true,那么该类会被建立。app
@Configuration
@ConditionalOnProperty(prefix = "study", name = "enable", havingValue = "true")
public class HelloServiceAutoConfiguration {
}
复制代码
ConditionalController
验证@RestController
public class ConditionalController {
@Autowired
private HelloServiceAutoConfiguration autoConfiguration;
}
复制代码
如今咱们的applicaiton.properties
配置文件里面是没有study.enable=true这行配置,咱们启动Spring Boot项目,会发现报错。咱们在配置文件中加入study.enable=true这行配置,项目正常运行。ide
话很少说,先看看@ConditionalOnProperty
源码。工具
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.TYPE, ElementType.METHOD })
@Documented
@Conditional(OnPropertyCondition.class)
public @interface ConditionalOnProperty {
String[] value() default {};
String prefix() default "";
String[] name() default {};
String havingValue() default "";
//缺省值
boolean matchIfMissing() default false;
}
复制代码
好比咱们看看Eureka的自动配置类EurekaClientAutoConfiguration
post
图片中标记的这行注解意思是,当application.properties中或者环境变量中配置了eureka.client.enabled=true或者不配置(不配置的话,缺省值matchIfMissing也是true),都会初始化这个类。只有当你显示的配置eureka.client.enabled=false的时候,才不会初始化这个类。因此在Eureka Client2.2.3中,不添加注解@EnableDiscoveryClient
也是能够向Eureka Server注册的。咱们会发现,在Cloud中会有不少这种条件注解的使用,因此这些基础东西必须熟练掌握,才能玩转Cloud体系。测试
其实咱们会发现,条件注解都是基于@Conditional
实现的,那么看看@Conditional
源码ui
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Conditional {
/**
* All {@link Condition Conditions} that must {@linkplain Condition#matches match}
* in order for the component to be registered.
*/
Class<? extends Condition>[] value();
}
复制代码
注释说的很清楚,Condition必须知足match方法才能使component注册,查看Condition
源码this
@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匹配返回true,这个组件就会被注册,不然就不会注册。也就是须要继承这个Condition
,实现该matches方法,来自定义条件匹配。那么ConditionalOnProperty
是如何跟Condition
关联上的呢?咱们能够看到ConditionalOnProperty
上面的注解@Conditional(OnPropertyCondition.class)
,看看类OnPropertyCondition
的源码。
@Order(Ordered.HIGHEST_PRECEDENCE + 40)
class OnPropertyCondition extends SpringBootCondition {
@Override
public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
List<AnnotationAttributes> allAnnotationAttributes = annotationAttributesFromMultiValueMap(
metadata.getAllAnnotationAttributes(ConditionalOnProperty.class.getName()));
List<ConditionMessage> noMatch = new ArrayList<>();
List<ConditionMessage> match = new ArrayList<>();
for (AnnotationAttributes annotationAttributes : allAnnotationAttributes) {
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));
}
···先省略其余代码
}
复制代码
发现这个类中并无matches方法,利用快捷键搜索方法matches,发现这个方法在类SpringBootCondition
中 利用idea工具,查看OnPropertyCondition
的结构
OnPropertyCondition
继承了
SpringBootCondition
,而类是一个抽象类,而且是一个模板类,源码以下:
public abstract class SpringBootCondition implements Condition {
@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 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();
}
复制代码
public abstract ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata);
复制代码
protected final void logOutcome(String classOrMethodName, ConditionOutcome outcome) {
if (this.logger.isTraceEnabled()) {
this.logger.trace(getLogMessage(classOrMethodName, outcome));
}
}
复制代码
private void recordEvaluation(ConditionContext context, String classOrMethodName, ConditionOutcome outcome) {
if (context.getBeanFactory() != null) {
ConditionEvaluationReport.get(context.getBeanFactory()).recordConditionEvaluation(classOrMethodName, this,
outcome);
}
}
复制代码
咱们来看一下类ConditionOutcome
信息。条件匹配结果里面包含了macth,和log信息,因此返回outcome.isMatch()就是返回ConditionOutcome
中的match值,只有这步和第②步很关键,前面几步都是记录日志等。
public class ConditionOutcome {
private final boolean match;
private final ConditionMessage message;
···
}
复制代码
看到这里,估计大部分人都有些迷糊了,咱们在理理思路。SpringBootCondition
是一个模板类,只有第②步getMatchOutcome(ConditionContext, AnnotatedTypeMetadata)
是个抽象类留个子类实现,其余的都已经实现好了,那么只须要看下类OnPropertyCondition
中的getMatchOutcome(ConditionContext, AnnotatedTypeMetadata)
便可。
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) {
// 核心代码: 肯定ConditionOutcome
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));
}
复制代码
肯定ConditionOutcome
/* annotationAttributes这是是从注解里面读出来的值
* resolver 这个理解为从配置文件中取值,也就是从application.properties中取值
*/
private ConditionOutcome determineOutcome(AnnotationAttributes annotationAttributes, PropertyResolver resolver) {
//将annotationAttributes包装成Spec类
Spec spec = new Spec(annotationAttributes);
//
List<String> missingProperties = new ArrayList<>();
List<String> nonMatchingProperties = new ArrayList<>();
// 将spec与resolver进行匹配,看属性值是否匹配
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) {
// key在这里等于study.enable,由于以前将属性包装成Spec类,这个类将prefix = prefix + "."
String key = this.prefix + name;
// 判断环境属性中是否包含这个key,若是application.properties配置了study.enable=true,
// 那么这里返回true,那么列表nonMatching与missing都为null
if (resolver.containsProperty(key)) {
if (!isMatch(resolver.getProperty(key), this.havingValue)) {
nonMatching.add(name);
}
}
// 若是返回false
else {
// 继续判断matchIfMissing值,默认为false,加入missing列表。
if (!this.matchIfMissing) {
missing.add(name);
}
}
}
}
复制代码
最后来看看ConditionOutcome
的match方法与noMatch方法。原来match方法就是返回true,nomatch方法就是返回false,
public static ConditionOutcome match(ConditionMessage message) {
return new ConditionOutcome(true, message);
}
public static ConditionOutcome noMatch(ConditionMessage message) {
return new ConditionOutcome(false, message);
}
复制代码
那么如今已经解析完毕了。最后看一下Spring Boot是在哪里调用的?
ConfigurationClassParser#processConfigurationClass
protected void processConfigurationClass(ConfigurationClass configClass, Predicate<String> filter) throws IOException {
if (this.conditionEvaluator.shouldSkip(configClass.getMetadata(), ConfigurationPhase.PARSE_CONFIGURATION)) {
return;
}
ConfigurationClass existingClass = this.configurationClasses.get(configClass);
if (existingClass != null) {
if (configClass.isImported()) {
if (existingClass.isImported()) {
existingClass.mergeImportedBy(configClass);
}
// Otherwise ignore new imported config class; existing non-imported class overrides it.
return;
}
else {
this.configurationClasses.remove(configClass);
this.knownSuperclasses.values().removeIf(configClass::equals);
}
}
// Recursively process the configuration class and its superclass hierarchy.
SourceClass sourceClass = asSourceClass(configClass, filter);
do {
sourceClass = doProcessConfigurationClass(configClass, sourceClass, filter);
}
while (sourceClass != null);
this.configurationClasses.put(configClass, configClass);
}
复制代码
这里太熟悉了,由于前几天@ComponentSacn的解析就是在这行代码里。
sourceClass = doProcessConfigurationClass(configClass, sourceClass, filter);
复制代码
代码的第一行就是整个@Conditional
的切入点,若是验证不经过,那么会忽略后面的解析逻辑,那么这个类的其余属性以及@ComponentSacn之类的配置都不会获得解析。这个方法会获取类上的@Conditional
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 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();
}
// 终于看到了condition的matches,这里直接跳到SpringBootCondition的matches方法,造成闭环,解析完成
if ((requiredPhase == null || requiredPhase == phase) && !condition.matches(this.context, metadata)) {
return true;
}
}
return false;
}
复制代码
若是有地方有疑惑或者写的有很差,能够评论或者经过邮箱联系我creazycoder@sina.com
文章参考:
《Spring源码深度解析》