Spring高級裝配(二) 條件化的bean
如果你希望一個bean在特定的條件下才會出現:
- 應用的類路徑下包含特定的庫時才創建
- 只有當某個特定的bean也聲明之後才會創建
- 某個特定的環境變量設定之後才創建某個bean
在Spring 4之前,很難實現這種級別的條件化配置,但是Spring4引入了一個新的@Conditional註解,它可以用到帶有@Bean註解的方法上。如果給定的條件計算結果為true,就會創建這個bean,否則的話,這個bean會被忽略。
示例:設置了magic環境屬性才去實例化MagicBean
1 @Bean 2 @Conditional(MagicExistsCondition.class) // 條件化地創建bean 3 public MagicBean magicBean() { 4 return new MagicBean(); 5 }
這裏的@Conditional中給定了一個Class,它指明了條件,也就是MagicCondition。
Condition接口如下:
1 public interface Condition { 2 boolean matches(ConditionContext ctxt, AnnotatedTypeMetadata metadata); 3 }
設置給@Conditional的類可以使任意實現了Condition接口的類型。如此一來,只需要提供matches( )方法的實現即可。如果matches( )方法返回為true,那麽就會創建帶有@Conditional註解的bean。反之則不會創建這些bean。
MagicExistsCondition的實現:
1 public class MagicExistsCondition implements Condition { 2 public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { 3 Environment env = context.getEnvironment(); 4 return env.containsProperty("magic"); // 檢查是否有magic屬性 5} 6 }
上面的例子中只獲取了Environment,ConditionContext中的內容還挺豐富的,它是一個接口:
1 public interface ConditionContext { 2 BeanDefinitionRegistry getRegistry(); 3 ConfigurableListableBeanFactory getBeanFactory(); 4 Environment getEnvironment(); 5 ResourceLoader getResourceLoader(); 6 ClassLoader getClassLoader(); 7 }
通過ConditionContext,可以拿到的資源有:
- 借助getRegistry()返回的BeanDefinitionRegistry檢查bean的定義
- 借助getBeanFactory()返回的ConfigurableListableBeanFactory檢查bean是否存在(這個屌),甚至探查bean的屬性
- 借助getEnvironment()返回的Environment檢查環境變量是否存在已經它的值是什麽
- 讀取並探查getResourceLoader()返回的ResourceLoader所加載的資源
- 借助getClassLoader()返回的ClassLoader加載並檢查類是否存在
AnnotationedTypeMetadata則能夠讓我們檢查帶有@Bean註解的方法上還有什麽其他註解。
1 public interface AnnotatedTypeMetadata { 2 boolean isAnnotated(String var1); 3 Map<String, Object> getAnnotationAttributes(String var1); 4 Map<String, Object> getAnnotationAttributes(String var1, boolean var2); 5 MultiValueMap<String, Object> getAllAnnotationAttributes(String var1); 6 MultiValueMap<String, Object> getAllAnnotationAttributes(String var1, boolean var2); 7 }
通過isAnnotated方法,我們能夠判斷帶有@Bean註解的方法是不是還有其他特定的註解。借助其他的方法,能夠檢查@Bean註解的方法上其他註解的屬性。
在Spring 4開始,@Profile註解進行了重構,使其基於@Conditional和Condition的實現。
在Spring 4中@Profile的實現如下:
1 @Retention(RetentionPolicy.RUNTIME) 2 @Target({ElementType.TYPE, ElementType.METHOD}) 3 @Documented 4 @Conditional({ProfileCondition.class}) 5 public @interface Profile { 6 String[] value(); 7 }
ProfileCondition:
1 class ProfileCondition implements Condition { 2 @Override 3 public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { 4 if (context.getEnvironment() != null) { 5 MultiValueMap<String, Object> attrs = metadata.getAllAnnotationAttributes(Profile.class.getName()); 6 if (attrs != null) { 7 for (Object value : attrs.get("value")) { 8 if (context.getEnvironment().acceptsProfiles(((String[]) value))) { 9 return true; 10 } 11 } 12 return false; 13 } 14 } 15 return true; 16 } 17 }
ProfileCondition通過AnnotatedTypeMetadata得到了用於@Profile註解的所有屬性。借助該信息,它會明確地檢查value屬性,該屬性包含了bean的profile名稱。然後根據ConditionContext得到的Environment來檢查(借助acceptsProfiles()方法)該profile是否處於激活狀態。
總結
- @Conditional註解可以實現一個bean的條件化聲明
- Profile在Spring 4中使用@Conditional進行了重構
- 實現條件化配置中有兩個關鍵的接口:ConditionContext和AnnotatedTypeMetadata,在檢測條件的時候起了關鍵作用
- 這裏沒有講在xml中如何實現條件化配置
- 這裏發現用Java來進行config還是挺爽的
Spring高級裝配(二) 條件化的bean