@ConditionalOnClass注解解析
springboot中各种`@ConditionalXxx`注解控制着Bean是否注册,只有满足了一定条件才会被注册到容器中。这些注解包含`@ConditionalOnClass、@OnBeanCondition、@ConditionalOnProperty`等等,这篇文章就和大家探究下这些`@ConditionalXxx`注解到底是如何生效的,我会试着分析其中一个注解`@ConditionalO
概要
springboot中各种@ConditionalOnXxx
注解控制着Bean是否注册,只有满足了一定条件才会被注册到容器中。这些注解包含@ConditionalOnClass、@ConditionalOnBean、@ConditionalOnProperty
等等,这篇文章就和大家探究下这些@ConditionalOnXxx
注解到底是如何生效的,我会试着分析其中一个注解@ConditionalOnClass
生效的规则,只要看懂一个,其余的@ConditionalOnXxx
注解生效规则各位自己就可以看懂分析。看此篇文章需要一定的spring容器的基础,需要了解ConfigurationClassPostProcessor
这个类加载bean的逻辑(将从这个类开始分析,前面过程略过),看完此篇文件你会知道@Conditional
是如何生效的,更快的了解@ConditionalOnXxx
的作用和原理。
Bean注册过程
这个过程分两步:
1、spring容器ApplicationContext
在启动的过程中会根据类路径进行扫描,扫描到类上面定义了@Component
注解的类(注意这里的类上面定义@Component
并非是必须显示定义,也可以通过一些注解带入,例如@Service
注解标记在业务类上也会被注册的原因是@Service
注解本身定义里面是携带了@Component
注解,会被spring提取),会把这些类作为spring创建bean的来源。
2、@Conditional
的value属性会导入一个class数组。有了bean的来源,如果此类上定义了@Conditional
注解,那么需要匹配导入的class类中的matches
方法,如果所有的都匹配成功,那么会注册这个bean,如果有一个没匹配上,那么不会注册此bean。
ConfigurationClassParser.processConfigurationClass
方法
protected void processConfigurationClass(ConfigurationClass configClass, Predicate<String> filter) throws IOException {
if (!this.conditionEvaluator.shouldSkip(configClass.getMetadata(), ConfigurationPhase.PARSE_CONFIGURATION)) {
ConfigurationClass existingClass = (ConfigurationClass)this.configurationClasses.get(configClass);
if (existingClass != null) {
if (configClass.isImported()) {
if (existingClass.isImported()) {
existingClass.mergeImportedBy(configClass);
}
return;
}
this.configurationClasses.remove(configClass);
this.knownSuperclasses.values().removeIf(configClass::equals);
}
SourceClass sourceClass = this.asSourceClass(configClass, filter);
do {
sourceClass = this.doProcessConfigurationClass(configClass, sourceClass, filter);
} while(sourceClass != null);
this.configurationClasses.put(configClass, configClass);
}
}
每个bean的来源都要进入ConfigurationClassParser.processConfigurationClass
方法(特殊bean除外),只有加入到this.configurationClasses
中的类才能被注册为bean(后面会针对这个类生成BeanDefinition)。所以这里的if判断就成了关键,如果if判断能为true(需要shouldSkip方法返回是false),类才会真的加入到this.configurationClasses.put(configClass, configClass);
中,否则此类将被跳过注册。
public boolean shouldSkip(@Nullable AnnotatedTypeMetadata metadata, @Nullable ConfigurationCondition.ConfigurationPhase phase) {
if (metadata != null && metadata.isAnnotated(Conditional.class.getName())) {
if (phase == null) {
return metadata instanceof AnnotationMetadata && ConfigurationClassUtils.isConfigurationCandidate((AnnotationMetadata)metadata) ? this.shouldSkip(metadata, ConfigurationPhase.PARSE_CONFIGURATION) : this.shouldSkip(metadata, ConfigurationPhase.REGISTER_BEAN);
} else {
List<Condition> conditions = new ArrayList();
Iterator var4 = this.getConditionClasses(metadata).iterator();
while(var4.hasNext()) {
String[] conditionClasses = (String[])var4.next();
String[] var6 = conditionClasses;
int var7 = conditionClasses.length;
for(int var8 = 0; var8 < var7; ++var8) {
String conditionClass = var6[var8];
Condition condition = this.getCondition(conditionClass, this.context.getClassLoader());
conditions.add(condition);
}
}
AnnotationAwareOrderComparator.sort(conditions);
var4 = conditions.iterator();
Condition condition;
ConfigurationCondition.ConfigurationPhase requiredPhase;
do {
do {
if (!var4.hasNext()) {
return false;
}
condition = (Condition)var4.next();
requiredPhase = null;
if (condition instanceof ConfigurationCondition) {
requiredPhase = ((ConfigurationCondition)condition).getConfigurationPhase();
}
} while(requiredPhase != null && requiredPhase != phase);
} while(condition.matches(this.context, metadata));
return true;
}
} else {
return false;
}
}
进入shouldSkip方法会发现判断的依据正是是否在类上定义了@Conditional
注解,如果定义了此注解,那么会取出value属性导入的所有的class对象(class对象是Condition
的子类),并把这些对象实例化,排序之后依次调用condition.matches
判断的匹配结果,如果都匹配上了那么返回false,加入到configurationClasses
中,只要有一个没匹配上那么会被跳过。
@ConditionalOnClass注解
springboot自动配置模块导入了很多 @ConditionalOnXxx
注解注解,例如@ConditionalOnClass
、@OnBeanCondition
、@ConditionalOnProperty
等等
看起来像是@Conditional
注解的亲戚,实际上当我们随便点击进去一个注解就会发现,这个注解本身导入了@Conditional
注解,而每个@Conditional
注解引入的class对象并不相同,例如@ConditionalOnClass
注解导入的类是OnClassCondition.class
,@OnBeanCondition
导入的是OnBeanCondition.class
。
@ConditionalOnClass
注解
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional({OnClassCondition.class})
public @interface ConditionalOnClass {
Class<?>[] value() default {};
String[] name() default {};
}
上面我们分析类上面定义了@Conditional
注解会被识别到,@ConditionalOnClass
本身导入了@Conditional
注解,所以类上面标记了@ConditionalOnClass
也会被spring提取出来,提取的正是这个value属性,那么OnClassCondition.class
类作为真正的匹配条件判断类。会去调用OnClassCondition
类的matches
方法,但是OnClassCondition
类本身并未实现该方法,它的实现方法在其父类SpringBootCondition
中实现。
public final boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
String classOrMethodName = getClassOrMethodName(metadata);
try {
ConditionOutcome outcome = this.getMatchOutcome(context, metadata);
this.logOutcome(classOrMethodName, outcome);
this.recordEvaluation(context, classOrMethodName, outcome);
return outcome.isMatch();
} catch (NoClassDefFoundError var5) {
throw new IllegalStateException("Could not evaluate condition on " + classOrMethodName + " due to " + var5.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)", var5);
} catch (RuntimeException var6) {
throw new IllegalStateException("Error processing condition on " + this.getName(metadata), var6);
}
}
这里的outcome.isMatch()
决定了匹配结果,点进去会发现就是返回了this.match
属性。在构建对象outcome
中实际会调用子类OnClassCondition.getMatchOutcome
方法。
public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
ClassLoader classLoader = context.getClassLoader();
ConditionMessage matchMessage = ConditionMessage.empty();
List<String> onClasses = this.getCandidates(metadata, ConditionalOnClass.class);
List onMissingClasses;
if (onClasses != null) {
onMissingClasses = this.filter(onClasses, ClassNameFilter.MISSING, classLoader);
if (!onMissingClasses.isEmpty()) {
return ConditionOutcome.noMatch(ConditionMessage.forCondition(ConditionalOnClass.class, new Object[0]).didNotFind("required class", "required classes").items(Style.QUOTE, onMissingClasses));
}
matchMessage = matchMessage.andCondition(ConditionalOnClass.class, new Object[0]).found("required class", "required classes").items(Style.QUOTE, this.filter(onClasses, ClassNameFilter.PRESENT, classLoader));
}
onMissingClasses = this.getCandidates(metadata, ConditionalOnMissingClass.class);
if (onMissingClasses != null) {
List<String> present = this.filter(onMissingClasses, ClassNameFilter.PRESENT, classLoader);
if (!present.isEmpty()) {
return ConditionOutcome.noMatch(ConditionMessage.forCondition(ConditionalOnMissingClass.class, new Object[0]).found("unwanted class", "unwanted classes").items(Style.QUOTE, present));
}
matchMessage = matchMessage.andCondition(ConditionalOnMissingClass.class, new Object[0]).didNotFind("unwanted class", "unwanted classes").items(Style.QUOTE, this.filter(onMissingClasses, ClassNameFilter.MISSING, classLoader));
}
return ConditionOutcome.match(matchMessage);
}
this.getCandidates(metadata, ConditionalOnClass.class)
这个方法会找出该类上@ConditionalOnClass
注解导入的value和name并进行合并成onClasses
,如果onClasses
不为空那么执行filter
方法,filter
方法返回的onMissingClasses
不为空的话直接返回ConditionOutcome.noMatch
构建的ConditionOutcome
对象,这个构建方法里面直接设置了match
属性为false,匹配失败,该bean会被跳过。如果代码走到了ConditionOutcome.match(matchMessage);
那么证明匹配成功,则match属性会被设置为true,该bean会被注册。
public static ConditionOutcome noMatch(ConditionMessage message) {
return new ConditionOutcome(false, message);
}
OnClassCondition.filter
方法
protected final List<String> filter(Collection<String> classNames, ClassNameFilter classNameFilter, ClassLoader classLoader) {
if (CollectionUtils.isEmpty(classNames)) {
return Collections.emptyList();
} else {
List<String> matches = new ArrayList(classNames.size());
Iterator var5 = classNames.iterator();
while(var5.hasNext()) {
String candidate = (String)var5.next();
if (classNameFilter.matches(candidate, classLoader)) {
matches.add(candidate);
}
}
return matches;
}
}
在filter
方法中遍历刚才合并的onClasses
属性,并遍历调用classNameFilter.matches(candidate, classLoader)
方法,如果不能匹配上(取反操作),那么加入到matches
返回中。这里的filter
是上层传递过来的ClassNameFilter.MISSING
。
MISSING {
public boolean matches(String className, ClassLoader classLoader) {
return !isPresent(className, classLoader);
}
};
取反操作,本质是调用isPresent
方法
static boolean isPresent(String className, ClassLoader classLoader) {
if (classLoader == null) {
classLoader = ClassUtils.getDefaultClassLoader();
}
try {
FilteringSpringBootCondition.resolve(className, classLoader);
return true;
} catch (Throwable var3) {
return false;
}
}
FilteringSpringBootCondition.resolve
方法
protected static Class<?> resolve(String className, ClassLoader classLoader) throws ClassNotFoundException {
return classLoader != null ? Class.forName(className, false, classLoader) : Class.forName(className);
}
可以看到,这个匹配的逻辑就是去类加载@ConditionalOnClass
注解value和name属性导入的class类名,如果被加载到则返回true,未被加载到则返回false,返回false则会加入到filter
方法matches
中。
所以@ConditionalOnClass
注解作用是需要在我们项目环境中存在某个类或者某些类的时候才会去注册此bean。
总结
@ConditionalOnClass、@ConditionalOnMissingClass、@ConditionalOnBean
这些是springboot自动配置模块中引入的注解,其目的是通过这些注解引入@Conditional
注解,并设置对应的匹配解析类,从而实现控制当达到某些必要条件时才会生成bean。
需要注意的是
1、@Conditional
本身属于spring context模块,可以认为自动配置模块中的这些注解是对@Conditional
的扩展,这在自动装配的时候显得很重要,集成三方的模块注册bean通常需要满足某些条件下才进行某些bean的注册。
2、上面的代码中有@ConditionalOnMissingClass
注解没有分析,实际上分析的思路是一样的,只不过这里它把代码也冗余到了getMatchOutcome
方法中而已。
3、当类上具有多个@ConditionalOnXxx
注解的时候,必须满足所有的匹配条件才能注册此bean。
4、分析完@ConditionalOnClass
的逻辑大家可以根据这个思路分析其他的注解,本质上就是注册的匹配类不同,就是状态绕来绕去不太好理清思绪,可以选择多看几遍。
5、可以根据需要自己注册自己的@ConditionalOnXxx注解以实现定制化功能。
更多推荐
所有评论(0)