Profile是個好東西。通過Profile,我們可以非常方便地條件化Bean的創建,動態調整應用程式的功能。可是,Profile只能做些簡單的條件化,對於複雜一點的條件化Profile是無法勝任的。比如現有這樣的數據源創建需求: 1.如果類路徑存在DBCP的JAR包,則創建DBCP提供的Basic ...
Profile是個好東西。通過Profile,我們可以非常方便地條件化Bean的創建,動態調整應用程式的功能。可是,Profile只能做些簡單的條件化,對於複雜一點的條件化Profile是無法勝任的。比如現有這樣的數據源創建需求:
1.如果類路徑存在DBCP的JAR包,則創建DBCP提供的BasicDataSource數據源。
2.如果類路徑沒有DBCP的JAR包,則創建Spring提供的DriverManagerDataSource數據源。
毫無疑問,這樣的需求Profile是實現不了的。要想實現這樣的需求,還得仰賴Spring提供的,專門用於Bean的條件化創建的,功能遠比Profile強大的Condition。而這,需要我們做好兩件事情:
1.實現Condition介面,以描述Bean的創建條件。
2.往配置方法添加@Conditional註解,告訴Spring容器創建Bean時以某個實現了Condition介面的類作為條件。
Condition介面簽名如下:
1 @FunctionalInterface 2 public interface Condition { 3 boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata); 4 }
其中定義的matches方法返回一個布爾類型的值:如果返回的是TRUE,說明條件成立,Spring容器將會創建相應的Bean;如果返回的是FALSE,說明條件失敗,Spring容器不會創建相應的Bean
另外,matches方法還接受兩個參數:一個參數是ConditionContext類型的,能向我們提供一些諸如Bean的定義,環境變數之類的信息;一個參數是AnnotatedTypeMetadata類型的,能向我們提供一些諸如配置方法是否帶有某種註解之類的信息。我們實現matches方法的時候,能用這些信息進行條件檢查。
因此,為了實現文章開頭提到的需求,我們首先需要做的就是這樣實現Condition介面:
1 public class ConditionOnDriverManagerDataSource implements Condition { 2 @Override 3 public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { 4 var dbcpFileName = "WEB-INF\\lib\\commons-dbcp2-2.8.0.jar"; 5 var resourceLoader = context.getResourceLoader(); 6 var resource = resourceLoader.getResource(dbcpFileName); 7 return !resource.exists(); 8 } 9 } 10 11 public class ConditionOnBasicDataSource implements Condition { 12 @Override 13 public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { 14 var dbcpFileName = "WEB-INF\\lib\\commons-dbcp2-2.8.0.jar"; 15 var resourceLoader = context.getResourceLoader(); 16 var resource = resourceLoader.getResource(dbcpFileName); 17 return resource.exists(); 18 } 19 }
這段代碼定義的兩個類都實現了Condition介面:
1.ConditionOnDriverManagerDataSource能夠檢查類路徑是否存在DBCP的JAR包:如果存在則返回FALSE;否則返回TRUE。這個條件能夠告訴Spring容器只有類路徑沒有DBCP的JAR包時,才會創建相應的Bean
2.ConditionOnBasicDataSource能夠檢查類路徑是否存在DBCP的JAR包:如果存在則返回TRUE;否則返回FALSE。這個條件能夠告訴Spring容器只有類路徑存在DBCP的JAR包時,才會創建相應的Bean
於是,我們完成了Condition介面的實現,該把它們交給@Conditional註解進行Bean的條件化配置了。@Conditional註解有個Class<T>類型的value屬性,用於指定實現了Condition介面的類的Class對象,告訴Spring容器創建Bean時以哪個Condition作為條件。如下所示:
1 @Configuration 2 public class AppConfig { 3 @Bean 4 @Conditional(value = ConditionOnDriverManagerDataSource.class) 5 public DataSource produceDataSource() { 6 var dataSource = new DriverManagerDataSource(); 7 dataSource.setUsername("root"); 8 dataSource.setPassword("123456"); 9 dataSource.setUrl("jdbc:mysql://localhost:3306/sm_person_rest"); 10 dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver"); 11 return dataSource; 12 } 13 14 @Bean 15 @Conditional(value = ConditionOnBasicDataSource.class) 16 public DataSource produceBasicDataSource() { 17 var dataSource = new BasicDataSource(); 18 dataSource.setUsername("root"); 19 dataSource.setPassword("123456"); 20 dataSource.setUrl("jdbc:mysql://localhost:3306/sm_person_rest"); 21 dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver"); 22 return dataSource; 23 } 24 }
這段配置代碼定義了兩個方法:
1.方法produceDataSource帶有@Conditional(ConditionOnDriverManagerDataSource.class)註解。Spring容器瞧見這個註解之後,就會調用ConditionOnDriverManagerDataSource的matches方法進行條件判斷:如果matches方法返回TRUE,則創建DriverManagerDataSource數據源;否則不創建。
2.方法produceBasicDataSource帶有@Conditional(ConditionOnBasicDataSource.class)註解。Spring容器瞧見這個註解之後,就會調用ConditionOnBasicDataSource的matches方法進行條件判斷:如果matches方法返回TRUE,則創建BasicDataSource數據源;否則不創建。
如此一來,Spring容器就能根據類路徑是否存在DBCP的JAR包決定創建哪種數據源了。有趣的是,前文介紹的Profile其實也是Condition的一種實現。如下所示:
1 @Target({ElementType.TYPE, ElementType.METHOD}) 2 @Retention(RetentionPolicy.RUNTIME) 3 @Documented 4 @Conditional({ProfileCondition.class}) 5 public @interface Profile { 6 String[] value(); 7 } 8 9 class ProfileCondition implements Condition { 10 public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { 11 MultiValueMap<String, Object> attrs = metadata.getAllAnnotationAttributes(Profile.class.getName()); 12 if (attrs != null) { 13 Iterator var4 = ((List)attrs.get("value")).iterator(); 14 15 Object value; 16 do { 17 if (!var4.hasNext()) { 18 return false; 19 } 20 21 value = var4.next(); 22 } while(!context.getEnvironment().acceptsProfiles(Profiles.of((String[])((String[])value)))); 23 24 return true; 25 } else { 26 return true; 27 } 28 } 29 }
至此,關於Condition的介紹也就告一段落了。下章,我們將會開始介紹混合配置。歡迎大家繼續閱讀,謝謝大家!