环境:SpringBoot3.4.0
1. 简介
@Conditional是Spring 4.0版本引入的一个强大注解,它允许开发者根据特定条件来决定是否创建某个Bean。该注解只有当满足预设条件时,Spring容器才会将对应的Bean实例化并加入到应用程序的上下文中。
通过@Conditional注解,开发者可以根据不同的环境(如开发、测试、生产环境)、配置文件中的属性、类路径中的特定类是否存在,或者任何自定义的逻辑条件,来动态地控制Bean的创建过程。这种机制极大地提高了Spring应用程序的可配置性和灵活性,使得开发者能够根据不同的场景和需求,智能地选择性地装配Bean。
@Conditional注解通常与@Configuration和@Bean注解一起使用,以标记那些需要条件化创建的Bean。在Spring Boot中,@Conditional注解更是被广泛应用,衍生出了如@ConditionalOnProperty、@ConditionalOnBean、@ConditionalOnClass等多个便捷的条件注解,进一步简化了条件配置的过程。
当Spring Boot预设的条件注解不能满足我们的需求时,我们还可以通过自定义的方式来实现更为复杂和特定的条件逻辑。
接下来,我们将详细介绍有关自定义条件注解的多方面知识的应用。
图片
2. 实战案例
2.1 自定义Condition接口
这里我们简单的根据配置文件中的属性来决定是否注册Bean对象。
public class EnvCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
Environment env = context.getEnvironment();
return "true".equals(env.getProperty("pack.api.enabled"));
}
}
只有配置文件中的"pack.api.enabled"设置为true才会对相应的Bean进行注册;自定义注解:
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Conditional(ApiCondition.class)
public @interface ConditionalOnApi {
}
接下来,进行测试:
@RestController
@RequestMapping("/api")
@ConditionalOnApi
public class ApiController {
@PostConstruct
public void init() {
System.err.println("ApiController init...") ;
}
}
首先,配置文件不对pack.api.enabled进行配置,如果启动容器后没有输出任何内容则表示我们的条件注解生效了。
图片
没有任何的输出:
配置文件中设置pack.api.enabled=true, 再次启动服务
成功注册bean。
2.2 带参数增强实现
在上面的示例中,我们直接在代码中硬编码了要判断的属性,这种做法缺乏灵活性。因此,在该示例中,我们将采用自定义注解属性来实现条件判断,以增强代码的灵活性和可配置性。
修改注解如下:
public @interface ConditionalOnApi {
String value() ;
}
修改条件类:
public class ApiCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
Map<String, Object> attrs = metadata.getAnnotationAttributes(
ConditionalOnApi.class.getName()) ;
String key = (String) attrs.get("value") ;
Environment env = context.getEnvironment() ;
return "true".equals(env.getProperty(key)) ;
}
}
最后,修改使用处。
@ConditionalOnApi("pack.api.enabled")
public class ApiController {}
通过上面的改造我们的自定义条件灵活多了。
2.3 基于SpringBootCondition实现
通过继承SpringBootCondition实现条件是官方推荐的方式。它的强大之处在于它提供了有用的日志记录功能,以帮助用户诊断哪些类被加载了。
自定义条件:
public class ApiMonitorCondition extends SpringBootCondition {
private static final ConditionMessage.Builder message = ConditionMessage.forCondition("API Monitor");
@Override
public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
String monitorEnabled = context.getEnvironment().getProperty("pack.api.monitor.enabled");
if ("true".equals(monitorEnabled)) {
return ConditionOutcome.match(message.available("开启API监控功能"));
}
return ConditionOutcome.noMatch(message.because("API监控功能关闭"));
}
}
自定义注解:
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Conditional(ApiMonitorCondition.class)
public @interface ConditionalOnApiMonitor {
}
使用条件注解:
@Configuration
@ConditionalOnApiMonitor
public class ApiMonitorConfig {
}
接下来,进行测试。
首先,不配置pack.api.monitor.enabled或者设置为false,同时将debug设置为true,启动服务控制台输出如下:
图片
最后,将pack.api.monitor.enabled设置为true,控制台输出如下:
图片
2.4 多条件组合实现
在该案例中我们将采用多个条件来进行判断是否符合条件。我们还是使用上面的示例,条件的判断我们不仅判断属性,而且还会判断相应的类及Bean是否存在。
这里我们直接修改上面的条件注解:
public class ApiMonitorCondition extends SpringBootCondition {
private static final ConditionMessage.Builder message = ConditionMessage.forCondition("API Monitor");
private static final String CLASS_NAME = "com.pack.condition.test.MonitorComponent" ;
@Override
public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
String monitorEnabled = context.getEnvironment().getProperty("pack.api.monitor.enabled");
boolean enabled = "true".equals(monitorEnabled) ;
// 判断对应的类是否存在
boolean isPresent = isPresent(CLASS_NAME, context.getClassLoader()) ;
if (enabled) {
if (isPresent) {
try {
// 判断对应的bean是否存在
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory() ;
if (beanFactory.containsBean("monitorComponent")) {
return ConditionOutcome.match(message.available("开启API监控功能"));
}
return ConditionOutcome.noMatch(message.because("容器不存在beanName=monitorComponent的Bean对象"));
} catch (Exception e) {
return ConditionOutcome.noMatch(message.because("容器不存在【" + CLASS_NAME + "】类型的Bean"));
}
} else {
return ConditionOutcome.match(message.because("API监控未能开启缺少【" + CLASS_NAME + "】类"));
}
} else {
return ConditionOutcome.noMatch(message.because("API监控功能关闭"));
}
}
private static boolean isPresent(String className, ClassLoader classLoader) {
try {
resolve(className, classLoader);
return true;
}
return false ;
}
private static Class<?> resolve(String className, ClassLoader classLoader) throws ClassNotFoundException {
if (classLoader != null) {
return Class.forName(className, false, classLoader);
}
return Class.forName(className);
}
}
以上我们就完成了多个条件组件判断。
测试结果:
图片
存在对应的类及bean时输出如下:
图片
最终是匹配的。