Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Understanding the necessity of type Safety in CDI

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!

like image 822
Sazzadur Rahaman Avatar asked Mar 05 '13 18:03

Sazzadur Rahaman


1 Answers

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.

like image 192
rdcrng Avatar answered Sep 17 '22 19:09

rdcrng