如何从第 3 方为 @Inject 设置非 CDI bean 到 CDI bean
How to setup a non-CDI bean from a 3rd party for @Inject into CDI bean
虽然我在 CDI 中找到了设置 @Produces
(有点像工厂)或使用 CDI javax.enterprise.inject.spi.Unmanaged
概念的示例,但它们似乎都假定 CDI 将创建class 的实例以其自身的条款和生命周期(这是有道理的)。
但是,在某些情况下,CDI 根本无法创建实例。
例如在内部创建对象并将其提供给您的第 3 方库(不使用 CDI 本身)。
现在,我如何获取这些已经实例化的对象(顺便说一句,它们是没有默认构造函数的最终对象),然后让我的 CDI 托管 bean 可以使用它们?
这是一个简化的例子。
public class Foo
{
@Inject
private ByteBuffer buf;
public void go()
{
// do something, with buffer
}
}
public void process() {
ByteBuffer buf = ByteBuffer.allocate(500);
// TODO: how to add "buf" to current context?
Foo foo = CDI.current().select(Foo.class,AnyLiteral.INSTANCE).get();
foo.go();
}
现在,我意识到对于这个 特定的 示例,我可以很容易地传入 ByteBuffer,或者为 ByteBuffer 设置一个 @Produces
,甚至让 Foo make ByteBuffer 本身。 (所有这些都会更容易)。我选择了 ByteBuffer,因为它展示了我在第 3 方库中面临的相同问题
- 实例是有效的 beans
- 我无法控制它的来源
- 实例由库创建
- 这些实例是
final
,不能包装、覆盖或代理
该用例还存在嵌套 CDI 引用也可以使用对此 @Inject ByteBuffer buf;
的访问的情况。
理想情况下,希望这是一种纯 CDI api 技术,而不是特定于焊接或实现的技术。
我也研究了自定义作用域的创建,认为这可能是一个解决方案,有一种 @BufferScope
来标识这个的 start()
和 end()
实例。但是 none 的示例和文档非常清楚地说明了 CDI 无法调用 newInstance()
或 produce()
的对象。对象实例化不在我手中,但可以将其呈现给 CDI 范围,甚至可以管理该实例的最终死亡/破坏。
代理与包装不同。通常的解决方法是创建一个 ByteBufferHolder
(在此处插入您的 class),将自定义构建器流程调整为一个了解它位于 DI 上下文中的 bean。
我不确定我是否同意 CDI 假设它正在构建实例的原始断言。例如,我在 JSF 应用程序中使用以下代码来注入当前的 FacesContext 实例....
import javax.enterprise.context.RequestScoped;
import javax.enterprise.inject.Produces;
import javax.faces.context.FacesContext;
public class FacesContextProducer {
@Produces @RequestScoped FacesContext getFacesContext() {
return FacesContext.getCurrentInstance();
}
}
我没有在任何地方创建 FacesContext 实例。所需要的只是外部框架创建的内容是可访问的。
那么,如果您从外部来源获取实例,只要您可以获得正确的来源,@Produces 方法应该仍然有效吗?
这里有一些根本性的误解。不需要 CDI 来构造它可以通过生产者方法提供服务的 bean 实例。如果 class 不可代理,则从属范围将正常工作。您还可以为对象创建一个包装器 class,然后将所述 class 实例放入您需要的任何范围内。
你使用生产者方法,它可以像这样简单:
@Produces
@ApplicationScoped
private Foo produceFoo() {
final Foo instance = // acquire one of those instances you're talking about
return instance;
}
如果出于某种原因这对您不起作用,您也可以编写一个可移植的扩展并以编程方式添加一个 Bean
来满足您的需求。所以在 CDI 2.0+ 中是这样的(未经测试):
private void afterBeanDiscovery(@Observes final AfterBeanDiscovery event) {
final Foo instance = // acquire one of these final instances you're talking about
event.addBean()
.scope(ApplicationScoped.class)
.qualifiers(whateverYouNeed)
.addTransitiveTypeClosure(instance.getClass())
.createWith(cc -> instance) // you may also need .destroyWith()
;
}
设置好 "producing side" 后,在任何 CDI bean 中,您可以在任何地方执行以下操作:
@Inject
private Foo foo;
虽然我在 CDI 中找到了设置 @Produces
(有点像工厂)或使用 CDI javax.enterprise.inject.spi.Unmanaged
概念的示例,但它们似乎都假定 CDI 将创建class 的实例以其自身的条款和生命周期(这是有道理的)。
但是,在某些情况下,CDI 根本无法创建实例。
例如在内部创建对象并将其提供给您的第 3 方库(不使用 CDI 本身)。
现在,我如何获取这些已经实例化的对象(顺便说一句,它们是没有默认构造函数的最终对象),然后让我的 CDI 托管 bean 可以使用它们?
这是一个简化的例子。
public class Foo
{
@Inject
private ByteBuffer buf;
public void go()
{
// do something, with buffer
}
}
public void process() {
ByteBuffer buf = ByteBuffer.allocate(500);
// TODO: how to add "buf" to current context?
Foo foo = CDI.current().select(Foo.class,AnyLiteral.INSTANCE).get();
foo.go();
}
现在,我意识到对于这个 特定的 示例,我可以很容易地传入 ByteBuffer,或者为 ByteBuffer 设置一个 @Produces
,甚至让 Foo make ByteBuffer 本身。 (所有这些都会更容易)。我选择了 ByteBuffer,因为它展示了我在第 3 方库中面临的相同问题
- 实例是有效的 beans
- 我无法控制它的来源
- 实例由库创建
- 这些实例是
final
,不能包装、覆盖或代理
该用例还存在嵌套 CDI 引用也可以使用对此 @Inject ByteBuffer buf;
的访问的情况。
理想情况下,希望这是一种纯 CDI api 技术,而不是特定于焊接或实现的技术。
我也研究了自定义作用域的创建,认为这可能是一个解决方案,有一种 @BufferScope
来标识这个的 start()
和 end()
实例。但是 none 的示例和文档非常清楚地说明了 CDI 无法调用 newInstance()
或 produce()
的对象。对象实例化不在我手中,但可以将其呈现给 CDI 范围,甚至可以管理该实例的最终死亡/破坏。
代理与包装不同。通常的解决方法是创建一个 ByteBufferHolder
(在此处插入您的 class),将自定义构建器流程调整为一个了解它位于 DI 上下文中的 bean。
我不确定我是否同意 CDI 假设它正在构建实例的原始断言。例如,我在 JSF 应用程序中使用以下代码来注入当前的 FacesContext 实例....
import javax.enterprise.context.RequestScoped;
import javax.enterprise.inject.Produces;
import javax.faces.context.FacesContext;
public class FacesContextProducer {
@Produces @RequestScoped FacesContext getFacesContext() {
return FacesContext.getCurrentInstance();
}
}
我没有在任何地方创建 FacesContext 实例。所需要的只是外部框架创建的内容是可访问的。
那么,如果您从外部来源获取实例,只要您可以获得正确的来源,@Produces 方法应该仍然有效吗?
这里有一些根本性的误解。不需要 CDI 来构造它可以通过生产者方法提供服务的 bean 实例。如果 class 不可代理,则从属范围将正常工作。您还可以为对象创建一个包装器 class,然后将所述 class 实例放入您需要的任何范围内。
你使用生产者方法,它可以像这样简单:
@Produces
@ApplicationScoped
private Foo produceFoo() {
final Foo instance = // acquire one of those instances you're talking about
return instance;
}
如果出于某种原因这对您不起作用,您也可以编写一个可移植的扩展并以编程方式添加一个 Bean
来满足您的需求。所以在 CDI 2.0+ 中是这样的(未经测试):
private void afterBeanDiscovery(@Observes final AfterBeanDiscovery event) {
final Foo instance = // acquire one of these final instances you're talking about
event.addBean()
.scope(ApplicationScoped.class)
.qualifiers(whateverYouNeed)
.addTransitiveTypeClosure(instance.getClass())
.createWith(cc -> instance) // you may also need .destroyWith()
;
}
设置好 "producing side" 后,在任何 CDI bean 中,您可以在任何地方执行以下操作:
@Inject
private Foo foo;