如何使用 CDI 进行方法参数注入?

Posted

技术标签:

【中文标题】如何使用 CDI 进行方法参数注入?【英文标题】:How to use CDI for method parameter injection? 【发布时间】:2012-05-29 21:30:34 【问题描述】:

是否可以使用 CDI 将参数注入方法调用?预期的行为将类似于场注入。查找首选生产商并使用产品。

我想做的是:

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

或者这个(语法不那么混乱):

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

这种语法在这两种情况下都是非法的。有替代方案吗?如果不是 - 如果可能的话,出于某种原因,这会是一个坏主意吗?

谢谢

编辑 - 我的要求可能不够明确 - 我希望能够直接调用该方法,将 bar 变量的初始化留给容器。 Jörn Horstmann 和 Perception 的回答表明这是不可能的。

【问题讨论】:

【参考方案1】:

当容器实例化 bean 时,会为 bean 处理注入点,这确实限制了方法级注入的用例数量。当前版本的规范认可以下类型的方法注入:

初始化方法注入

public class MyBean 
    private Processor processor;

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

MyBean 的实例被注入时,处理器实例也会被注入,通过它的setter 方法。

事件观察者方法

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

事件实例被直接注入到事件处理方法中(虽然,不是用@Inject注解)

生产者方法

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

生产者方法的参数自动被注入。

【讨论】:

谢谢你,感知。第一句话就足以毁了我的梦想:)“当它被实例化时”。我的想法应该像生产者方法一样工作,而不是一个。我猜我的用例不是专家组的意图。 是的,不幸的是,规范并没有强制要求方法调用成为 bean 生命周期管理的一部分。因此,直接调用方法将调用注入(类似于直接在对象上调用new)。如果方法注入进入规范的下一个版本,我不会感到惊讶。【参考方案2】:

如果你真正想要的不是方法的参数(应该由调用者提供),而是每次调用方法时正确初始化的 CDI bean 实例,并完全构造和注入,然后检查

javax.inject.Provider<T>

基本上,首先为类注入一个提供者

@Inject Provider<YourBean> yourBeanProvider;

然后,在方法中,获取一个新的实例

YourBean bean = yourBeanProvider.get();

希望这会有所帮助:)

【讨论】:

酷,谢谢。 bean 不必是方法参数。我只需要为每个方法调用正确初始化 bean 实例,而无需调用者提供它。所以你的答案很准确:)【参考方案3】:

这个问题是在我最初搜索这个主题时出现的,从那以后我了解到随着 CDI 1.1(包含在 JavaEE 7 规范中)的发布,现在有一种方法可以实际执行 OP 想要的操作,部分。你还是做不到

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

但你可以“注入”一个局部变量,虽然你不使用@Inject,而是像这样以编程方式查找注入的实例:

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

请注意,select() 方法可以选择采用您可能需要提供的任何限定符注释。祝你好运获得java.lang.annotation.Annotation 的实例。遍历您的Instance&lt;Bar&gt; 以找到您想要的可能会更容易。

我被告知您需要像我在上面所做的那样销毁Instance&lt;Bar&gt;,并且可以根据经验验证上述代码是否有效;但是,我不能发誓你需要摧毁它。

【讨论】:

这工作正常,但可能会在单元测试中产生问题。【参考方案4】:

CDI 的该功能称为“初始化方法”。语法与您的代码不同,因为整个方法用@Inject 注释,方法参数可以通过限定符进一步注释以选择特定的bean。 JSR 299 的第 3.9 节显示了以下示例,@Selected 是一个限定符,如果只有一个 bean 实现,则可以省略。

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

请注意

应用程序可以直接调用初始化方法,但容器不会向该方法传递任何参数。

【讨论】:

谢谢你,Jörn,我阅读了这个规范部分。您笔记中的情况正是我想做的 - 直接调用该方法并让容器提供一个 bean 实例。在 CDI 中是否还有另一种可能性?【参考方案5】:

您可以在方法中使用 BeanManager API 来获取上下文引用,或者根据您的最终目标,您可以注入一个

Instance<Bar>

在方法之外,在方法中使用。

【讨论】:

谢谢,召集人。很高兴知道它毕竟可以完成,尽管“手动”。我想,大多数情况下不保证显式 lokup 增加 WTF 潜力,但我会记住这种可能性。【参考方案6】:

如果您的目标是通过反射调用方法,则可以为每个参数创建一个InjectionPoint

以下是使用 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");
        
    

【讨论】:

以上是关于如何使用 CDI 进行方法参数注入?的主要内容,如果未能解决你的问题,请参考以下文章

启用 CDI 注入到由生产者方法创建的 bean

Gluon 默认使用啥 CDI 机制?

Spring MVC方法参数注入

CDI Features

Spring功能介绍SpringMVC集成Java Bean Validation实现参数检验功

Java 求spring用@Autowired进行方法参数注入例子。