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