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

Eric B. picture Eric B. · Jan 23, 2018 · Viewed 8k times · Source

I'm trying to apply the constructor injection pattern to beans in my CDI application and am encountering the following error message:

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)

Indeed, in order to use the constructor injection pattern, I have intentionally designed my class with a single constructor requiring arguments:

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

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

    ...
    ...

}

Looking through the CDI Specs of Unproxyable bean types, I see that:

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.

And in further in section Normal scopes and pseudo-scopes it states:

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

Given @ApplicationScoped beans are by definition @NormalScope, I need to have a non-private no-args constructor. So then I need to have a protected no-arg constructor just to satisfy the CDI spec? I've tried with a protected no-args constructor, and it seems to work, but I do not understand how WELD is working in that case; in which conditions does it use the no-args constructor? Why is this a requirement in CDI at all?

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?

Answer

Siliarus picture Siliarus · Jan 24, 2018

I am going to try an answer it in a bit broader fashion, if I miss something, let me know below.

What does Weld need to do?

What Weld needs is to instantiate a proxy of your @NormalScoped bean. Such proxy doesn't carry much information, it is more or less just a delegate which it hands around instead of the contextual instance. The proxy is going to be a class that extends your bean - this isn't stated anywhere, but it's how Weld (and OWB) does it. It makes sense if you think about it... type safety, interception/decoration impl and so on. The mileage of how it does this varies. (Because it extends the beans is why having a protected no-args constructor will suffice. It has to invoke some constructor of the superclass)

Why the limitation?

The limitation to have no-arg constructor comes from Java itself where the only legitimate way to programatically instantiate an object is to call a constructor. Please note that we are not talking instantiation of proxies, not beans! Invoking a parameterized constructor to create a proxy is not really an option because you have no context as to what the parameters should be.

The bean might have a constructor with injection (@Inject) but the proxy needs a no-args constructor to be created.

Also it would possibly prevent some scenarios with circular injection. Furthermore it could also trigger undesired initalization of other objects linked to it. You just cannot know what might be happening inside a constructor with parameters.

Therefore CDI spec requires you to have no-args constructor so that Weld can be sure it is always there and can be used to safely instantiate it's proxy without any side-effects.

A life-saver for when you truly cannot have no-arg constructor

As a matter of fact, there is a way around this limitation. A non-portable Weld configuration option, which instead of using constructor can use Unsafe. See the docs if you wanna know how to enable it.