博客索引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源碼深度解析》