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 Condition}s 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。
菜鳥不易,望有問題指出,共同進步