我正在尝试将构造函数注入模式应用于 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;
}
...
...
}
浏览不可代理 bean 类型的 CDI 规范,我看到:
3.15. 不可代理的 Bean 类型
容器使用代理来提供某些功能。某些合法的 Bean 类型不能由 容器:
- 没有没有参数的非私有构造函数的类,
- 被宣布为最终课程的类,
- 具有非静态的类,具有公共、受保护或 默认可见性,
- 基元类型,
- 和数组类型。
如果注入点解析为 豆:
- 需要客户端代理,或
- 具有关联的装饰器,或
- 有一个绑定拦截器。
否则,容器会自动检测问题,并处理 它作为一个部署问题。
在"正常作用域"和"伪作用域"一节中,它指出:
必须显式声明所有正常作用域@NormalScope,以向容器指示需要客户端代理。
鉴于@ApplicationScoped
bean 根据定义是@NormalScope
的,我需要有一个非私有的无参数构造函数。 那么我需要有一个受保护的无参数构造函数来满足 CDI 规范吗? 我已经尝试使用受保护的无参数构造函数,它似乎可以工作,但我不明白 WELD 在这种情况下是如何工作的;它在哪些条件下使用 no-args 构造函数? 为什么这是 CDI 的要求?
Weld 是否只使用 no-arg 来创建代理,但在实际调用底层实现时,它使用带有参数的基于注入的构造函数?
我将尝试以更广泛的方式回答它,如果我错过了什么,请在下面告诉我。
焊接需要做什么?
Weld 需要的是实例化您的@NormalScoped
Bean 的代理。这种代理不携带太多信息,它或多或少只是一个委托,它交给它而不是上下文实例。代理将是一个扩展你的bean的类 - 这在任何地方都没有说明,但这就是Weld(和OWB)的方式。如果你仔细想想,这是有道理的...类型安全,拦截/装饰IMPL等。它如何做到这一点的里程各不相同。(因为它扩展了 bean,这就是为什么有一个protected
no-args 构造函数就足够了。它必须调用超类的某个构造函数)
为什么会有这个限制?
使用no-arg构造函数的限制来自Java本身,其中以编程方式实例化对象的唯一合法方法是调用构造函数。请注意,我们不是在谈论代理的实例化,而不是 bean!调用参数化构造函数来创建代理实际上不是一个选项,因为您没有关于参数应该是什么的上下文。
Bean 可能有一个带注入的构造函数 (@Inject
) 但是代理需要创建一个无参数构造函数。
此外,它可能会防止循环注入的某些情况。 此外,它还可能触发与其链接的其他对象的意外启动。 您只是无法知道带有参数的构造函数内部可能发生了什么。
因此,CDI 规范要求您拥有无参数构造函数,以便 Weld 可以确保它始终存在,并可用于安全地实例化其代理,而不会产生任何副作用。
当您真的不能拥有无arg构造函数时,救命稻
草事实上,有一种方法可以绕过这个限制。一个不可移植的 Weld 配置选项,它不使用构造函数可以使用Unsafe
.如果您想知道如何启用它,请参阅文档。
我需要有一个受保护的 no-arg 构造函数来满足 CDI 规范? 它在哪些条件下使用 no-args 构造函数?为什么这是 CDI 的要求?
就像你引用的,在 CDI 规范中,如果 bean 没有 no-arg 构造函数,但有带有 args 的构造函数,它们将变得不可代理。它不是"仅仅为了规范",尽管从某种意义上说,需求没有任何用处:CDI使用的代理创建机制需要这个。他们首先创建代理,然后创建实现。
Weld 是否只使用 no-arg 来创建代理,但在实际调用底层实现时,它使用带有参数的基于注入的构造函数?
简而言之,是的。
我在类似情况下使用的一种替代方法(而不是@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。