- Conditional beans
- 需求:
- 在某些條件的成立下,此bean才被create,比如某個特定的environment variable被設置以後才create bean
- 解法:
- Spring4以後引入一個新的@Conditional annotation -> 若給定的條件成回傳為true,則會create這個bean,否則會被忽略
- 範例:
- @Bean @Conditional(MagicExistsCondition.class) //是一個implements Conditional的class public MagicBean magicBean() { return new MagicBean(); } 
- @Conditional 會透過conditional interface 進行比對
- public interface Condition { boolean matches(ConditionContext ctxt,AnnotatedTypeMetadata metadata); } 
- 以下為implements Condition的class,目的在是否有確認magic attribute
- public class MagicExistsCondition implements Condition { public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { Environment env = context.getEnvironment(); return env.containsProperty("magic"); } } 
- ConditionalContext
- 能夠藉由ConditionalContext context得到environment
- 以下為conditionContext interface
- public interface ConditionContext { BeanDefinitionRegistry getRegistry(); // 檢查bean定義 ConfigurableListableBeanFactory getBeanFactory(); // bean是否存在及其屬性 Environment getEnvironment(); // 檢查environment variable及是否存在和value為何 ResourceLoader getResourceLoader(); // 讀取load的resources ClassLoader getClassLoader(); //檢查calss是否存在 } 
- AnnotatedTypeMetadata
- 檢查@Bean annotation的method上是否有其他annotation
- 以下為 AnnotatedTypeMetadata interface
- public interface AnnotatedTypeMetadata { boolean isAnnotated(String annotationType); Map<String, Object> getAnnotationAttributes(String annotationType); Map<String, Object> getAnnotationAttributes( String annotationType, boolean classValuesAsString); MultiValueMap<String, Object> getAllAnnotationAttributes( String annotationType); MultiValueMap<String, Object> getAllAnnotationAttributes( String annotationType, boolean classValuesAsString); } 
- Profile annotation使用Conditional實際範例
- @Profile annotation:
- @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE, ElementType.METHOD}) @Documented @Conditional(ProfileCondition.class) public @interface Profile { String[] value(); } 
- ProfileCondition.class:
- class ProfileCondition implements Condition { public boolean matches( ConditionContext context, AnnotatedTypeMetadata metadata) { if (context.getEnvironment() != null) { // environment 存在 MultiValueMap < String, Object > attrs = metadata.getAllAnnotationAttributes(Profile.class.getName()); // 藉由AnnotatedTypeMetadata取得@Profile所有annotation的屬性 if (attrs != null) { for (Object value: attrs.get("value")) { if (context.getEnvironment().acceptsProfiles(((String[]) value))) { // 確認profile是否active return true; } } return false; } } return true; } 
- Addressing ambiguity in autowiring
- 只有一個bean符合所需的結果的時候,autowired才是有效的
- 假設現有一setDessert()使用@Autowired
- @Autowired public void setDessert(Dessert dessert) { this.dessert = dessert; } 
- 且Dessert是個interface,有三個class implements
- @Component public class Cake implements Dessert {... } @Component public class Cookies implements Dessert {... } @Component public class IceCream implements Dessert {... } 
- 以上三者皆會被加入Spring application context bean中,當執行至setDessert時,會發現並沒有唯一的bean,Spring會無法做出選擇,只好宣告失敗拋出NoUniqueBeanDefinitionException
- 解決方法
- primary
- qualifier
- Primary bean
- 設定某個bean為首選
- 當遇到ambiguous的時候,Spring會選擇primary bean
- @Primary可以與其他annotation一起使用
- 使用@Component:
- @Component @Primary public class IceCream implements Dessert { ... } 
- 使用Java config file:
- @Bean @Primary public Dessert iceCream() { return new IceCream(); } 
- 使用xml:
- <bean id="iceCream" class="com.desserteater.IceCream" primary="true" /> 
- 問題:
- 若設定兩個bean為@Primary則Spring還是沒辦法判斷要選擇何者,一樣會有錯誤發生,此時可以使用@Qualifier
- Qualifier autowired
- 可以與@Autowired及@Inject一起使用。
- 下例顯示在inject dessert時選擇iceCream
- @Autowired @Qualifier("iceCream") //括號內寫beanID public void setDessert(Dessert dessert) { this.dessert = dessert; } 
- 注意:
- 括號內必須加入雙引號(String type)
- iceCream上也需要有@Qualifier內("iceCream"),若bean上沒有@Qualifier,default會自己建立
- 意即所有bean都會有一個default @Qualifier,且內容與bean ID相同
- 問題:
- refactor的時候可能會失敗,因為Qualifier內的字串需要修改
- 解決:
- Custom qualifier
- Custom qualifiers
- 方法:
- 在bean上自行添加@Qualifier
- 可以與Component組合使用
- @Component @Qualifier("cold") // 自行定義的qualifier public class IceCream implements Dessert { ... } 
- 問題:
- 若有多個bean有相同的custom qualifiers,也會造成ambiguous
- 定義Custom qualifiers
- Java8允許重複出現的annotation,只要annotation本身定義的時候帶有@Reoeatable即可,帶Spring的@Qualified定義時並沒有加入@Reoeatable
- 可以create 自定義的qualified annotation
- 方法
- 不使用@Qualified("cole"),直接使用自定義的@Cold annotation
- @Target({ ElementType.CONSTRUCTOR, ElementType.FIELD, ElementType.METHOD, ElementType.TYPE }) @Retention(RetentionPolicy.RUNTIME) @Qualifier public @interface Cold {} //自行定義的@Cold annotation 
- 或是也自行定義@Creamy
- @Target({ElementType.CONSTRUCTOR, ElementType.FIELD, ElementType.METHOD, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Qualifier public @interface Creamy { } 
- 這樣的話就能直接使用@Cold或@Creamy,甚至可以直接重疊使用
- @Component @Cold @Creamy public class IceCream implements Dessert { ... } 
- 最後就能透過inject,注入自己所需要的object instance
- @Autowired @Cold @Creamy public void setDessert(Dessert dessert) { this.dessert = dessert; } 
- 優勢:
- 透過自行定義的Qualifier,可以同時使用多個並自行進行組合
- 相對於使用default Qualifier內放String type,更顯得自行定義的Qualifier更為type safe
 
沒有留言:
張貼留言