2017年12月21日 星期四

Java Spring - Conditional beans


  • 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一起使用
      1. 使用@Component:
        • @Component
          @Primary
          public class IceCream implements Dessert { ... }
          
      2. 使用Java config file:
        • @Bean
          @Primary
          public Dessert iceCream() {
              return new IceCream();
          }
          
      3. 使用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

沒有留言:

張貼留言