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

2

19

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?

Hindi answered 23/1, 2018 at 20:42 Comment(5)
I don't believe Spring has any such restriction. I would say it's a better CDI implementation that Java EE.Sheppard
@Sheppard I don't think Spring DI implements CDI, even though it supports JSR-330 and JSR-250. If you meant to say it is better DI implementation I think I agree.Delicate
Does CDI stand for constructor dependency injection? If yes, then it most certainly does. I consider it preferable to setter injection. All annotation based.Sheppard
@Sheppard no, it stands for Contexts and Dependency Injection for the Java EE platform, which is a java EE specification.Delicate
I’ll have to look and see what it means. Rod Johnson was on the committee that wrote the standard, so I’d guess it was close to Spring.Sheppard
D
13

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.

Driscoll answered 24/1, 2018 at 8:49 Comment(3)
"calling parameterized one is not really an option because you have no context as to what value should the parameters have. With beans you might have one with injection but then again, proxies need not call such constructors as they are just delegates and it would also possibly prevent some scenarios with circular injection. " I think this is just not true as other DI frameworks, such as Spring, work doing this without issues. Also CDI does support constructor parameter injection as I showed in my example, albeit in a limited way.Delicate
Ah I might not have been clear enough, sorry. You have to differentiate between instantiating the bean for which you can use constructor injection (basically anything, you have have to be able to tell the DI framework which one to use) and the proxy object which is a basis for contexts in CDI. With proxy, you cannot use these for reasons I stated above and your question is more or less about proxies (normal scoped beans have them all). Is that clearer?Driscoll
"Please note that we are not talking instantiation of proxies, not beans!" I assume the first 'not' is wrong?Stature
D
8

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?

Like you quoted, in CDI spec, beans will become unproxyable if they don't have no-arg constructor but have ones with args. It's not "just for spec" though in the sense that requirement would serve no purpose: the proxy creation mechanisms used by CDI need this. They first create the proxy, then the implementation.

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?

In short, yes.

One alternative that I've used in similar scenario, instead of @ApplicationScoped, is @Singleton pseudoscope. That does work without no-param constructor as it is not using normal scope. This means though that the bean will not be proxied. For my use cases this has been ok. Here's an example 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));
    }
}

(Note though that if you use them for jax-rs resources like I have, jax-rs specification says this:

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.

So it might or might not work, depending on implementation. I used Weld for my class where it works.)

Delicate answered 23/1, 2018 at 21:26 Comment(5)
From what I read however, @Singleton is not detected by Weld when in annotated-bean discovery mode (ie: in beans.xml, bean-discovery-mode="annotated").Hindi
@EricB. according to this discussion, you're right in that it doesn't detect them by default, but you can use a stereotype for that.Delicate
Thanks for the pointer. I feel it is a little 'hackish' to have to create my own annotation to accomplish what I would expect weld to do it if the box. More importantly, if I have to do it that way, then I would expect that the is a prevailing logic that the designers of CDI had in mind when they wrote the spec which is what I'm trying to figure out/understand. I'm still not sure why this limitation is imposed.Hindi
bean-discovery-mode annotation is for a situation that you want to annotate your beans as beans yourself. A limited set of annotations is accepted for that, stereotype being one of them. Note that you don't have to create your own annotation, you can just have Singleton + Stereotype, or one annotation that has Stereotype on it (though I think Stereotype implies that you do). In general I agree, I think CDI is defined in an unintuitive ways sometimes.Delicate
Using Stereotype is also documented in the specification: docs.jboss.org/cdi/spec/1.2/…Delicate

© 2022 - 2024 — McMap. All rights reserved.