- 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
 
沒有留言:
張貼留言