@ConditionOnClass指定类不存在会报错吗?源码解析

432 阅读3分钟

这是我参与8月更文挑战的第22天,活动详情查看:8月更文挑战

结论:既然springboot那么做肯定不会报错。原因运行时是通过ClassLoader进行加载指定类,不存在则跳过配置类加载。

从源码来看下过程

SpringApplication.run的加载过程通过选择器AutoConfigurationImportSelector进行自动配置加载。

AutoConfigurationImportSelector类process处理过程获取了所有的配置configurations,然后进行filter过滤。

/** class AutoConfigurationImportSelector **/
// 使用DeferredImportSelector 处理SpringBootApplication注解
public void process(AnnotationMetadata annotationMetadata, DeferredImportSelector deferredImportSelector) {
			Assert.state(deferredImportSelector instanceof AutoConfigurationImportSelector,
					() -> String.format("Only %s implementations are supported, got %s",
							AutoConfigurationImportSelector.class.getSimpleName(),
							deferredImportSelector.getClass().getName()));
  		// 获取配置实体  getAutoConfigurationMetadata 获取自动配置元数据
			AutoConfigurationEntry autoConfigurationEntry = ((AutoConfigurationImportSelector) deferredImportSelector)
					.getAutoConfigurationEntry(getAutoConfigurationMetadata(), annotationMetadata);
			this.autoConfigurationEntries.add(autoConfigurationEntry);
			for (String importClassName : autoConfigurationEntry.getConfigurations()) {
				this.entries.putIfAbsent(importClassName, annotationMetadata);
			}
		}
/**  class AutoConfigurationImportSelector **/
// 获取自动配置实体
protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata,
			AnnotationMetadata annotationMetadata) {
		if (!isEnabled(annotationMetadata)) {
			return EMPTY_ENTRY;
		}
		AnnotationAttributes attributes = getAttributes(annotationMetadata);
		List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
		configurations = removeDuplicates(configurations);
		Set<String> exclusions = getExclusions(annotationMetadata, attributes);
		checkExcludedClasses(configurations, exclusions);
		configurations.removeAll(exclusions);
		//对配置类进行过滤
		configurations = filter(configurations, autoConfigurationMetadata);
		fireAutoConfigurationImportEvents(configurations, exclusions);
		return new AutoConfigurationEntry(configurations, exclusions);
	}
/** class AutoConfigurationImportSelector **/
// 获取自动配置元数据
private AutoConfigurationMetadata getAutoConfigurationMetadata() {
 if (this.autoConfigurationMetadata == null) {
  this.autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);
 }
 return this.autoConfigurationMetadata;
}

/** class AutoConfigurationMetadataLoader **/
final class AutoConfigurationMetadataLoader {
	
	protected static final String PATH = "META-INF/spring-autoconfigure-metadata.properties";
  
	// 从配置文件 META-INF/spring-autoconfigure-metadata.properties 加载元数据
	static AutoConfigurationMetadata loadMetadata(ClassLoader classLoader) {
		return loadMetadata(classLoader, PATH);
	}
}

AutoConfigurationMetadataLoader类从META-INF/spring-autoconfigure-metadata.properties加载元数据。

RedisAutoConfiguration.ConditionalOnClass

org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration.ConditionalOnClass=org.springframework.data.redis.core.RedisOperations

继续看filter方法

从配置文件中加载了AutoConfigurationImportFilter的配置类,使用所有类进行匹配过滤。

每个配置类使用了AutoConfigurationImportFilter的match方法进行匹配,继而调用了实现类的getOutcomes进行检验。

private List<String> filter(List<String> configurations, AutoConfigurationMetadata autoConfigurationMetadata) {

 String[] candidates = StringUtils.toStringArray(configurations);
 ...
 for (AutoConfigurationImportFilter filter : getAutoConfigurationImportFilters()) {
  invokeAwareMethods(filter);
  boolean[] match = filter.match(candidates, autoConfigurationMetadata);
  ...
 }
 ...
}

getAutoConfigurationImportFilters() 方法使用SpringFactoriesLoader从配置文件spring.factories中加载AutoConfigurationImportFilter自动过滤类

protected List<AutoConfigurationImportFilter> getAutoConfigurationImportFilters() {
 return SpringFactoriesLoader.loadFactories(AutoConfigurationImportFilter.class, this.beanClassLoader);
}
# spring.factories
# Auto Configuration Import Filters
org.springframework.boot.autoconfigure.AutoConfigurationImportFilter=\
org.springframework.boot.autoconfigure.condition.OnBeanCondition,\
org.springframework.boot.autoconfigure.condition.OnClassCondition,\
org.springframework.boot.autoconfigure.condition.OnWebApplicationCondition

FilteringSpringBootCondition类的继承关系

FilteringSpringBootCondition (org.springframework.boot.autoconfigure.condition)

|--OnBeanCondition (org.springframework.boot.autoconfigure.condition)

|--OnClassCondition (org.springframework.boot.autoconfigure.condition)

|--OnWebApplicationCondition (org.springframework.boot.autoconfigure.condition)

来看下 OnClassCondition类条件的匹配

@Order(Ordered.HIGHEST_PRECEDENCE)
class OnClassCondition extends FilteringSpringBootCondition {

 @Override
 protected final ConditionOutcome[] getOutcomes(String[] autoConfigurationClasses,
   AutoConfigurationMetadata autoConfigurationMetadata) {
  // Split the work and perform half in a background thread if more than one
  // processor is available. Using a single additional thread seems to offer the
  // best performance. More threads make things worse.
   // 多核处理器,使用一个额外的线程处理一半工作,以获得最优性能
  if (Runtime.getRuntime().availableProcessors() > 1) {
   return resolveOutcomesThreaded(autoConfigurationClasses, autoConfigurationMetadata);
  }
  else {
   OutcomesResolver outcomesResolver = new StandardOutcomesResolver(autoConfigurationClasses, 0,
     autoConfigurationClasses.length, autoConfigurationMetadata, getBeanClassLoader());
   return outcomesResolver.resolveOutcomes();
  }
 }

OnClassCondition类获取匹配信息 通过key ConditionalOnClass来获取配置文件的类, matches通过classloader进行加载类,不存在类则添加异常信息

/** class OnClassCondition **/
private ConditionOutcome[] getOutcomes(String[] autoConfigurationClasses, int start, int end,
				AutoConfigurationMetadata autoConfigurationMetadata) {
			ConditionOutcome[] outcomes = new ConditionOutcome[end - start];
			for (int i = start; i < end; i++) {
				String autoConfigurationClass = autoConfigurationClasses[i];
				if (autoConfigurationClass != null) {
					String candidates = autoConfigurationMetadata.get(autoConfigurationClass, "ConditionalOnClass");
					if (candidates != null) {
						outcomes[i - start] = getOutcome(candidates);
					}
				}
			}
			return outcomes;
		}

ConditionalOnClass注解匹配信息

/** class OnClassCondition **/
private ConditionOutcome getOutcome(String className, ClassLoader classLoader) {
 if (ClassNameFilter.MISSING.matches(className, classLoader)) {
  return ConditionOutcome.noMatch(ConditionMessage.forCondition(ConditionalOnClass.class)
    .didNotFind("required class").items(Style.QUOTE, className));
 }
 return null;
}

FilteringSpringBootCondition 使用classLoader进行类加载

/** class FilteringSpringBootCondition **/
protected static Class<?> resolve(String className, ClassLoader classLoader) throws ClassNotFoundException {
 if (classLoader != null) {
  return classLoader.loadClass(className);
 }
 return Class.forName(className);
}

有趣的写法

enum实现类继承

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;
  }
 }

}

总结

SpringBoot自动配置过程AutoConfigurationImportSelector选择器process处理过程,通过filter进行配置过滤处理,filter是从配置文件spring.factories中获取FilteringSpringBootCondition配置类(OnBeanCondition、OnClassCondition、OnWebApplicationCondition),OnClassCondition实现对ConditionalOnClass的处理,springBoot从配置文件spring-autoconfigure-metadata.properties获取的元数据autoConfigurationMetadata中获取key为ConditionalOnClass的配置类,通过ClassLoader进行类加载,类不存在则过滤掉不再进行配置类加载。

那么@ConditionOnClass编译是怎么处理的呢?

使用optional选项

<dependency>
  <groupId>org.springframework.data</groupId>
  <artifactId>spring-data-redis</artifactId>
  <version>2.2.5.RELEASE</version>
  <scope>compile</scope>
  <optional>true</optional>
</dependency>