How to use CDI for method parameter injection?
Asked Answered
S

6

26

Is it possible to use CDI to inject parameters into method calls? The expected behaviour would be similar to field injection. The preferred producer is looked up and the product is used.

What I would like to do is this:

public void foo(@Inject Bar bar){
  //do stuff
} 

or this (with less confusing sytax):

public void foo(){
  @Inject 
  Bar bar;
  //do stuff
} 

This syntax is illegal in both cases. Is there an alternative? If no - would this be a bad idea for some reason if it were possible?

Thank you

EDIT - I may have made my requirements not clear enough - I would like to be able to call the method directly, leaving the initialization of the bar variable to the container. Jörn Horstmann's and Perception's answer suggest that it is not possible.

Starter answered 23/5, 2012 at 10:32 Comment(0)
C
20

Injection points are processed for a bean when it is instantiated by the container, which does limit the number of uses cases for method level injection. The current version of the specification recognizes the following types of method injection:

Initializer method injection

public class MyBean {
    private Processor processor;

    @Inject
    public void setProcessor(final Processor processor) {
        this.processor = processor;
    }
}

When an instance of MyBean is injected, the processor instance will also be injected, via it's setter method.

Event Observer Methods

public class MyEventHandler {
    public void processSomeEvent(@Observes final SomeEvent event) {
    }
}

The event instance is injected into the event handling method directly (though, not with the @Inject annotation)

Producer Methods

public class ProcessorFactory {
    @Produces public Processor getProcessor(@Inject final Gateway gateway) {
        // ...
    }
}

Parameters to producer methods automatically get injected.

Clew answered 23/5, 2012 at 11:32 Comment(2)
Thank you, Perception. The first sentence was enough to ruin my dream :) "when it is instantiated". What I had in mind should work like a producer method without being one. I guess my use case wasn't intended by the expert group.Starter
Yes, unfortunately the the specification does not mandate that method invocations be part of the bean lifecycle management. So, calling the method directly will not invoke injection (its similar to calling new directly on the object). I would not be surprised though if method injection makes it into the next version of the spec.Clew
U
12

If what you REALLY want is not something as the parameter of the method (which should be provided by the caller), but a properly initialized instance of a CDI bean each time when the method is called, and fully constructed and injected, then check

javax.inject.Provider<T>

Basically, first inject a provider to the class

@Inject Provider<YourBean> yourBeanProvider;

then, in the method, obtain a new instance

YourBean bean = yourBeanProvider.get();

Hope this helps :)

Uncanonical answered 1/4, 2014 at 4:13 Comment(1)
Cool, thanks. The bean does not have to be a method parameter. I just needed a properly initialized bean instance for each method call without having the caller to provide it. So your answer nails it :)Starter
C
8

This question came up when I originally did a search on this topic, and I have since learned that with the release of CDI 1.1 (included in the JavaEE 7 spec), there is now a way to actually do what the OP wanted, partially. You still cannot do

public void foo(@Inject Bar bar){
   //do stuff
}

but you can "inject" a local variable, although you do not use @Inject but rather programmatically look up the injected instance like this:

public void foo() {
    Instance<Bar> instance = CDI.current().select(Bar.class);
    Bar bar = instance.get();
    CDI.current().destroy(instance);
    // do stuff with bar here
}

Note that the select() method optionally takes any qualifier annotations that you may need to provide. Good luck obtaining instances of java.lang.annotation.Annotation though. It may be easier to iterate through your Instance<Bar> to find the one you want.

I've been told you need to destroy the Instance<Bar> as I have done above, and can verify from experience that the above code works; however, I cannot swear that you need to destroy it.

Cranach answered 17/2, 2016 at 17:41 Comment(1)
This works fine but might create problems in unit tests.Ison
I
5

That feature of CDI is called an "initializer method". The syntax differs from your code in that the whole method is annotated with @Inject, the method parameters can further be annotated by qualifiers to select a specific bean. Section 3.9 of JSR 299 shows the following example, with @Selected being a qualifier that can be omitted if there is only one bean implementation.

@Inject
void setProduct(@Selected Product product) {
    this.product = product;
}

Please note that

The application may call initializer methods directly, but then no parameters will be passed to the method by the container.

Iodous answered 23/5, 2012 at 11:19 Comment(1)
Thank you, Jörn, I read this spec part up. The case in your note is exacty what I would like to do - call the method directly AND have the container provide a bean instance. Is there another possibility in CDI for that?Starter
T
1

You can use the BeanManager API in your method to get contextual references, or depending on your ultimate goal you could inject an

Instance<Bar>

outside of the method and use it in the method.

Tralee answered 27/5, 2012 at 22:8 Comment(1)
Thanks, covener. Nice to know that it can be done after all, though "manually". I guess, most cases do not warrant for the added WTF-potential of an explicit lokup, but i'll keep the possibility in mind.Starter
T
0

If your goal is to call the method via reflection, it is possible to create an InjectionPoint for each parameter.

Here's an example using CDI-SE:

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.enterprise.context.ApplicationScoped;
import javax.enterprise.context.Dependent;
import javax.enterprise.context.spi.CreationalContext;
import javax.enterprise.inject.se.SeContainer;
import javax.enterprise.inject.se.SeContainerInitializer;
import javax.enterprise.inject.spi.AnnotatedMethod;
import javax.enterprise.inject.spi.AnnotatedType;
import javax.enterprise.inject.spi.BeanManager;

public class ParameterInjectionExample {

    public static class Foo {

        // this method will be called by reflection, all parameters will be resolved from the BeanManager
        // calling this method will require 2 different Bar instances (which will be destroyed at the end of the invocation)
        public void doSomething(Bar bar, Baz baz, Bar bar2) {
            System.out.println("got " + bar);
            System.out.println("got " + baz);
            System.out.println("got " + bar2);
        }
    }

    @Dependent
    public static class Bar {

        @PostConstruct
        public void postConstruct() {
            System.out.println("created " + this);
        }

        @PreDestroy
        public void preDestroy() {
            System.out.println("destroyed " + this);
        }
    }

    @ApplicationScoped
    public static class Baz {

        @PostConstruct
        public void postConstruct() {
            System.out.println("created " + this);
        }

        @PreDestroy
        public void preDestroy() {
            System.out.println("destroyed " + this);
        }
    }

    public static Object call(Object target, String methodName, BeanManager beanManager) throws Exception {
        AnnotatedType<?> annotatedType = beanManager.createAnnotatedType(target.getClass());
        AnnotatedMethod<?> annotatedMethod = annotatedType.getMethods().stream()
                .filter(m -> m.getJavaMember().getName().equals(methodName))
                .findFirst() // we assume their is only one method with that name (no overloading)
                .orElseThrow(NoSuchMethodException::new);
        // this creationalContext will be valid for the duration of the method call (to prevent memory leaks for @Dependent beans)
        CreationalContext<?> creationalContext = beanManager.createCreationalContext(null);
        try {
            Object[] args = annotatedMethod.getParameters().stream()
                    .map(beanManager::createInjectionPoint)
                    .map(ip -> beanManager.getInjectableReference(ip, creationalContext))
                    .toArray();
            return annotatedMethod.getJavaMember().invoke(target, args);
        } finally {
            creationalContext.release();
        }
    }

    public static void main(String[] args) throws Exception {
        try (SeContainer container = SeContainerInitializer.newInstance().disableDiscovery().addBeanClasses(Bar.class, Baz.class).initialize()) {
            System.out.println("beanManager initialized");
            call(new Foo(), "doSomething", container.getBeanManager());
            System.out.println("closing beanManager");
        }
    }
}
Toothbrush answered 8/5, 2021 at 13:56 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.