3 Replies Latest reply on Nov 21, 2016 4:50 AM by mkouba

    Assisted injection - I have a working prototype, but...

    steappe

      I'm currently in the process of integrating a DI framework in an old Java SE legacy application. As you can imagine, this application has many classes with multiple constructors that are not eligible for injections. As we want to minimize the redesign of the application (because we want to minimize the development cost of the DI integration), these legacy constructs are a real problem with CDI. Another approach of course could be to use Guice, which is a pretty good framework in such a situation. But I don't like much the bootstrapping effort required with Guice. The programmatic binding of managed beans is just a pain, and I'm not talking about the future maintenance of it, especially in case where some dependencies between beans would have to be changed for some reasons. By the way, I'd love to hear about solid arguments for and against Guice, so I can make the right choice.

       

      For the sake of a smooth integration, I've just implemented a CDI portable extension for the support of assisted injection, just like we can do with Guice:

      • The bean is annotated with an AssistedInject annotation that specifies the class of the factory interface that must be use to create the bean, and behind the scene that will be used to call the bean's constructor.
      • The constructor of the bean can have unmanaged parameters, and they are annotated with an Assisted annotation.

       

      The CDI portable extension proceeds as follows:

      • During the ProcessAnnotatedType event, it creates a Contextual Bean that acts as a producer of a dynamically created instance of the factory interface specified in the AssistedInject annotation.
      • It registers each of these Contextual Bean producers during the AfterBeanDiscovery event. Therefore, the factories specified in the AssistedInject annotations are eligible for injection in other beans.
      • When any of these factories is injected, a proxy is dynamically created and is associated to the constructor of the bean.
      • When the factory method of the factory is called, the constructor of the bean is invoked with the arguments specified by the application.

       

      This works pretty well, and I can even have bean constructors with a mixed of unmanaged and managed parameters (the managed parameters are injected by CDI).

       

      However, I have at least three problems left that I know so far:

      1. Observer methods of the bean are not scanned, probably because the bean is not a managed bean. These beans cannot receive events.
      2. I've scanned the observer methods myself, and I've added them to CDI during the AfterBeanDiscovery event, but still the observer methods are not called.
      3. I'd love to add the beans in some context, with a dependent scope, and I don't know how to do that.

       

      One idea I have to solve these issues is to dynamically create producers for the unmanaged parameters of the bean's constructor, and annotate the bean's constructor with Inject.

      I can produce the source code of the prototype (I will probably host it on GitHub soon), if it can help in the discussion.

       

      Thanks in advance for all the tips and suggestions you would provide to me to help me with the remaining problems, including but not limited to considering another approach.

        • 1. Re: Assisted injection - I have a working prototype, but...
          mkouba

          Hi Stéphane,

          I'm not really sure I fully understand the concept of "assisted injection". Although your approach seems sensible I can't say more without some examples of the legacy constructs you're trying to integrate. It would be great if you could share the source code of your integration together with some legacy constructs examples.

          1. Yes, the container does not search classes of custom beans added during AfterBeanDiscovery.
          2. This should work and is imho tested - pls provide some reproducer/test so that we can find the issue.
          3. This is not possible - dependent beans do not live in a context. They are scoped to the lifecycle of the bean they depend on.
          • 2. Re: Assisted injection - I have a working prototype, but...
            steappe

            Let's take an example of legacy code where assisted injection could help:

             

            public class SomeClass {

                private final int someInt;

                private final DataModel someDataModel;

             

                public SomeClass(int someInt, DataModel someDataModel) {

                }

            }

             

            This class is not a managed bean. I can hardly build a producer for this class, unless I have some mean to pass the factory method of the producer all the arguments needed for the construction of an instance of this class.

             

            So in that case, what I've done with a CDI portable extension is the following:

             

            @AssistedInject(SomeFactory.class)

            public class SomeClass {

                private final int someInt; // this is not a managed bean

                private final DataModel someDataModel; // this is a managed bean and it will be injected

             

                public SomeClass(@Assisted int someInt, DataModel someDataModel) {

                }

            }

             

            The factory named SomeFactory.class is an interface that is proxied automatically.

             

            The using code is:

            public class SomeUsingCode {

                 @Inject

                 private SomeFactory someFactory;

             

                 void foo(int someInt) {

                      SomeClass someClass = someFactory.create(someInt); // don't have to know the construction details of SomeClass, just have to pass the parameters that cannot be determined by the DI framework

                 }

            }

             

            This is working pretty well, but if SomeClass has an observer method, then it's not added to the bean manager because SomeClass is not a managed bean.

            • 3. Re: Assisted injection - I have a working prototype, but...
              mkouba

              Well, so you're basically creating a new instance of SomeClass referencing some bean instances. So why not use something like:

              public class SomeUsingCode {
                   @Inject
                   private DataModel dataModel;
              
                   void foo(int someInt) {
                        SomeClass someClass = new SomeClass(someInt, dataModel);
                        // do something with someClass instance
                   }
              }
              

              This should work fine (of course, if DataModel is a @Dependent bean it will be bound to the lifecycle of SomeUsingCode). I think the "assisted injection" pattern is rather an overkill for similar use cases. And of course, you will hit problems like those mentioned above because the instance produced by a factory is not a managed bean (observer methods, etc.).

               

              And if SomeClass is @Dependent you could also make use of injection point metadata to determine "non-injectable" values - see also Injection point metadata example.