Understanding the necessity of type Safety in CDI

旧城冷巷雨未停 提交于 2019-12-29 03:34:06

问题


First I should clarify that this post is not intended to criticize CDI, but to discover the thinking and assumptions behind the design of CDI and that will have obvious influence on designing any web app, which uses CDI.

One of the most distinguished feature of CDI (of Java EE 6) is type safety. Jboss Seam was not safe in type. It uses name to qualify any instance to inject. Like bellow:

   @Name("myBean")
   public class MyBean implements Bean {
     ...
   }

   @Name("yourBean")
   public class YourBean implements Bean {
     ...
   }

While injecting MyBean one can do this:

   @In
   private Bean myBean; //myBean is injected

   @In
   private Bean yourBean; //yourBean is injected  

And earlier versions of Spring (before 3.0), this type of injection happened like bellow:

Just define the beans in bean configuration file:

   <bean id="myBean" class="com.example.common.MyBean">
       ...
   </bean>

   <bean id="yourBean" class="com.example.common.YourBean">
       ...
   </bean>

And use named qualifier, deciding which one to use:

   @Autowired
   @Qualifier("myBean")
   private Bean bean;

   @Autowired
   @Qualifier("yourBean")
   private Bean bean; 

But now in CDI, First you need to define a custom Qualifier annotation for any specific type of object. Then use that annotation for qualifying that object. At the end of the day, when you look at your source code, you see that, you wasted considerable amount of time to write lots of custom annotations for dependency injection. Java community is moving towards annotations, leaving XML based configurations (verbose XML) behind. Is there anything that would convince anyone to think this (type safety with custom annotations) not as verbose annotations, but as an excellent and distinguished feature of CDI?

Edit:

Points, pushed to be highlighted

  1. If I use custom qualifier for type safety per service or dao (per interface), then for a large sized application like having 1000 or more service or dao classes with multiple implementations, it will be messy. Then for large sized applications, Is that feasible to use type safe injection?
  2. If the answer of the above question is "No" then, what is the point to use type safety?
  3. Even if it is feasible to write annotations for type safety, in large applications, is it really worth the effort for just avoiding verbose xml configuration?
  4. When actually I need type safety instead of bean name qualifier?

Short discussion on the above points

  1. There are not too many cases when you actually need type safe injection, specially when you have one implementation of an interface, you should use @Name as the qualifier. So yes, in a large sized application it is feasible to use type safety when it is actually needed.
  2. Ofcourse type safety is one of the distinguished feature of CDI and in the accepted answer there is a non-exhaustive list of reasons why you may chose to use type safety.
  3. As you are an intelligent programmer and you know precisely when to use type safety, so definitely it worth the effort, when really needed.
  4. Most of the parts of the accepted answer really talks, when do we need type safety and this article is also very helpful to understand.

Thanks and happy coding!


回答1:


Is CDI verbose? Are qualifiers needed?

  1. First off, you don't need qualifiers when you only have one implementation of an interface.
  2. If you have multiple implementations of an interface, then ask yourself - do you need to differentiate between them after deployment?

    • If the answer is no, then consider using alternatives.
    • If the answer is yes, then you still don't need qualifiers. Here's why:

      To use your own example:

      public class MyBean implements Bean {
          ...
      }
      
      public class YourBean implements Bean {
          ...
      }
      

      Then, you simply do:

      @Inject MyBean bean;
      

      or

      @Inject YourBean bean;
      

      If you don't like your instance variables to be of a concrete type and would rather see an interface, than do this:

      private Bean bean;
      
      @Inject
      public void setBean(MyBean bean) {
          this.bean = bean;
      }
      

      or

      private Bean bean;
      
      @Inject
      public void setBean(YourBean bean) {
          this.bean = bean;
      }
      

      In all the above cases it's completely qualifier free, absolutely type-safe, and definitely not verbose.

  3. Then, to elaborate on the last point - does the developer need to chose the appropriate implementation or can the choice be made problematically?

    • If the the developer will be choosing, then do as described above in 2.
    • If the the choice can be made problematically, then use a producer:

      @Produces
      public Bean obtainTheAppropriateBean(InjectionPoint ip) {
          if (meetsConditionA(ip)) {
              return getBeanImplA();
          } else if (meetsConditionB(ip)) {
              return getBeanImplB();
          } else if (...) {
              ...
          } else {
              return getDefaultBeanImpl();
          }
      }
      

      Still qualifier free, type-safe, and maybe still not verbose (trade choice for automation).

    See this article for an excellent expansion of this point and ideas on how to use the InjectionPoint API.

Are qualifiers needed at all?

I can see this question arising after the above examples. The answer is yes and here's a non-exhaustive list of reasons why you may chose to use them:

  • In one of the examples above I mentioned injecting specific implementations of an interface in order to avoid using qualifiers. That is entirely fine when the code is internal and internal developers will know which is which. But what if the code is a library or framework and you don't want to expose any particular implementation in the public API? Define some qualifiers then and document them well. How is this different than verbose XML? Even though you as the library writer may be doing just as much verbose work, your users will not have to. Instead, they will just write one word above an injection point and be happy you didn't make them write any XML - I personally would be very very very happy. :)
  • In the producer example above, you may be able to cover most cases but not all with the logic in the producer method. Or maybe you just want the ability to override that logic at any particular injection point. Then, keep the producer, make a qualifier and annotate some specific implementation with it. Then use the qualifier when you don't want the producer logic to run.
  • Imagine a situation where you have multiple interfaces and multiple implementations. Particular implementations may have common distinguishable traits and those traits are common to all of your interfaces. As an example, lets take the Java Colections Framework, specifically the List, Set, and Map interfaces. Each of those have multiple implementations, but there are common traits across all or some interfaces. For example linked nodes (fast iteration) - think LinkedList, LinkedHashSet, LinkedHashMap; sorted (ordering) - think TreeSet, TreeMap; hash-table based (fast insertion / removal / contains) - think HashSet, LinkedHashSet, HashMap, LinkedHashMap; concurent; random access; etc. Now, you could define the @Linked, @Sorted, and @Hashannotations. Then inject:

    @Inject @Linked @Hash private Map map;
    @Inject @Sorted private Map map;
    @Inject @Hash private Set set;
    @Inject @Hash private Set set;
    

    Now, is it worth doing this for a collections framework? I wouldn't do it, but I have a case similar to what I describe here in my current project at work (sorry, can't discuss).

  • And finally, you could use qualifiers to pass parameters to producers in conjunction with @Nonbinding. Continuing with the collections framework above, define:

    @Qualifier
    @Documented
    @Retention(RetentionPolicy.RUNTIME)
    public @interface Hash {
        @Nonbinding int capacity() default 16;
        @Nonbinding float loadFactor() default 0.75f;
    }
    

    This way, you could pass in a desired capacity and load factor to the producer returning anything hash table based like so:

    @Inject @Hash(capacity = 256, loadFactor = 0.85f) private Set set;
    @Inject @Hash private Set set;
    @Inject @Hash(capacity = 8, loadFactor = 0.65f) private Map map;
    

I hope this answers your question. It sure is part of why I love CDI.




回答2:


When do custom qualifiers need to be used?

CDI custom qualifiers only need to be used when: (a) your (non-annotation) code has deliberately introduced ambiguity in the type to use; (b) you wish to inject a more specific type of object than indicated by the code; (c) you wish to have more flexible type selection and configurability than provided by the predefined qualifier @Named.

Qualifiers disambiguate which type to be created/injected by CDI.
(a) & (b) occur when you use high-level types in code to give a powerful general algorithm that can be reused and flexibly adjusted. E.g. often it is possible and encouraged to code an entire algorithm against an interface rather than a specific class - such as algorithms against List, Map or Queue. But often these algorithms only work well if we commit to particular implementations of the interface such as a SortedList, TreeMap or PriorityQueue. If CDI is to act as the object factory, carrying out object lifecycle management, initialising the correct objects for injection and dependency management, then it needs to know the full details.

Custom Qualifiers are smart and configurable in how they disambiguate the type to be created/injected by CDI. @Named is a much blunter instrument. For (c), when I mention "type selection" I mean that the developer can combine multiple qualifiers together to "logically select" the most appropriate type, without naming the precise class. @Named effectively requires nomination of the precise class - multiple qualifiers will allow CDI to work it out for you. Configurability refers to the ability to change behaviour of CDI at deployment time by modifying beans.xml (no need to modify code/annotations). i.e. can logically tune what CDI is doing and can do it all the way up to deployment time - not touching code OR annotations.

When do custom qualifiers need to be declared?

CDI custom qualifiers don't need to be created for every individual type that can be injected. This is most important.

Multiple qualifiers can be used on both the injection point and on type declarations. CDI matches the bean type plus the SET OF qualifiers when determining what type to inject. To reduce the number of qualifiers to declare, it is a good idea to factor types into a number of attributes/concerns describing them. Then instead of one qualifier per type, can have one qualifier per type "attribute" - even if such an attribute is multi-valued such as Hash/Tree/Enum (see next paragraph).

Secondly, qualifiers should use parameters intelligently to further reduce the number of declarations required. Rather than creating 10 qualifiers you could have one qualifier which has a parameter that is a string or, preferably, an enum that can take on 10 values.

Combininge these two ideas (qualifiers=type attribute PLUS use qualifier parameters) into an example for a collections library: The types could be described with attributes represpenting Sorted/Non-Sorted? Hash/Tree/Array? Linked/Unlinked? If I specify a variable to be

@Inject @Sorted @Organisation("hash") @Navigation("linked") Map myMap;

then I have strong type selection because each of these aspects is guaranteed to be satisfied, I have a sorted linked hash map without knowing that this needs a very particular class in a particular package.

Points not been addressed yet:

If I use some annotation with parameterized bean name, then how it is actually type safe?

Strong typing is guaranteed because there must be a complete match with:

  • the bean type
  • the set of qualifiers
  • the set of parameter values

No injected type can violate any of these specified things. Instead of thinking of typesafety as matching of a single string (packagename.classname) think of it as matching of multiple strings/values totally controlled by the developer - these same strings/values are used on both the declaration & injection side giving safe matching. Also, instead of thinking that you must match to the lowest level concrete class - remember that you can match to higher level classes in the inheritance hierarchy and smart use of @Default qualifiers on your types (remember Java EE 6 "smart defaults over configuration"?) will take to you to the preferred concrete types.

If I use custom qualifier for type safety per service or dao (per interface), then for a large sized application like having 1000 or more service or dao classes with multiple implementations, it will be messy. Then for large sized applications, Is that feasible to use type safe injection?

  • 1000 or more service or dao classes? Really??! That would often flag a big design problem right away. Unless this is one super-mega-sized-guiness-records-attempt application for a complex business such as the tax department or NASA. Even then, it would be normal to break down into smaller Java EE modules / SOA services with much smaller number of classes. If this is what you have, you might want to look at simplifying your design - you might have far bigger problems then cut-and-past qualifier definitions...

  • Qualifiers are only needed where there is type ambiguity - i.e. lots of ancestor/descendant classes in the same type hierarchy. It is often the case that relatively few service or DAO classes are ambiguous.

  • As described above, don't use the one-qualifier-per-implementation-class pattern: instead refactor to use qualifier-per-type-aspect and also use descriptive parameters-per-qualifier implmentation patterns

  • It is feasible to use type safe injection with large applications.

If the answer of the above question is "No" then, what is the point to use type safety?

  • the answer is "Yes"
  • the scenario proposed (1000+ service/DAO classes) is very rare

Even if it is feasible to write annotations for type safety, in large applications, is it really worth the effort for just avoiding verbose xml configuration?

The purpose of CDI is not just to "avoid verbose xml configuration" CDI has the following goals:

  • Support for scopes (including new web conversation scope) with lifecycle management of objects
  • Typesafe dependency injection mechanism, including the ability to select dependencies at either development or deployment time, without verbose configuration
  • Support for Java EE modularity and the Java EE component architecture—the modular structure of a Java EE application is taken into account when resolving dependencies between Java EE components
  • Integration with the Unified Expression Language (EL), allowing any contextual object to be used directly within a JSF or JSP page
  • The ability to decorate injected objects
  • The ability to associate interceptors to objects via typesafe interceptor bindings
  • An event notification model
  • A web conversation context in addition to the three standard web contexts defined by the Java Servlets specification
  • An SPI allowing portable extensions to integrate cleanly with the container

This is WAY different and more useful than a basic XML config file turned into annotations - even the earlier basic Java EE 5 resource injection annotations were different & more useful than XML config turned into annotations.




回答3:


It is not required to create custom @Qualifier every time. It is enough to create just one with some parameter. CDI will treat @CustomQualifier("myBean") and @CustomQualifier("yourBean") as different qualifiers.

Furthermore, such qualifier already in CDI package - @Named

But sometimes static qualifiers are very useful, for example (see @Alternative):

@Alternative
@Qualifier
@Documented
@Retention(value=RUNTIME)
public @interface Mock

and you able to mark some beans as @Mock to use for testing only (don't forget to enable alternative section in beans.xml in test classpath.



来源:https://stackoverflow.com/questions/15231255/understanding-the-necessity-of-type-safety-in-cdi

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!