上面章节介绍了 @ConditionalOnBean 注解的用法,本章将介绍 @ConditionalOnClass 注解的用法,以及该注解的实现原理。
@ConditionalOnBean 表示仅有你指定的类在类路径上时才匹配 @Conditional 注解。
你可以在 @Configuration 类上安全地指定 value(),因为在加载类之前通过使用 ASM 解析了注释元数据。
在对 @Bean 方法进行处理时,需要格外小心,考虑将条件隔离在单独的配置类中,特别是如果方法的返回类型与条件的目标匹配时。
利用 @ConditionalOnBean 注解的 name 属性,判断 classpath 下面是否存在 com.huangx.springboot.autoconfig.init.InitUser 类。如果存在该类,则实例化 UserService。
(1)先创建 UserService 和 OrderService 服务
a、UserService.java
package com.huangx.springboot.autoconfig.service;
public class UserService {
// 什么也不做
}b、OrderService.java
package com.huangx.springboot.autoconfig.service;
public class OrderService {
// 什么也不做
}(2)创建一个用户初始化类,后面 @ConditionalOnBean 注解将判断该类是否位于 classpath 中。
package com.huangx.springboot.autoconfig.init;
public class InitUser {
// 这里什么也不做,仅仅为了让 @ConditionalOnClass
// 能够在 classpath 下面发现它,成功匹配 @Conditional
}(3)@Configuration 配置类
a、UserConfig.java
package com.huangx.springboot.autoconfig.config;
import com.huangx.springboot.autoconfig.service.UserService;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
// 判断在 classpath 中是否存在 InitUser 类
// 如果存在,则匹配 @Conditional 注解,创建 UserService 实例
@ConditionalOnClass(name={"com.huangx.springboot.autoconfig.init.InitUser"})
public class UserConfig {
// 只有 @ConditionalOnClass 条件匹配时才能匹配 @Conditional
@Bean
public UserService userService() {
System.out.println("UserService -> userService()");
return new UserService();
}
}b、OrderConfig.java
package com.huangx.springboot.autoconfig.config;
import com.huangx.springboot.autoconfig.service.OrderService;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
// 判断在 classpath 中是否存在 InitOrder 类
// 如果存在,则匹配 @Conditional 注解,创建 OrderService 实例
@ConditionalOnClass(name={"com.huangx.springboot.autoconfig.init.InitOrder"})
public class OrderConfig {
// 只有 @ConditionalOnClass 条件匹配时才能匹配 @Conditional
@Bean
public OrderService orderService() {
System.out.println("OrderConfig -> orderService()");
return new OrderService();
}
}(4)客户端代码,下面将在 index() 方法内部动态的使用 ApplicationContext 的 getBean() 方法获取 UserService 和 OrderService 类的实例。如下:
package com.huangx.springboot.autoconfig;
import com.huangx.springboot.autoconfig.service.OrderService;
import com.huangx.springboot.autoconfig.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@SpringBootApplication
public class Demo3Application {
@Autowired
private ApplicationContext applicationContext;
public static void main(String[] args) {
SpringApplication.run(Demo3Application.class, args);
}
@GetMapping("/")
public String index() {
UserService userService = null;
try {
userService = applicationContext.getBean(UserService.class);
} catch (Exception e) {
System.err.println(e.getMessage());
}
OrderService orderService = null;
try {
orderService = applicationContext.getBean(OrderService.class);
} catch (Exception e) {
System.err.println(e.getMessage());
}
return "userService=" + userService + "<br/>" +
"orderService=" + orderService;
}
}运行上面代码后,在浏览器中访问 http://localhost:8080 地址。如下图:

上面我们学会了怎样去使用 @ConditionalOnClass 注解,接下来我们先看看 @ConditionalOnClass 注解的源码,如下:
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(OnClassCondition.class)
public @interface ConditionalOnClass {
// 通过 Class 类指定你期待在 classpath 中必须存在的类
// 注意:如果你指定的类不再你项目中,IDEA 可能会提示错误,找不到你需要的类
Class<?>[] value() default {};
// 指定你期待在 classpath 中的必须存在类的全限定名称
String[] name() default {};
}上面源码中,@Conditional(OnClassCondition.class) 语句指定了 @ConditionalOnClass 注解的条件判断具体实现类。@ConditionalOnClass 的功能是由 OnClassCondition 类去实现的,我们可以通过分析 OnClassCondition 类了解 @ConditionalOnClass 是如何工作的?
@Order(Ordered.HIGHEST_PRECEDENCE)
class OnClassCondition extends FilteringSpringBootCondition {
//...
}OnClassCondition 类继承自 FilteringSpringBootCondition 类,而 FilteringSpringBootCondition 类又继承了 SpringBootCondition,SpringBootCondition 又实现了 Condition 接口。继承层次机构如下图:

Condition 接口仅仅提供了一个 boolean matches() 方法,该方法用来判断条件是否匹配。如果条件成立,则匹配 @Conditional 注解;如果条件不成立,则不匹配 @Conditional 注解。源码如下:
@FunctionalInterface
public interface Condition {
// 确定条件是否匹配
boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);
}关于 @Conditional 的用法,请查看自定义 @Conditional 条件。
Condition 接口的 matches() 方法在 SpringBootCondition 类中提供了实现。在 FilteringSpringBootCondition 类中,又重写了 matches() 方法。代码如下:
@Override
public boolean[] match(String[] autoConfigurationClasses, AutoConfigurationMetadata autoConfigurationMetadata) {
ConditionEvaluationReport report = ConditionEvaluationReport.find(this.beanFactory);
// 这里去判断指定的类是否在 classpath 下面
ConditionOutcome[] outcomes = getOutcomes(autoConfigurationClasses, autoConfigurationMetadata);
boolean[] match = new boolean[outcomes.length];
for (int i = 0; i < outcomes.length; i++) {
match[i] = (outcomes[i] == null || outcomes[i].isMatch());
if (!match[i] && outcomes[i] != null) {
logOutcome(autoConfigurationClasses[i], outcomes[i]);
if (report != null) {
report.recordConditionEvaluation(autoConfigurationClasses[i], this, outcomes[i]);
}
}
}
return match;
}上面方法中,getOutcomes() 方法将返回一个 ConditionOutcome[] 数组。每一个 ConditionOutcome 对象代表了一个类在 classpath 中是否得到匹配,包含匹配日志信息。如下图:

然而 getOutcomes() 方法是一个抽象方法:
protected abstract ConditionOutcome[] getOutcomes(String[] autoConfigurationClasses, AutoConfigurationMetadata autoConfigurationMetadata);
该方法由子类 OnClassCondition 中实现,代码如下:
@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 (autoConfigurationClasses.length > 1 && Runtime.getRuntime().availableProcessors() > 1) {
// 如果是多核CPU,则使用多线程完成解析
return resolveOutcomesThreaded(autoConfigurationClasses, autoConfigurationMetadata);
}
else {
// 如果不是多核CPU,则不使用线程进行解析
OutcomesResolver outcomesResolver = new StandardOutcomesResolver(autoConfigurationClasses, 0,
autoConfigurationClasses.length, autoConfigurationMetadata, getBeanClassLoader());
return outcomesResolver.resolveOutcomes();
}
}resolveOutcomesThreaded() 方法将会把待验证的类名称平均分配给两个线程去进行匹配。代码如下:
private ConditionOutcome[] resolveOutcomesThreaded(String[] autoConfigurationClasses,
AutoConfigurationMetadata autoConfigurationMetadata) {
int split = autoConfigurationClasses.length / 2;
// 第一个解析器
OutcomesResolver firstHalfResolver = createOutcomesResolver(autoConfigurationClasses, 0, split,
autoConfigurationMetadata);
// 第二个解析器
OutcomesResolver secondHalfResolver = new StandardOutcomesResolver(autoConfigurationClasses, split,
autoConfigurationClasses.length, autoConfigurationMetadata, getBeanClassLoader());
// 分别调用第一个和第二个解析器进行解析
ConditionOutcome[] secondHalf = secondHalfResolver.resolveOutcomes();
ConditionOutcome[] firstHalf = firstHalfResolver.resolveOutcomes();
// 将第一个和第二个解析器返回的结果进行合并
ConditionOutcome[] outcomes = new ConditionOutcome[autoConfigurationClasses.length];
System.arraycopy(firstHalf, 0, outcomes, 0, firstHalf.length);
System.arraycopy(secondHalf, 0, outcomes, split, secondHalf.length);
return outcomes;
}根据传递的开始地址和偏移量从目标类数组中取出子数组,然后创建 ThreadedOutcomesResolver 多线程解析器。如下:
private OutcomesResolver createOutcomesResolver(String[] autoConfigurationClasses, int start, int end,
AutoConfigurationMetadata autoConfigurationMetadata) {
// 标准解析器
OutcomesResolver outcomesResolver = new StandardOutcomesResolver(autoConfigurationClasses, start, end,
autoConfigurationMetadata, getBeanClassLoader());
try {
// 多线程解析器
return new ThreadedOutcomesResolver(outcomesResolver);
}
catch (AccessControlException ex) {
return outcomesResolver;
}
}我们继续看看 ThreadedOutcomesResolver 类的实现,代码如下:
private static final class ThreadedOutcomesResolver implements OutcomesResolver {
private final Thread thread;
// 匹配结果
private volatile ConditionOutcome[] outcomes;
private ThreadedOutcomesResolver(OutcomesResolver outcomesResolver) {
// 启动线程去进行匹配
this.thread = new Thread(() -> this.outcomes = outcomesResolver.resolveOutcomes());
this.thread.start();
}
@Override
public ConditionOutcome[] resolveOutcomes() {
try {
// 等待线程结束
this.thread.join();
}
catch (InterruptedException ex) {
Thread.currentThread().interrupt();
}
// 返回匹配结果
return this.outcomes;
}
}从上面代码可以知道,在 ThreadedOutcomesResolver 类中实际还是调用 StandardOutcomesResolver 类的 resolveOutcomes() 方法去完成匹配的。resolveOutcomes() 方法实现如下:
@Override
public ConditionOutcome[] resolveOutcomes() {
return getOutcomes(this.autoConfigurationClasses, this.start, this.end, this.autoConfigurationMetadata);
}进入 getOutcomes() 方法,该方法将迭代我们需要匹配的类,进行逐个匹配。代码如下:
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) {
// 重点:通过 getOutcome 去验证某个类
outcomes[i - start] = getOutcome(candidates);
}
}
}
return outcomes;
}getOutcome() 方法的源码如下:
private ConditionOutcome getOutcome(String candidates) {
try {
// 如果是单个类
if (!candidates.contains(",")) {
return getOutcome(candidates, this.beanClassLoader);
}
// 如果多个类使用逗号分割,如:com.hxstrive.A,com.hxstrive.B,则只会匹配第一个类
// 如果第一个类不存在,继续匹配第二个,直到匹配最后一个或匹配到存在的类为止
for (String candidate : StringUtils.commaDelimitedListToStringArray(candidates)) {
ConditionOutcome outcome = getOutcome(candidate, this.beanClassLoader);
if (outcome != null) {
return outcome;
}
}
}
catch (Exception ex) {
// We'll get another chance later
}
return null;
}快了,我们继续进一步进入 getOutcome(String className, ClassLoader classLoader) 方法,代码如下:
private ConditionOutcome getOutcome(String className, ClassLoader classLoader) {
// 关键代码来了,matches() 方法用来验证类是否在 classpath 下面
if (ClassNameFilter.MISSING.matches(className, classLoader)) {
return ConditionOutcome.noMatch(ConditionMessage.forCondition(ConditionalOnClass.class)
.didNotFind("required class").items(Style.QUOTE, className));
}
return null;
}下面是 FilteringSpringBootCondition.ClassNameFilter 枚举的代码,如下:
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 {
// 实际解析类是否在classpath中,该方法位于 FilteringSpringBootCondition 类
resolve(className, classLoader);
return true;
}
catch (Throwable ex) {
return false;
}
}
}进入 FilteringSpringBootCondition 类,resolve() 方法代码如下:
/**
* Slightly faster variant of {@link ClassUtils#forName(String, ClassLoader)} that
* doesn't deal with primitives, arrays or inner types.
* @param className the class name to resolve
* @param classLoader the class loader to use
* @return a resolved class
* @throws ClassNotFoundException if the class cannot be found
*/
protected static Class<?> resolve(String className, ClassLoader classLoader) throws ClassNotFoundException {
if (classLoader != null) {
return Class.forName(className, false, classLoader);
}
return Class.forName(className);
}看到了吧!它实际上使用的是 Class.forName 来检查你指定的类是否在 classpath 下面,这也要求我们指定类的完全限定名。如果 Class.forName 时,类不存在会抛出 ClassNotFoundException 异常。如下图:
