8
votes

I'm trying to use CDI for my JSF/Java EE application. I have the following class hierarchy:

/**
 * base controller class
 * also contains some final methods and an inner enum class declaration
 */
public abstract class AbstractCrudController<K, E> implements Serializable {
  private Class<E> entityClass;

  public AbstractCrudController(Class<E> entityClass) {
    this.entityClass = entityClass;
  }

  // ...
}


import javax.enterprise.context.SessionScoped;
import javax.inject.Named;

@Named
@SessionScoped
public class CategoryController extends AbstractCrudController<Long, Category> implements Serializable {
  public CategoryController() {
    super(Category.class);
  }
  //...
}

When I try to deploy the application on GF 3.1, I get the following CDI/Weld exception:

SEVERE: Exception while loading the app : WELD-001435 Normal scoped bean class com.web.AbstractCrudController is not proxyable because it has no no-args constructor. org.jboss.weld.exceptions.UnproxyableResolutionException: WELD-001435 Normal scoped bean class com.web.AbstractCrudController is not proxyable because it has no no-args constructor. at org.jboss.weld.util.Proxies.getUnproxyableClassException(Proxies.java:215) at org.jboss.weld.util.Proxies.getUnproxyableTypeException(Proxies.java:166) at org.jboss.weld.util.Proxies.getUnproxyableTypesException(Proxies.java:191) at org.jboss.weld.bootstrap.Validator.validateBean(Validator.java:134) at org.jboss.weld.bootstrap.Validator.validateRIBean(Validator.java:148) at org.jboss.weld.bootstrap.Validator.validateBeans(Validator.java:363) at org.jboss.weld.bootstrap.Validator.validateDeployment(Validator.java:349) at org.jboss.weld.bootstrap.WeldBootstrap.validateBeans(WeldBootstrap.java:416) at org.glassfish.weld.WeldDeployer.event(WeldDeployer.java:178) at org.glassfish.kernel.event.EventsImpl.send(EventsImpl.java:128) at org.glassfish.internal.data.ApplicationInfo.start(ApplicationInfo.java:265) at com.sun.enterprise.v3.server.ApplicationLifecycle.deploy(ApplicationLifecycle.java:402) at com.sun.enterprise.v3.server.ApplicationLifecycle.deploy(ApplicationLifecycle.java:221) at org.glassfish.deployment.admin.DeployCommand.execute(DeployCommand.java:351) at com.sun.enterprise.v3.admin.CommandRunnerImpl$1.execute(CommandRunnerImpl.java:360) at com.sun.enterprise.v3.admin.CommandRunnerImpl.doCommand(CommandRunnerImpl.java:375) at com.sun.enterprise.v3.admin.CommandRunnerImpl.doCommand(CommandRunnerImpl.java:1072) at com.sun.enterprise.v3.admin.CommandRunnerImpl.access$1200(CommandRunnerImpl.java:101) at com.sun.enterprise.v3.admin.CommandRunnerImpl$ExecutionContext.execute(CommandRunnerImpl.java:1221) at com.sun.enterprise.v3.admin.CommandRunnerImpl$ExecutionContext.execute(CommandRunnerImpl.java:1210) at com.sun.enterprise.v3.admin.AdminAdapter.doCommand(AdminAdapter.java:375) at com.sun.enterprise.v3.admin.AdminAdapter.service(AdminAdapter.java:209) at com.sun.grizzly.tcp.http11.GrizzlyAdapter.service(GrizzlyAdapter.java:166) at com.sun.enterprise.v3.server.HK2Dispatcher.dispath(HK2Dispatcher.java:117) at com.sun.enterprise.v3.services.impl.ContainerMapper.service(ContainerMapper.java:234) at com.sun.grizzly.http.ProcessorTask.invokeAdapter(ProcessorTask.java:824) at com.sun.grizzly.http.ProcessorTask.doProcess(ProcessorTask.java:721) at com.sun.grizzly.http.ProcessorTask.process(ProcessorTask.java:1014) at com.sun.grizzly.http.DefaultProtocolFilter.execute(DefaultProtocolFilter.java:220) at com.sun.grizzly.DefaultProtocolChain.executeProtocolFilter(DefaultProtocolChain.java:135) at com.sun.grizzly.DefaultProtocolChain.execute(DefaultProtocolChain.java:102) at com.sun.grizzly.DefaultProtocolChain.execute(DefaultProtocolChain.java:88) at com.sun.grizzly.http.HttpProtocolChain.execute(HttpProtocolChain.java:76) at com.sun.grizzly.ProtocolChainContextTask.doCall(ProtocolChainContextTask.java:53) at com.sun.grizzly.SelectionKeyContextTask.call(SelectionKeyContextTask.java:57) at com.sun.grizzly.ContextTask.run(ContextTask.java:69) at com.sun.grizzly.util.AbstractThreadPool$Worker.doWork(AbstractThreadPool.java:530) at com.sun.grizzly.util.AbstractThreadPool$Worker.run(AbstractThreadPool.java:511) at java.lang.Thread.run(Thread.java:637)

Even if I add a no-args constructor to the base class, Weld still complains with the same exception that the class is not proxyable because it has final methods. Why does WELD force me to change my class design? Everything worked fine using the JSF @ManagedBean annotation.

I would appreciate any help. Thank, Theo

3

3 Answers

16
votes

Why does WELD force me to change my class design? Everything worked fine using the JSF @ManagedBean annotation.

Well, Weld/CDI doesn't work the same way. My understanding is that when you use injection to obtain a reference to a bean, what you get is in most cases a proxy object. This proxy object sub-classes your bean and overrides the methods to implement delegation. And this introduces some restrictions on the classes CDI can proxy.

The CDI spec puts it like this:

5.4.1. Unproxyable bean types

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 or have final methods,
  • primitive types,
  • and array types.

If an injection point whose declared type cannot be proxied by the container resolves to a bean with a normal scope, the container automatically detects the problem and treats it as a deployment problem.

My suggestion would be to make the methods non final.

References

  • CDI Specification
    • Section 5.4. "Client proxies"
    • Section 5.4.1 "Unproxyable bean types"
    • Section 6.3. "Normal scopes and pseudo-scopes"
0
votes

I am in the process of migrating from JSF Managed beans to CDI managed beans, and I just confirmed that I am able to use super successfully in a descendant CDI bean (with 'custom' @Descendant qualifier) that 'extends' an ancestor CDI bean (with @Default qualifier).

CDI bean ancestor with @Default qualifier:

@Default
@Named("pf_pointOfContactController")
@SessionScoped
public class pf_PointOfContactController implements Serializable {

Ancestor bean has the following:

@PostConstruct
protected void init() {

CDI bean descendant with @Descendant qualifier:

@Descendant
@Named("pf_orderCustomerPointOfContactController")
@SessionScoped
public class pf_OrderCustomerPointOfContactController extends pf_PointOfContactController {

Descendant bean has the following:

@PostConstruct
public void init(){
    super.init();

I had to add/use super.init(), because methods in the ancestor CDI bean were raising NullPointerException, since ancestor bean's @PostConstruct is not executed in CDI @Descendant bean.

I've seen/heard/read that it is recommended to use @PostConstruct instead of Constructor method when using CDI, so ancestor bean's constructor had the 'initialization' logic, and ancestor bean's constructor was automatically invoked/executed when using JSF managed beans.

0
votes

Because accepted answer is correct but incomplete I think I can add my two cents just for future readers.

The problem which OP encountered can be solved in two ways:

  1. By removing final keyword from methods and class itself
  2. Marking such "unproxyable class" with @Singleton or @Dependent pseudo-scope (of course if it makes sense). It will work because CDI doesn't create a proxy object for pseudo-scoped beans.

In the OP use case the second approach IMHO was recommended, as controllers definitely can be marked as singletons.

Hope it helps somebody