【问题标题】:How to use CDI for method parameter injection?如何使用 CDI 进行方法参数注入?
【发布时间】: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 的回答表明这是不可能的。

【问题讨论】:

    标签: java dependency-injection cdi jboss-weld


    【解决方案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");
            }
        }
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-04-22
      • 1970-01-01
      • 2014-01-01
      • 2015-12-16
      • 1970-01-01
      相关资源
      最近更新 更多