如何使用 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<Bar>
以找到您想要的可能会更容易。
我被告知您需要像我在上面所做的那样销毁Instance<Bar>
,并且可以根据经验验证上述代码是否有效;但是,我不能发誓你需要摧毁它。
【讨论】:
这工作正常,但可能会在单元测试中产生问题。【参考方案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 进行方法参数注入?的主要内容,如果未能解决你的问题,请参考以下文章