为什么我需要一个无 args 构造函数才能在 CDI 中使用 ApplicationScoped Bean 和构造函数注入?

我正在尝试将构造函数注入模式应用于我的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规格,我发现:

3.15. 不可复制的豆类

容器使用代理来提供某些功能。某些法定豆类型不能由容器代理:

  • 没有没有参数的非私有构造函数的类,
  • 被宣布为最终状态的类,
  • 具有非静态、最终方法的类,具有公共、受保护或默认可见性,
  • 基元类型,
  • 和数组类型。

如果注入点解析为 Bean,则 Bean 类型必须是可代理的:

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

否则,容器会自动检测问题,并将其视为部署问题。

“正常作用域和伪作用域”一节中,它指出:

必须在@NormalScope显式声明所有正常作用域,以向容器指示需要客户端代理。

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

Weld 是否只使用 no-arg 来创建代理,但在实际调用底层实现时,它使用带有参数的基于 inject 的构造函数?


答案 1

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

Weld需要做什么?

Weld 需要的是实例化 Bean 的代理。这样的代理不携带太多信息,它或多或少只是一个委托,它交给它而不是上下文实例。代理将是一个扩展你的bean的类 - 这在任何地方都没有说明,但这是Weld(和OWB)如何做到这一点。如果你仔细想想,这是有道理的...类型安全,拦截/装饰impl等。它如何做到这一点的里程各不相同。(因为它扩展了bean,这就是为什么有一个受保护的no-args构造函数就足够了。它必须调用超类的一些构造函数)@NormalScoped

为什么有限制?

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

Bean 可能有一个带有注入 () 的构造函数,但代理需要一个无 args 构造函数来创建。@Inject

此外,它可能会防止某些循环注入的情况。此外,它还可能触发与其链接的其他对象的意外初始化。你只是无法知道在带有参数的构造函数中可能发生了什么。

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

当您真的不能拥有无 arg 构造函数时,可以挽救生命

事实上,有一种方法可以解决这一限制。非可移植 Weld 配置选项,可以使用 ..r 而不是使用构造函数。如果您想了解如何启用它,请参阅文档Unsafe


答案 2

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

就像你引用的,在CDI规范中,如果bean没有no-arg构造函数,但有带有args的构造函数,那么它们将变得不可操作。它不是“只是为了规范”,尽管从某种意义上说,需求没有任何作用:CDI使用的代理创建机制需要这个。他们首先创建代理,然后创建实现。

Weld 是否只使用 no-arg 来创建代理,但在实际调用底层实现时,它使用带有参数的基于 inject 的构造函数?

简而言之,是的。

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

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。


推荐