为什么我需要一个无参数构造函数来在 CDI 中使用 ApplicationScoped bean 和构造函数注入?

Why do I need a no-args constructor to use ApplicationScoped beans with Constructor injection within CDI?

我正在尝试将构造函数注入模式应用于我的 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)

确实,为了使用构造函数注入模式,我特意将 class 设计为具有需要参数的单个构造函数:

@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. Unproxyable bean types

The container uses proxies to provide certain functionality. Certain legal bean types cannot be proxied by the container:

  • classes which don’t have a non-private constructor with no parameters,
  • classes which are declared final,
  • classes which have non-static, final methods with public, protected or default visibility,
  • primitive types,
  • and array types.

A bean type must be proxyable if an injection point resolves to a bean:

  • that requires a client proxy, or
  • that has an associated decorator, or
  • that has a bound interceptor.

Otherwise, the container automatically detects the problem, and treats it as a deployment problem.

并且在 Normal scopes and pseudo-scopes 部分进一步指出:

All normal scopes must be explicitly declared @NormalScope, to indicate to the container that a client proxy is required.

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

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

I need to have a protected no-arg constructor just to satisfy the CDI spec? in which conditions does it use the no-args constructor? Why is this a requirement in CDI at all?

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

Does Weld only use the no-arg to create the proxy, but when actually calling the underlying implementation, it uses the inject-based constructor with arguments?

简而言之,是的。

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

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 规范是这样说的:

Support for constructor injection of JAX-RS resources is OPTIONAL. Portable applications MUST instead use fields or bean properties in conjunction with a @PostConstruct annotated method. Implementations SHOULD warn users about use of non-portable constructor injection.

所以它可能有效也可能无效,具体取决于实施情况。我在 class 的地方使用了 Weld。)

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

Weld需要做什么?

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

为什么要限制?

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

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

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

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

当你真的不能拥有无参数构造函数时的救星

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