3 Replies Latest reply on Mar 22, 2010 12:01 PM by nickarls

    Method @Produces with varying implementations at runtime

    wujek
      I have been reading the Weld docs and reached 8.2 Injection into producer methods:

      @Produces @Preferred @SessionScoped
      public PaymentStrategy getPaymentStrategy(@New CreditCardPaymentStrategy ccps,
                                                @New CheckPaymentStrategy cps,
                                                @New PayPalPaymentStrategy ppps) {
         switch (paymentStrategy) {
            case CREDIT_CARD: return ccps;
            case CHEQUE: return cps;
            case PAYPAL: return ppps;
            default: return null;
         }
      }

      the point of injecting the parameters is that when we use new, the objects are not CDI managed so they don't have dependency injection, whereas the injected ones do.

      Question #1: are all 3 object created for each invocation of the method, or are they created once, and linger and are reused for consecutive calls as long as the instance of the bean that defines this method exists (possibly for the whole application with @Singleton or @ApplicationScoped?)

      If they are created each time, even for @ApplicationScoped, it would mean that for each invocation of this method 3 object would be created (potentially expensive ones), even though only one is eventually used.

      I have been thinking, would it not be possible to have functionality that producer methods could be intercepted, object would be created with new by the user, and upon return, the object returned would be injected by the container? It is very simple to use BeanManager to inject into an already existing instance, maybe the container could support such additional service? Producer methods are Bean<> instances anyways, managed by the container, (normally) not directly called by the user code so this should be fairly simple?

      Wujek
        • 1. Re: Method @Produces with varying implementations at runtime
          nickarls

          Yes, they are created each time (they're dependents).


          You could always inject a manager object that has stuff cached or use Instance etc if you don't want stuff created too often.


          I must say I didn't understand the last paragraph ;-)

          • 2. Re: Method @Produces with varying implementations at runtime
            wujek
            What I meant is this: the Bean that stands for a producer method is container managed, and this method is called by the container when a bean that this method produces (with the type and qualifiers) is required, it is not normally called by the user. So it would be fairly simple for the container to call this method, then intercept the result, perform injection on it and return this injected instance. This would enable the producer method to instantiate the object with the 'new' keyword, and still have such instances injected.

            You wrote: Yes, they are created each time (they're dependents).

            Weld docs say (section 8.3 from http://docs.jboss.org/weld/reference/1.0.0/en-US/html/producermethods.html#d0e3306):
            "Then a new dependent  instance of CreditCardPaymentStrategy will be created, passed to the producer method, returned by the producer method and finally bound to the session context. The dependent object won't be destroyed until the Preferences object is destroyed, at the end of the session."
            The above would imply that the created @New beans are dependent on the bean that defines the producer method, not on the bean on whose behalf the method was called.
            Suppose the Preferences is @ApplicationScoped. This means, that for 100 invocations of the producer method, there will exist 300 dependent objects, 200 of which were never used, and the remaining 100 were used but are not needed any more (they are dependent, and the bean that used this method is potentially out of scope - dependent itself, but the Preferences instance is @ApplicationScoped and it holds on to the objects). If the application runs for a long time, this might result in OutOfMemoryException, as the instances cannot be GC-ed. The option to allow 'new' creation and injecting afterwards would decrease the number of object by 3. And I still don't understand why the produced @New dependent objects are preserved for the whole lifetime of the Preferences object - shouldn't they be destroyed when the bean instance that needed them and on whose behalf the producer was called is destroyed? I see creating a manager (as suggested above) as a workaround - CDI has it's own contexts and scope management, so it is the manager for me, and should take care of removing unneeded instances, why would I want to create my own?
            (I base the above on my understanding of the quoted excerpt and your last post. If what I say is rubbish, feel free to bash me, I will be very happy to learn otherwise ;d).

            What I meant that it is simple to inject in to an instance is (I am sure you know this code):

            Object instance = ...
            Class<?> clazz = instance.getClass();
            Annotation[] qualifiers = getQualifiers(instance.getClass().getAnnotations()); // helper that uses BeanManager.isQualifier()
            AnnotatedType<?> annotatedType = beanManager.createAnnotatedType(instance.getClass());
            InjectionTarget<?> injectionTarget = beanManager.createInjectionTarget(annotatedType);
            Set<Bean<?>> beans = beanManager.getBeans(instance.getClass(), qualifiers);
            if (beans.size() == 0) {
                throw new IllegalStateException("class '" + clazz.getName()
                                + "' seems not to be a CDI managed bean; make sure it complies with "
                                + "the CDI specification requirements and the class is in a bean "
                                + "archive - there must be a 'beans.xml' file in appropriate location");
            }
            Bean<?> bean = beans.iterator().next();
            CreationalContext<?> creationalContext = beanManager.createCreationalContext(bean);
            InjectionTarget rawInjectionTarget = injectionTarget;
            rawInjectionTarget.inject(instance, creationalContext);

            I do it in our TestNG tests, where instance is the test class instance - this enables us to declare object in test classes using CDI typesafe injection, and have them injected for us. Works a charm.

            Wujek
            • 3. Re: Method @Produces with varying implementations at runtime
              nickarls

              I glanced at the code and I think I'll stand corrected.


              Producers have CreationalContexts (sort of bubbles around them that picks up what they produce so they can be destroyed as dependents when the producer-defining bean goes down), I get the picture that these contexts are searched first to see if they already exist from a previous invocation which keeps the number of instances to go out of control with @New (they're beans, too).