概念和使用规则
过滤-配置类生效的方式
@ConditionalOnClass : 某个class位于类路径上,才会实例化这个Bean。
@ConditionalOnMissingClass : classpath中不存在该类时起效
@ConditionalOnBean : DI容器中存在该类型Bean时起效
@ConditionalOnMissingBean : DI容器中不存在该类型Bean时起效
@ConditionalOnSingleCandidate : DI容器中该类型Bean只有一个或@Primary的只有一个时起效
@ConditionalOnExpression : SpEL表达式结果为true时
@ConditionalOnProperty : 参数设置或者值一致时起效
@ConditionalOnResource : 指定的文件存在时起效
@ConditionalOnJndi : 指定的JNDI存在时起效
@ConditionalOnJava : 指定的Java版本存在时起效
@ConditionalOnWebApplication : Web应用环境下起效
@ConditionalOnNotWebApplication : 非Web应用环境下起效
=======
@Conditional :自定义实现条件注入
uml类图
时序图说明
此图说明了 @ConditionalOnClass、@ConditionalOnBean等待在整个spring容器中校验的时机
如何自定义注入
如何基于@Conditional实现自定义注入呢
@Component
public class ConditionalTest {
@Component
@ConditionalOnClass(DataSource.class)
@Conditional(ConfigurationTest.EmbeddedDatabaseCondition.class)
protected static class EmbeddedDatabaseConfiguration {
}
static class EmbeddedDatabaseCondition extends SpringBootCondition {
@Override
public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
return new ConditionOutcome(true, (String) null);
}
}
}
源码分析
如上面自定义注入案例,在11行断点
…
堆栈直接定位到这里
shouldSkip
org.springframework.context.annotation.ConditionEvaluator#shouldSkip(org.springframework.core.type.AnnotatedTypeMetadata)
public boolean shouldSkip(AnnotatedTypeMetadata metadata) {
return shouldSkip(metadata, null);
}
public boolean shouldSkip(@Nullable AnnotatedTypeMetadata metadata, @Nullable ConfigurationPhase phase) {
// 判断是否包含@Conditional注解,像spring自带的注解@ConditionalOnClass等,都是@Conditional注解
if (metadata == null || !metadata.isAnnotated(Conditional.class.getName())) {
return false;
}
// 阶段给个默认值
if (phase == null) {
// 判断当前元数据是否包含 @Bean\@Compnent\@ComponentScan\@Import\@ImportResource 注解
if (metadata instanceof AnnotationMetadata &&
ConfigurationClassUtils.isConfigurationCandidate((AnnotationMetadata) metadata)) {
// 给phase一个默认值PARSE_CONFIGURATION,再递归一下
return shouldSkip(metadata, ConfigurationPhase.PARSE_CONFIGURATION);
}
return shouldSkip(metadata, ConfigurationPhase.REGISTER_BEAN);
}
List<Condition> conditions = new ArrayList<>();
// 解析当前bean上 所有的@Conditional注解的class(包含: @ConditionalOnClass、@ConditionalOnBean)
for (String[] conditionClasses : getConditionClasses(metadata)) {
for (String conditionClass : conditionClasses) {
// 根据class实例化
Condition condition = getCondition(conditionClass, this.context.getClassLoader());
conditions.add(condition);
}
}
// 排序
AnnotationAwareOrderComparator.sort(conditions);
for (Condition condition : conditions) {
ConfigurationPhase requiredPhase = null;
// 根据上面类图可得只有 @OnBeanCondition 实现了 ConfigurationCondition
if (condition instanceof ConfigurationCondition) {
requiredPhase = ((ConfigurationCondition) condition).getConfigurationPhase();
}
/**
条件1、
这里经过反复debug发现当前bean的元数据包含@ComponentScan注解时,
phase才会=REGISTER_BEAN,
其他情况时 phase都是null,然后在上面11行代码里面初始化 PARSE_CONFIGURATION 执行
所以这里的 requiredPhase ,差不多都是PARSE_CONFIGURATION
当然也有 REGISTER_BEAN 的情况,具体堆栈:ConfigurationClassParser#doProcessConfigurationClass:286行
条件2、
condition.matches 会默认走到 SpringBootCondition.matches 仅有SpringBootCondition实现此方法
*/
if ((requiredPhase == null || requiredPhase == phase) && !condition.matches(this.context, metadata)) {
return true;
}
}
return false;
}
shouldSkip时机(思考)
可能不会有人思考这俩枚举值有啥不一样的作用?
- ConfigurationPhase.PARSE_CONFIGURATION
- ConfigurationPhase.REGISTER_BEAN
spring 虽然搞了很多条件注解,但是总的分为俩类
- 基于Bean(spring容器的bean)的条件:@ConditionalOnBean
- 基于其他的匹配:@ConditionalOnProperty、ConditionalOnResource、@ConditionalOnClass
因为在注册beanDefinition之前无法校验spring的bean,
所以要根据注册beanDefinition之前,和注册beanDefinition之后进行分开校验。
注册BeanDefinition之前
org.springframework.context.annotation.ConfigurationClassParser#doProcessConfigurationClass
注意这里默认是启动类,因为@ComponentScan 注解默认在启动类上面,当然如果再加一个@ComponentScan 注解也能走到这里
但是这里是 registerBeanDefinition 之前的校验
doScan
直接定位到doScan这里
org.springframework.context.annotation.ClassPathBeanDefinitionScanner#doScan
org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider#findCandidateComponents
org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider#scanCandidateComponents
isCandidateComponent
org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider#isCandidateComponent(org.springframework.core.type.classreading.MetadataReader)
protected boolean isCandidateComponent(MetadataReader metadataReader) throws IOException {
for (TypeFilter tf : this.excludeFilters) {
if (tf.match(metadataReader, getMetadataReaderFactory())) {
return false;
}
}
for (TypeFilter tf : this.includeFilters) {
if (tf.match(metadataReader, getMetadataReaderFactory())) {
// 执行这里
return isConditionMatch(metadataReader);
}
}
return false;
}
private boolean isConditionMatch(MetadataReader metadataReader) {
if (this.conditionEvaluator == null) {
this.conditionEvaluator =
new ConditionEvaluator(getRegistry(), this.environment, this.resourcePatternResolver);
}
return !this.conditionEvaluator.shouldSkip(metadataReader.getAnnotationMetadata());
}
注册BeanDefinition之后
org.springframework.context.annotation.ConfigurationClassPostProcessor#processConfigBeanDefinitions
loadBeanDefinitions
org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader#loadBeanDefinitions
public void loadBeanDefinitions(Set<ConfigurationClass> configurationModel) {
// 创建 requiredPhase 的条件执行器,默认只有 OnBeanCondition 会生效
TrackedConditionEvaluator trackedConditionEvaluator = new TrackedConditionEvaluator();
for (ConfigurationClass configClass : configurationModel) {
loadBeanDefinitionsForConfigurationClass(configClass, trackedConditionEvaluator);
}
}
private void loadBeanDefinitionsForConfigurationClass(
ConfigurationClass configClass, TrackedConditionEvaluator trackedConditionEvaluator) {
// requiredPhase 执行 OnBeanCondition类的校验
if (trackedConditionEvaluator.shouldSkip(configClass)) {
String beanName = configClass.getBeanName();
if (StringUtils.hasLength(beanName) && this.registry.containsBeanDefinition(beanName)) {
this.registry.removeBeanDefinition(beanName);
}
this.importRegistry.removeImportingClass(configClass.getMetadata().getClassName());
return;
}
if (configClass.isImported()) {
registerBeanDefinitionForImportedConfigurationClass(configClass);
}
for (BeanMethod beanMethod : configClass.getBeanMethods()) {
loadBeanDefinitionsForBeanMethod(beanMethod);
}
loadBeanDefinitionsFromImportedResources(configClass.getImportedResources());
loadBeanDefinitionsFromRegistrars(configClass.getImportBeanDefinitionRegistrars());
}
private class TrackedConditionEvaluator {
private final Map<ConfigurationClass, Boolean> skipped = new HashMap<>();
public boolean shouldSkip(ConfigurationClass configClass) {
Boolean skip = this.skipped.get(configClass);
if (skip == null) {
if (configClass.isImported()) {
boolean allSkipped = true;
for (ConfigurationClass importedBy : configClass.getImportedBy()) {
if (!shouldSkip(importedBy)) {
allSkipped = false;
break;
}
}
if (allSkipped) {
// The config classes that imported this one were all skipped, therefore we are skipped...
skip = true;
}
}
if (skip == null) {
skip = conditionEvaluator.shouldSkip(configClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN);
}
this.skipped.put(configClass, skip);
}
return skip;
}
}
matches
org.springframework.boot.autoconfigure.condition.SpringBootCondition#matches(org.springframework.context.annotation.ConditionContext, org.springframework.core.type.AnnotatedTypeMetadata)
@Override
public final boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
String classOrMethodName = getClassOrMethodName(metadata);
try {
/*
这里会根据this动态去执行:
OnBeanCondition ==》@ConditionalOnMissingBean、@ConditionalOnBean、@ConditionalOnSingleCandidate
OnClassCondition ==》@ConditionalOnClass、@ConditionalOnMissingClass
OnWebApplicationCondition ==》@ConditionalOnWebApplication
||
==> getMatchOutcome
*/
ConditionOutcome outcome = getMatchOutcome(context, metadata);
// 打印日志
logOutcome(classOrMethodName, outcome);
// 用来记录自动化配置过程中条件匹配的详细信息及日志信息
// 在完成finishRefresh时会在ConditionEvaluationReportLoggingListener监听消息打印日志
recordEvaluation(context, classOrMethodName, outcome);
// 返回 OnBeanCondition、OnClassCondition、OnClassCondition 匹配后返回的结果
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);
}
}
getMatchOutcome
OnBeanCondition
案例
直接定位到具体判断的地方
org.springframework.boot.autoconfigure.condition.OnBeanCondition#containsBean
org.springframework.beans.factory.support.DefaultListableBeanFactory#containsBeanDefinition
OnClassCondition
org.springframework.boot.autoconfigure.condition.OnClassCondition#getMatchOutcome
@Override
public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
// 获取当前线程的类加载器是Launcher$AppClassLoader
ClassLoader classLoader = context.getClassLoader();
ConditionMessage matchMessage = ConditionMessage.empty();
/*
todo *****************************这一步没看懂具体是咋解析的,有空再研究一下 *****************************
这一步是获取当前注解上的值,
如: @ConditionalOnClass(DataSource.class) 解析出 javax.sql.DataSource
*/
List<String> onClasses = getCandidates(metadata, ConditionalOnClass.class);
if (onClasses != null) {
/*
这一步是精髓了,用classLoader.loadClass(className);检测当前类加载器中是否存在指定class文件
ClassNameFilter.MISSING 检测丢失的
ClassNameFilter.PRESENT 检测存在的
*/
List<String> missing = filter(onClasses, ClassNameFilter.MISSING, classLoader);
if (!missing.isEmpty()) {
// 如果不存在写入日志
// 且标记 ConditionOutcome.match=true
return ConditionOutcome.noMatch(ConditionMessage.forCondition(ConditionalOnClass.class)
.didNotFind("required class", "required classes").items(Style.QUOTE, missing));
}
// 存在记录
matchMessage = matchMessage.andCondition(ConditionalOnClass.class)
.found("required class", "required classes")
.items(Style.QUOTE, filter(onClasses, ClassNameFilter.PRESENT, classLoader));
}
// 对@ConditionalOnMissingClass 注解的解析与@ConditionalOnClass逻辑是相反的
List<String> onMissingClasses = getCandidates(metadata, ConditionalOnMissingClass.class);
if (onMissingClasses != null) {
List<String> present = filter(onMissingClasses, ClassNameFilter.PRESENT, classLoader);
if (!present.isEmpty()) {
return ConditionOutcome.noMatch(ConditionMessage.forCondition(ConditionalOnMissingClass.class)
.found("unwanted class", "unwanted classes").items(Style.QUOTE, present));
}
matchMessage = matchMessage.andCondition(ConditionalOnMissingClass.class)
.didNotFind("unwanted class", "unwanted classes")
.items(Style.QUOTE, filter(onMissingClasses, ClassNameFilter.MISSING, classLoader));
}
// 走到这里标记 ConditionOutcome.match=true
return ConditionOutcome.match(matchMessage);
}
org.springframework.boot.autoconfigure.condition.FilteringSpringBootCondition#filter
abstract class FilteringSpringBootCondition extends SpringBootCondition
implements AutoConfigurationImportFilter, BeanFactoryAware, BeanClassLoaderAware {
protected final List<String> filter(Collection<String> classNames, ClassNameFilter classNameFilter,
ClassLoader classLoader) {
if (CollectionUtils.isEmpty(classNames)) {
return Collections.emptyList();
}
List<String> matches = new ArrayList<>(classNames.size());
for (String candidate : classNames) {
if (classNameFilter.matches(candidate, classLoader)) {
matches.add(candidate);
}
}
return matches;
}
protected enum ClassNameFilter {
PRESENT {
@Override
public boolean matches(String className, ClassLoader classLoader) {
return isPresent(className, classLoader);
}
},
MISSING {
@Override
public boolean matches(String className, ClassLoader classLoader) {
return !isPresent(className, classLoader);
}
};
abstract boolean matches(String className, ClassLoader classLoader);
static boolean isPresent(String className, ClassLoader classLoader) {
if (classLoader == null) {
classLoader = ClassUtils.getDefaultClassLoader();
}
try {
resolve(className, classLoader);
return true;
}
catch (Throwable ex) {
return false;
}
}
}
protected static Class<?> resolve(String className, ClassLoader classLoader) throws ClassNotFoundException {
if (classLoader != null) {
return classLoader.loadClass(className);
}
return Class.forName(className);
}
}