Master Spring Boot 3 Conditional Annotations with Real‑World Examples
This article introduces a continuously updated Spring Boot 3 case collection and PDF e‑book, then dives into the @Conditional annotation, custom condition interfaces, parameterized enhancements, SpringBootCondition usage, and multi‑condition combinations, providing complete code examples and test results.
Spring Boot 3 Practical Case Collection
The collection now contains over 90 practical articles and a PDF e‑book with 94 cases, continuously updated for subscribers, who also receive the source code.
1. Introduction
@Conditional is a powerful annotation introduced in Spring 4.0 that lets developers decide whether to create a bean based on conditions such as environment, properties, classpath, or custom logic. It is usually used together with @Configuration and @Bean , and Spring Boot provides derived annotations like @ConditionalOnProperty , @ConditionalOnBean , @ConditionalOnClass .
2. Practical Cases
2.1 Custom Condition Interface
Define a condition that checks a property pack.api.enabled before registering a bean.
<code>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"));
}
}
</code>Custom annotation:
<code>@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Conditional(ApiCondition.class)
public @interface ConditionalOnApi {}
</code>Test controller:
<code>@RestController
@RequestMapping("/api")
@ConditionalOnApi
public class ApiController {
@PostConstruct
public void init() {
System.err.println("ApiController init...");
}
}
</code>When the property is not set, no output appears; setting pack.api.enabled=true registers the bean.
2.2 Parameterized Enhancement
Make the condition configurable via an annotation attribute.
<code>public @interface ConditionalOnApi {
String value();
}
</code> <code>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));
}
}
</code>Usage:
<code>@ConditionalOnApi("pack.api.enabled")
public class ApiController {}
</code>2.3 Using SpringBootCondition
Extending SpringBootCondition provides logging and richer matching logic.
<code>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监控功能关闭"));
}
}
</code>Custom annotation and usage:
<code>@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Conditional(ApiMonitorCondition.class)
public @interface ConditionalOnApiMonitor {}
</code> <code>@Configuration
@ConditionalOnApiMonitor
public class ApiMonitorConfig {}
</code>2.4 Multiple Condition Combination
Combine property, class, and bean existence checks in a single condition.
<code>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 {
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;
} catch (ClassNotFoundException e) {
return false;
}
}
private static Class<?> resolve(String className, ClassLoader classLoader) throws ClassNotFoundException {
return classLoader != null ? Class.forName(className, false, classLoader) : Class.forName(className);
}
}
</code>Testing shows appropriate console messages for each scenario.
... (additional images omitted for brevity) ...
Spring Full-Stack Practical Cases
Full-stack Java development with Vue 2/3 front-end suite; hands-on examples and source code analysis for Spring, Spring Boot 2/3, and Spring Cloud.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.