【问题标题】:Why do I need a no-args constructor to use ApplicationScoped beans with Constructor injection within CDI?为什么我需要一个无参数构造函数才能在 CDI 中使用带有构造函数注入的 ApplicationScoped bean?
【发布时间】:2018-01-23 20:42:23
【问题描述】:

我正在尝试将构造函数注入模式应用于我的 CDI 应用程序中的 bean,但遇到以下错误消息:

15:18:11,852 ERROR [izone.adams.webapp.error.IzoneExceptionHandler] (default task-40) org.jboss.weld.exceptions.UnproxyableResolutionException: WELD-001435: Normal scoped bean class webapp.util.LoginManagerAction is not proxyable because it has no no-args constructor - <unknown javax.enterprise.inject.spi.Bean instance>.
        at org.jboss.weld.bean.proxy.DefaultProxyInstantiator.validateNoargConstructor(DefaultProxyInstantiator.java:50)

确实,为了使用构造函数注入模式,我特意设计了我的类,其中包含一个需要参数的构造函数:

@ApplicationScoped
@Typed(LoginManagerAction.class)
public class LoginManagerAction extends UtilBasicDispatchAction {

  @Inject
   public LoginManagerAction( SessionManager sessionManager, JMSHealthCheckService jmsHealthCheckService) {
       super();
       this.sessionManager = sessionManager;
       this.jmsHealthCheckService = jmsHealthCheckService;
   }

    ...
    ...

}

查看CDI Specs of Unproxyable bean types,我看到了:

3.15。不可代理的 bean 类型

容器使用代理来提供某些功能。某些合法的 bean 类型不能被代理 容器:

  • 没有无参数的非私有构造函数的类,
  • 声明为 final 的类,
  • 具有非静态最终方法的类具有公共、受保护或 默认可见性,
  • 原始类型,
  • 和数组类型。

如果注入点解析为 豆子:

  • 需要客户端代理,或
  • 具有关联的装饰器,或
  • 具有绑定拦截器。

否则,容器会自动检测到问题,并处理 这是一个部署问题。

Normal scopes and pseudo-scopes 部分的进一步说明:

所有普通范围必须显式声明@NormalScope,以向容器指示需要客户端代理。

鉴于@ApplicationScoped bean 的定义是@NormalScope,我需要一个非私有的无参数构造函数。那么我需要一个受保护的无参数构造函数来满足 CDI 规范吗?我尝试过使用受保护的无参数构造函数,它似乎可以工作,但我不明白 WELD 在这种情况下是如何工作的;它在什么情况下使用无参数构造函数?为什么这是 CDI 中的一项要求?

Weld 是否只使用无参数来创建代理,但在实际调用底层实现时,它使用基于注入的构造函数和参数?

【问题讨论】:

  • 我不相信 Spring 有任何这样的限制。我会说它是一个比 Java EE 更好的 CDI 实现。
  • @duffymo 我不认为 Spring DI 实现了 CDI,即使它支持 JSR-330 和 JSR-250。如果你的意思是说它是更好的 DI 实现,我想我同意。
  • CDI 代表构造函数依赖注入吗?如果是,那么它肯定会。我认为最好是二传手注入。基于所有注释。
  • @duffymo 不,它代表Contexts and Dependency Injection for the Java EE platform,这是一个java EE规范。
  • 我得看看它是什么意思。 Rod Johnson 是编写标准的委员会成员,所以我猜它接近 Spring。

标签: java jakarta-ee dependency-injection cdi weld


【解决方案1】:

我将尝试以更广泛的方式回答它,如果我遗漏了什么,请在下面告诉我。

Weld 需要做什么?

Weld 需要实例化您的 @NormalScoped bean 的代理。这样的代理不携带太多信息,它或多或少只是一个委托而不是上下文实例。代理将是一个扩展您的 bean 的类——这在任何地方都没有说明,但 Weld(和 OWB)就是这样做的。如果您考虑一下,这是有道理的……类型安全、拦截/装饰 impl 等等。它如何做到这一点的里程各不相同。 (因为它扩展了 bean,所以拥有一个 protected 无参数构造函数就足够了。它必须调用超类的一些构造函数)

为什么会有限制?

无参数构造函数的限制来自 Java 本身,其中以编程方式实例化对象的唯一合法方法是调用构造函数。 请注意,我们不是在谈论代理的实例化,而不是 bean! 调用参数化构造函数来创建代理并不是一个真正的选择,因为您没有关于参数应该是什么的上下文。

bean 可能有一个带有注入的构造函数 (@Inject) 但是代理需要创建一个无参数的构造函数。

此外,它可能会阻止循环注入的某些情况。 此外,它还可能触发与其链接的其他对象的意外初始化。 您只是无法知道带参数的构造函数内部可能发生的情况。

因此,CDI 规范要求您有无参数构造函数,以便 Weld 可以确保它始终存在并且可以用于安全地实例化它的代理,而不会产生任何副作用。

当你真的无法拥有无参数构造函数时的救命稻草

事实上,有办法绕过这个限制。一个不可移植的 Weld 配置选项,可以使用 Unsafe 而不是使用构造函数。如果您想知道如何启用它,请参阅the docs

【讨论】:

  • "调用参数化的不是一个真正的选项,因为你没有关于参数应该具有什么值的上下文。对于 bean,你可能有一个带有注入的bean,但是再一次,代理不需要调用这样的构造函数他们只是代表,它也可能会阻止循环注入的某些情况。”我认为这是不正确的,因为其他 DI 框架(例如 Spring)可以毫无问题地执行此操作。 CDI 也支持构造函数参数注入,如我在示例中所示,尽管方式有限。
  • 啊,我可能还不够清楚,抱歉。您必须区分实例化可以使用构造函数注入的 bean(基本上任何东西,您必须能够告诉 DI 框架使用哪一个)和 代理对象 这是 CDI 中上下文的基础。使用代理,由于我上面提到的原因,您不能使用这些,您的问题或多或少是关于代理的(普通范围的 bean 都有它们)。是不是更清楚了?
  • “请注意,我们不是在谈论代理的实例化,而不是 bean!” 我假设第一个 'not' 是错误的?
【解决方案2】:

我需要一个受保护的无参数构造函数来满足 CDI 规范吗? 它在什么情况下使用无参数构造函数?为什么这是 CDI 中的一项要求?

就像你引用的那样,在 CDI 规范中,如果 bean 没有无参数构造函数但有带参数的构造函数,它们将变得不可代理。它不是“仅用于规范”,尽管从某种意义上说,需求不会起到任何作用:CDI 使用的代理创建机制需要这个。他们首先创建代理,然后是实现。

Weld 是否只使用无参数来创建代理,但在实际调用底层实现时,它使用基于注入的构造函数和参数?

简而言之,是的。

我在类似场景中使用的一种替代方法是@Singleton 伪范围,而不是@ApplicationScoped。这在没有无参数构造函数的情况下确实有效,因为它没有使用正常范围。这意味着尽管 bean 不会被代理。对于我的用例,这没问题。这是一个示例类:

import javax.inject.Inject;
import javax.inject.Singleton;
import javax.validation.Valid;
import javax.ws.rs.Consumes;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.container.AsyncResponse;
import javax.ws.rs.container.Suspended;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;

@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
@Path("/add")
@Singleton
public class CounterController {

    private CounterService counterService;

    @Inject
    public CounterController(@Context CounterService counterService) {
        this.counterService = counterService;
    }

    @POST
    public void add(@Suspended final AsyncResponse asyncResponse, @Valid
            CounterRequest counterRequest) {
        asyncResponse.resume(counterService.count(counterRequest));
    }
}

(请注意,如果您像我一样将它们用于 jax-rs 资源,jax-rs 规范会这样说:

对 JAX-RS 资源的构造函数注入的支持是可选的。 便携式应用程序必须改为使用字段或 bean 属性 与 @PostConstruct 注释方法结合使用。实现 应该警告用户使用不可移植的构造函数注入。

所以它可能会或可能不会工作,具体取决于实施。我在我的课堂上使用了 Weld。)

【讨论】:

  • 从我读到的内容来看,@Singleton 在带注释的 bean 发现模式下未被 Weld 检测到(即:在 beans.xml 中,bean-discovery-mode="annotated")。跨度>
  • @EricB。根据this discussion,您是对的,默认情况下它不会检测到它们,但您可以为此使用刻板印象。
  • 感谢您的指点。我觉得必须创建我自己的注释来完成我希望焊接在盒子里做的事情有点“骇人听闻”。更重要的是,如果我必须这样做,那么我希望这是 CDI 的设计者在编写规范时所考虑的普遍逻辑,这是我试图弄清楚/理解的。我仍然不确定为什么会施加这种限制。
  • bean-discovery-mode 注释适用于您希望自己将 bean 注释为 bean 的情况。为此接受了一组有限的注释,刻板印象就是其中之一。请注意,您不必必须创建自己的注释,您可以只使用 Singleton + Stereotype,或一个带有 Stereotype 的注释(尽管我认为 Stereotype 暗示您这样做)。总的来说,我同意,我认为 CDI 有时以不直观的方式定义。
  • 使用 Stereotype 也记录在规范中:docs.jboss.org/cdi/spec/1.2/…
猜你喜欢
  • 2019-03-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-09-15
  • 1970-01-01
  • 2023-04-09
  • 1970-01-01
  • 2011-04-02
相关资源
最近更新 更多