4 Replies Latest reply on Jun 8, 2011 6:18 PM by gengis

    List and map injections

    gengis

      Dear all,


      I am trying to see how weld/jsr299 can represent a real alternative to Spring in terms of dependency injection. While simple injections are easy with @Inject, I did not understand how to inject dependencies in these situations :


      1) list injection. Example :



      @Stateless
      public void MyDao
      {
        private List<DataSource> ds;
      
        public void someMethod()
        {
           // iterate on ds and use the first working DataSource;  
        }
      }




      question : how would you say to Weld I want to use this datasource and this datasource in this specific order (assuming datasources are registered on the app server but here I do not want all of them).


      2) Map injection. Example 1 :


      @Stateless
      public void PublicationService
      {
        private Map<String, SpellChecker> checkers;
        // assuming SpellChecker is an interface with 2 implementations : FrenchSpellChecker and EnglishSpellChecker. Both would be session beans.
      
        public void publishDocument(Document doc) // document is a simple entity bean.
        {
          SpellChecker checker = this.checkers.get(doc.getLanguage()); // o.getLanguage() is a string, it could be fr or en.
          if(checker!=null)
          {
            checker.check(doc);
          }
          // do something with document, etc...
        }
      }



      Example 2 :


      @Stateless
      public void TranslationService
      {
        private Map<String, Interpreter> interpreters;
        // assuming Interpreter is an interface with 2 implementations : FrenchToEnglishInterpreter and EnglishToSpanishInterpreter. Both would be session beans.
      
        public String translate(String sentence, String langIn, String langOut)
        {
          Interpreter interpreter = this.interpreters.get(langInt+"-"+langOut);
          if(interpreter==null)
          {
            throw new UnsupportedTranslationException("Translation from "+langIn+" to "+langOut+" is not possible");
          }
          return interpreter.translate(sentence);
        }
      }
      



      Example 3 :


      public interface Observer
      {
        void notify(Object o);
      }
      
      public void MessageProcessor
      {
        private Map<String, List<Observer>> observers;
      
        public Object process(Object message)
        {
          for(Observer observer : this.observers.get(message.getClass().getName()))
          {
            observer.notify(message); // each implementation of Observer is a session bean 
          }
          return message;
        }
      }



      Question for these simple but real-world examples : how would you inject the map ?


      I am not trying to promote Spring but with xml configuration it's trivial to give to the container the 'recipe' to build these objects because xml allows to do exactly what you want, allows you to go beyond type matching injections. You can also register the same object in the container many times but with different injections, which is basically the definition of strategy pattern. Zero xml is appealing but reduces the injections to what can be 'autowired'.


      I think (I hope) I missed something, thanks for your help;

        • 1. Re: List and map injections
          gengis
          Dear all,

          No answer, I really would like to consider CDI as an alternative to Spring for serious DI but I feel like situations described hereabove cannot be implemented with CDI. Of course I can write a method that @Produces the list, the maps but the application becomes hard to understand : how would you know where do the list and the maps come from ? What if two methods @Produces the same thing and are both eligible for the same injection ?

          Another solution would consist in writing classes that would extends ArrayList<DataSource>, HashMap<String, SpellChecker>, HashMap<String, Interpreter> and HashMap<String, List<Observer>> where a @PostConstruct method would be responsible for adding/putting elements based on injected dependencies. Something like

          public class SpellCheckerConfig extends HashMap<String, SpellChecker>
          {
            private @EJB(beanName="frenchSpellChecker") SpellChecker frenchSpellChecker;
            private @EJB(beanName="englishSpellChecker") SpellChecker englishSpellChecker;
           
            @PostConstruct
            void init()
            {
              this.put("fr", this.frenchSpellChecker);
              this.put("en", this.englishSpellChecker);
            }
          }
          Pretty ugly. And it is still a kind of factory pattern and not strategy : at runtime I cannot have 2 instances of my PublicationService built with 2 different maps.

          It seems Java EE completly gave away xml, but it is just so convenient for everything that cannot be guessed from the code but has to be built from the code.

          Thanks for your opinions,
          • 2. Re: List and map injections
            asiandub

            That post definitely slipped through...


            When switching from Spring to CDI it's generally true that you'll have to adopt some of your use cases in order to benefit from the other technology. BTW, same scenario vice versa ;-)


            (1) I have problems to understand the use case, especially on the first working datasource - what would that be? or better: how did not working datasources make it on your application server?


            If you have more than one datasource in production, you would annotate the producer with different qualifiers, I'm sure you are aware of that option.


               @Produces @UserDatabase EntityManager userDatasource;
            
               @Produces @ProductDatabase EntityManager productDatasource;




            If you want to access all managed beans eligable for injection, you can make use of the injection-point:



            @Inject Instance<EntityManager> datasourceInstances;



            Instance implements Iterable and leaves you with the option to do whatever you want...


            • 3. Re: List and map injections
              asiandub

              (2) Same as in (1) - use Instance to iterate through all eligible beans documentation



              Pretty ugly. And it is still a kind of factory pattern and not strategy : at runtime I cannot have 2 instances of my PublicationService built with 2 different maps.

              yes :-)



              It seems Java EE completly gave away xml,

              Have a look at Seam-Config (here) - this allows to externalize any bean configuration into XML (this has already been part of the original JSR 299, but was removed later)...


              • 4. Re: List and map injections
                gengis
                Thanks Jan for not leaving me alone !

                For the datasource use case I admit the app server should be responsible for clustering, failover, and all this technical stuff. The idea was to illustrate that Instance<Something> gives no control on the order of the elements (like @Observes with Event<Something>)

                And for the map, well, your answer tends to confirm that the only way to implement the use cases hereabove is to create a class that extends HashMap, which contains a @PostConstruct routines responsible for filling the map. And in the exemple above (PublicationService, TranslationService, MessageProcessor), I must use hard coded beanName to make the difference between various implementations of the interface (here : SpellChecker, Interpreter, Observer) because @Inject does not seem to work on the concrete type when the class (a Session bean) implements an interface (because of proxy/wrapper built-in mechanism).

                It seems ('seams' ;)) that CDI/WebBeans/Seam team's lack of consideration for Spring results in missing some basic and usefull injection mechanisms available in Spring for years. But factory != strategy. Too bad that Spring did not contribute to the spec, their criticism indirectly resulted in major improvements (JSR 220) but now their disdain for Java EE does not help the community.

                About xml, one of the major criticism concerns readability and separation between code and conf but @Produces does not help to know 'what will be injected on this @Inject annotated field'. At least xml gives a single place for the 'injection' receipe. Anyway this two flaws could be overcome with good tooling (mouse over the field => description of what will be injected at runtime).