CDI and JSF aren't Best Friends Yet

Where I started banging my head into the table.

This specific issue has actually has jumped up and bit me a few times so I’ll just try explain the most recent one. Basically I have a new object that I am creating through a JSF form page, and that object needs to have a default value set at creation which is determined at runtime. For the sake of exposing this little issue lets assume that this value needs to be used multiple times during the request and thus we will make the producer @RequestScoped to avoid duplicate database calls.

To the code!

@ConversationScoped
    @Named
    public class CarPricerManager implements Serializable {
    
        private static final long serialVersionUID = 1L;
    
        @Inject
        @Active
        private PriceBook activePriceBook;
    
        private CarPricer carPricer;
    
        public CarPricer getCarPricer() {
            if(carPricer == null) {
    
                carPricer = new CarPricer();
                carPricer.setPriceBook(activePriceBook);
            }
            return carPricer;
        }
    }
    
    public class PriceBooks {
    
        @Produces
        @RequestScoped
        @Active
        public PriceBook findActivePriceBook() {
            return em.createQuery(.....).getSingleResult();
        }
    
        @Produces
        @RequestScoped
        @Named
        public List<PriceBook> priceBooks() {
            return em.createQuery(.....).getResultList();
        }
    }

Now lets show the JSF snippet to select our price book when creating our new CarPricer

<h:selectOneMenu value="\#{carPricerManager.carPricer.priceBook}" required="true"
        converter="PriceBookConverter">
        <f:selectItem itemLabel="Choose" />
        <f:selectItems value="\#{priceBooks}" var="_apb" itemLabel="\#{_apb.name}" />
    </h:selectOneMenu>

Where it goes wrong.

What we would expect to happen is the selectOneMenu would initially be selected with the value of our active price book. However this isn’t going to be the case because the injection of our active price book does not give us an object of PriceBook.java it gives us an proxy of PriceBook.java and the equals(...) that JSF is going to call will not return true.

Why it goes wrong.

The CDI EG has determined that there is not meaningful way to implement .equals() and .hashCode() on a proxy that can delegate to the underlying bean (See OWB-458 & WELD-695). What this means is that two proxies of the same underlying bean will be equal. However, two proxies to underlying beans which are equal according to .equals will not be equal.

How can we fix this?

One solution would be to execute the .equals() on the converted value if a converter exists. However, the JSF RI Mojarra feels (JAVASERVERFACES-2393) that any proxy must should delegate equals, hashCode and toString to the proxied instance per java.lang.reflect.Proxy. In the case of CDI the underlying bean can change during the lifecycle of the proxy and thus delegating isn’t realistic. This appears to be one of those places where the JSF and CDI expert groups need to step in and find a solution so that developers can enjoy consistent and predictable behavior in their applications.

A solutions that work now.

By making the bean with the producers @RequestScoped and storing the values in the bean after initial population we can have default dependent scoped producers which don’t produce proxies and hit the database only once.

@RequestScoped
    public class PriceBooks {
    
        private PriceBook activeBook;
        private List<PriceBook> books;
    
        @Produces
        @Active
        public PriceBook findActivePriceBook() {
        if (activeBook == null)
            activeBook = em.createQuery(.....).getSingleResult();
            return activeBook;
        }
    
        @Produces
        @Named
        public List<PriceBook> priceBooks() {
        if (books == null) {
            books = em.createQuery(.....).getResultList();
            return books;
        }
    }
tags: cdi jsf java
Cody Lerum - September 11, 2012

CDI Instance Injections and Session/Application Scoped Beans

If you’ve spent much time with CDI you invariably had a use case where you would prefer not to trigger an injection when the bean is initialized. This can be handy for example if it is an expensive action and the injected bean is only going be be invoked a small percentage of the time.

There is however caveat that is relevant. The beans produced by the Instance will live until the containing bean is destroyed.

This doesn’t cause an issue with Dependent, Request, View, or Conversation scoped beans, but it would cause a unexpected memory leak with Application or even Session scoped beans. Since the Instance could be invoked multiple times spanning hours or days with a @SessionScoped bean and potentially weeks or months with an @ApplicationScoped bean (if your lucky to run your app uninterrupted that long) the memory leak could bring your app to a halt.

Improvement are being made to the CDI 1.1 spec to handled this better CDI-139

But in the meantime the following will work nicely

@Inject
    BeanManager bm;
    
    public void run() {
    
      CreationalContext<Foo> ctx = bm.createCreationalContext(null);
      Bean<?> beans = BeanManagerTools.beans(bm, Foo.class);
      Foo f = bm.getReference(beans, Foo.class, ctx);
    
      //Do Work
    
      ctx.release();
    }

Calling the release at the end destroys all the beans created in the Context we initialized (ctx)

tags: java cdi
Cody Lerum - December 16, 2011
About outjected.com

Outjected is an infrequent blog about the frequent issues and annoyances that pop up while coding.

These are living posts and will be updated as errors or improvements are found.
About Me
Google+