spring 條件化bean的實踐

條件化bean

假設你希望某個bean只有當另外某個特定的bean也聲明瞭之後才建立,或者只與某個特定的環境變數設定之後,才會建立,那麼bean稱為條件化bean。在Spring4引入了一個新的@Conditional註解,它可以作用到帶有@Bean註解的方法上,如果給定的條件為true,就會建立這個bean,否則的話,這個bean會被忽略。

這種條件化bean,在後面我們的springboot學習中會有很多,首先我們來看Conditional註解的定義:

@Target({ElementType。TYPE, ElementType。METHOD})@Retention(RetentionPolicy。RUNTIME)@Documentedpublic @interface Conditional { Class<? extends Condition>[] value();}

其內部主要就是利用了Condition介面,來判斷是否滿足條件,從而決定是否需要載入Bean。下面是Condtion介面的定義,這個可以說是最基礎的入口了,其他的所有條件註解,歸根結底,都是透過實現這個介面進行擴充套件的:

@FunctionalInterfacepublic interface Condition { boolean matches(ConditionContext var1, AnnotatedTypeMetadata var2);}

這個介面中,有個引數比較有意思ConditionContext,它持有不少有用的物件,可以用來獲取很多系統相關的資訊,來豐富條件判斷,介面定義如下

public interface ConditionContext { BeanDefinitionRegistry getRegistry(); @Nullable ConfigurableListableBeanFactory getBeanFactory(); Environment getEnvironment(); ResourceLoader getResourceLoader(); @Nullable ClassLoader getClassLoader();}

透過ConditionContext,我們可以做到如下幾點:

藉助getRegistry()返回的BeanDefinitionRegistry檢查bean定義

藉助getBeanFactory()返回的ConfigurableListableBeanFactory檢查bean是否存在,甚至探查bean的屬性

藉助getEnvironment()返回的Environment檢查環境變數是否存在以及它的值是什麼

讀取並探查getResourceLoader()返回的ResourceLoader所載入的資源

藉助getClassLoader()返回的ClassLoader載入並檢查類是否存在

使用例項

需求:我們定義幾個Bean,並配上@Conditional註解,在Condition的實現類中,透過獲取環境變數中的spring。profiles。active值,若與我們的期望的值相同就代表需要建立這個Bean,若不相同,就不建立對應的Bean。

首先,建立一個配置類,並定義一些bean方法,如下:

package com。demo。spring。config;import com。demo。spring。condition。DBDevCondition;import com。demo。spring。condition。DBProdCondition;import com。demo。spring。condition。DBTestCondition;import com。mchange。v2。c3p0。ComboPooledDataSource;import org。springframework。context。annotation。Bean;import org。springframework。context。annotation。Conditional;import org。springframework。context。annotation。Configuration;import javax。sql。DataSource;import java。beans。PropertyVetoException;@Configurationpublic class ConditionConfiguration { @Bean @Conditional(DBDevCondition。class) public DataSource getDevDataSource() { System。out。println(“獲取【開發】環境的DataSource”); ComboPooledDataSource comboPooledDataSource = new ComboPooledDataSource(); comboPooledDataSource。setUser(“root”); comboPooledDataSource。setPassword(“root”); try { comboPooledDataSource。setDriverClass(“com。mysql。jdbc。Driver”); } catch (PropertyVetoException e) { e。printStackTrace(); } comboPooledDataSource。setJdbcUrl(“jdbc:mysql://localhost:3306/dev”); return comboPooledDataSource; } @Bean @Conditional(DBTestCondition。class) public DataSource getTestDataSource() { System。out。println(“獲取【測試】環境的DataSource”); ComboPooledDataSource comboPooledDataSource = new ComboPooledDataSource(); comboPooledDataSource。setUser(“root”); comboPooledDataSource。setPassword(“root”); try { comboPooledDataSource。setDriverClass(“com。mysql。jdbc。Driver”); } catch (PropertyVetoException e) { e。printStackTrace(); } comboPooledDataSource。setJdbcUrl(“jdbc:mysql://localhost:3306/test”); return comboPooledDataSource; } @Bean @Conditional(DBProdCondition。class) public DataSource getProdDataSource() { System。out。println(“獲取【生產】環境的DataSource”); ComboPooledDataSource comboPooledDataSource = new ComboPooledDataSource(); comboPooledDataSource。setUser(“root”); comboPooledDataSource。setPassword(“root”); try { comboPooledDataSource。setDriverClass(“com。mysql。jdbc。Driver”); } catch (PropertyVetoException e) { e。printStackTrace(); } comboPooledDataSource。setJdbcUrl(“jdbc:mysql://localhost:3306/prod”); return comboPooledDataSource; }}

然後我們給出其中一個Condition類的定義,如下:

package com。demo。spring。condition;import org。springframework。context。annotation。Condition;import org。springframework。context。annotation。ConditionContext;import org。springframework。core。env。Environment;import org。springframework。core。type。AnnotatedTypeMetadata;public class DBDevCondition implements Condition { @Override public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) { Environment environment = conditionContext。getEnvironment(); String value = environment。getProperty(“spring。profiles。active”); if(value。equals(“dev”)){ return true; } return false; }}

在VM options中新增引數-Dspring。profiles。active=test,然後編寫測試類,進行測試:

package com。demo。spring;import com。demo。spring。config。ConditionConfiguration;import org。junit。Test;import org。springframework。context。ApplicationContext;import org。springframework。context。annotation。AnnotationConfigApplicationContext;import javax。sql。DataSource;public class TestConditionConfiguration { @Test public void getDataSource(){ ApplicationContext applicationContext = new AnnotationConfigApplicationContext(ConditionConfiguration。class); System。out。println(applicationContext。getBean(DataSource。class)); }}

其他註解

Spring框架提供了一系列相關的註解,如下:

@ConditionalOnSingleCandidate:當給定型別的bean存在並且指定為Primary的給定型別存在時,返回true

@ConditionalOnMissingBean:當給定的型別、類名、註解、暱稱在beanFactory中不存在時返回true。各型別間是or的關係

@ConditionalOnBean:與上面相反,要求bean存在

@ConditionalOnMissingClass:當給定的類名在類路徑上不存在時返回true,各型別間是and的關係

@ConditionalOnClass:與上面相反,要求類存在

@ConditionalOnCloudPlatform:當所配置的CloudPlatform為啟用時返回true

@ConditionalOnExpression:SPEL表示式執行為true

@ConditionalOnJava:執行時的java版本號是否包含給定的版本號。如果包含,返回匹配,否則,返回不匹配

@ConditionalOnProperty:要求配置屬性匹配條件

@ConditionalOnJndi:給定的jndi的Location 必須存在一個。否則,返回不匹配

@ConditionalOnNotWebApplication:web環境不存在時

@ConditionalOnWebApplication:web環境存在時

@ConditionalOnResource:要求制定的資源存在