強大!基于Spring Boot自定義條件動態(tài)注冊組件
環(huán)境:SpringBoot3.4.0
1. 簡介
@Conditional是Spring 4.0版本引入的一個強大注解,它允許開發(fā)者根據(jù)特定條件來決定是否創(chuàng)建某個Bean。該注解只有當(dāng)滿足預(yù)設(shè)條件時,Spring容器才會將對應(yīng)的Bean實例化并加入到應(yīng)用程序的上下文中。
通過@Conditional注解,開發(fā)者可以根據(jù)不同的環(huán)境(如開發(fā)、測試、生產(chǎn)環(huán)境)、配置文件中的屬性、類路徑中的特定類是否存在,或者任何自定義的邏輯條件,來動態(tài)地控制Bean的創(chuàng)建過程。這種機制極大地提高了Spring應(yīng)用程序的可配置性和靈活性,使得開發(fā)者能夠根據(jù)不同的場景和需求,智能地選擇性地裝配Bean。
@Conditional注解通常與@Configuration和@Bean注解一起使用,以標(biāo)記那些需要條件化創(chuàng)建的Bean。在Spring Boot中,@Conditional注解更是被廣泛應(yīng)用,衍生出了如@ConditionalOnProperty、@ConditionalOnBean、@ConditionalOnClass等多個便捷的條件注解,進一步簡化了條件配置的過程。
當(dāng)Spring Boot預(yù)設(shè)的條件注解不能滿足我們的需求時,我們還可以通過自定義的方式來實現(xiàn)更為復(fù)雜和特定的條件邏輯。
接下來,我們將詳細介紹有關(guān)自定義條件注解的多方面知識的應(yīng)用。
圖片
2. 實戰(zhàn)案例
2.1 自定義Condition接口
這里我們簡單的根據(jù)配置文件中的屬性來決定是否注冊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"設(shè)置為true才會對相應(yīng)的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進行配置,如果啟動容器后沒有輸出任何內(nèi)容則表示我們的條件注解生效了。
圖片
沒有任何的輸出:
配置文件中設(shè)置pack.api.enabled=true, 再次啟動服務(wù)
成功注冊bean。
2.2 帶參數(shù)增強實現(xiàn)
在上面的示例中,我們直接在代碼中硬編碼了要判斷的屬性,這種做法缺乏靈活性。因此,在該示例中,我們將采用自定義注解屬性來實現(xiàn)條件判斷,以增強代碼的靈活性和可配置性。
修改注解如下:
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實現(xiàn)
通過繼承SpringBootCondition實現(xiàn)條件是官方推薦的方式。它的強大之處在于它提供了有用的日志記錄功能,以幫助用戶診斷哪些類被加載了。
自定義條件:
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監(jiān)控功能"));
}
return ConditionOutcome.noMatch(message.because("API監(jiān)控功能關(guān)閉"));
}
}
自定義注解:
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Conditional(ApiMonitorCondition.class)
public @interface ConditionalOnApiMonitor {
}
使用條件注解:
@Configuration
@ConditionalOnApiMonitor
public class ApiMonitorConfig {
}
接下來,進行測試。
首先,不配置pack.api.monitor.enabled或者設(shè)置為false,同時將debug設(shè)置為true,啟動服務(wù)控制臺輸出如下:
圖片
最后,將pack.api.monitor.enabled設(shè)置為true,控制臺輸出如下:
圖片
2.4 多條件組合實現(xiàn)
在該案例中我們將采用多個條件來進行判斷是否符合條件。我們還是使用上面的示例,條件的判斷我們不僅判斷屬性,而且還會判斷相應(yīng)的類及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) ;
// 判斷對應(yīng)的類是否存在
boolean isPresent = isPresent(CLASS_NAME, context.getClassLoader()) ;
if (enabled) {
if (isPresent) {
try {
// 判斷對應(yīng)的bean是否存在
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory() ;
if (beanFactory.containsBean("monitorComponent")) {
return ConditionOutcome.match(message.available("開啟API監(jiān)控功能"));
}
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監(jiān)控未能開啟缺少【" + CLASS_NAME + "】類"));
}
} else {
return ConditionOutcome.noMatch(message.because("API監(jiān)控功能關(guān)閉"));
}
}
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);
}
}
以上我們就完成了多個條件組件判斷。
測試結(jié)果:
圖片
存在對應(yīng)的類及bean時輸出如下:
圖片
最終是匹配的。