Dec 16, 2012

How not to use @PostConstruct in JSF

Introduction

  I always thought that the right way of filling or initializing CDI (or either former javax.faces.* ManagedBean) backing beans with data from the persistent layer is by @PostConstruct annotated methods. I just recently came to the conclusion that this is really the worst idea.
  Lifecycle Phase Event listeners are maybe known to many, but most people tend to use it as a last resort, or to handle special cases. I think it needs to be changed. I hope after reading it, phase listeners will be among the first tools you will reach for to solve a problem, and not the last one.
  Below I am going to explain what I wanted to solve and how my Controller logic was evolving in such a way that I on the other hand was falling into the dark cave of confusion of JSF aspects and approaches.
  I tried to word my problem in a generic way, so many people have already met or will meet it.

 

What did I want to solve with @PostConstruct?

  I wanted to use @PostConstruct as a default action in my managed bean... When our application needs to maintain a state for the UI --the state changes in reaction to user interaction--, one would run into the problem: when to initialize the data (e.g. the list of cars) provided to the facelet ? I don't consider myself a newbie and I always seek for the best solution. I would not have written this long post, if finding the answer to this simple question had not been instructive experience to me :)

 

Basic architecture

  First of all, let me quickly introduce the architecture we use in our JSF applications and a very simplified JSF lifecycle applied to it.
  We have the MVC pattern on the presentation tier. The View is the .xhtml, the facelet, that renders and updates the data stored in a @SessionScoped managed bean. Latter is called the Model.
On the other side of the model, we have the @RequestScoped managed bean that is responsible for handling client initiated requests, and updates the model as well as the persistent data through the business tier. Therefore we can call it the Controller.

 

Simplified JSF lifecycle in the Basic architecture

User changes a form value, clicks on command button:
  1. JSF set's the properties in the Model through their setter methods.
  2. JSF calls the defined valueChangeListeners, actionListener, action methods in this order on the Controller bean. (I listed some action method types without attempt to be complete)
  3. Suppose, the user action was a bulk remove items from a list. OUR Controller retrieves the data from the Model and removes some items from it, then it also removes the exact same items from the persistent store through an EJB call, so the store and UI representation is consistent.
  4. JSF reads the updated model and presents the most recent data.

User visits the page on the first time:
  1. JSF calls getters on the Model and renders the facelet presenting data in the Model. (I did not mention calling any lifecycle phase listeners, because my predicate is that it does not get much attention from average JSF programmers)

 

The problem of model initialization and update

The example application is a list of people you know, where you can filter the people shown in the list according to whether you like them or not like them.
  Suppose that You have to present a list of people, and filter the list to show only the people you either hate, like or both. Where would you put the code that assembles the list? Don't forget it has to work in 3 different scenarios:
  1. The user visits the page for the first time from any link (i.e. you cannot rely on commandButton's action property to init the model)
  2. User changes the filter value and immediately sees the updated list
  3. User have changed the filter and visits the page again: the filter remains
A little sidenote here:
  I use icefaces ice and ace components. The biggest benefit of this framework is it uses AJAX by default. So when I change the selectOneMenu, the framework automatically triggers an AJAX request, set's the new value, updates and renders the involved components (e.g. everything within the enclosed form tags) and the result is immediately shown on the screen. This is important to the 2. point and that I don't want to deal with the <f:ajax /> tags because I'm lazy.


1st approach (the naive)

  1. The user visits the page the first time:
      The default value of PeopleModel.liked will be NULL. It is fine for us, the  PeopleRepositoryBean.findPeopleILike(Boolean) method will return everyone.
    Except one little problem: PeopleModel.people list will not be initialized, because we do not reference the PeopleController bean from the facelet --> it's @PostConstruct will not be called --> list of people remain empty (null)
  Why not put  PeopleController.init() into a PeopleModel.@PostConstruct method? Because we really want to focus on not confusing our controller logic with the model. Also, That would only be called once the session bean is initialized.

We go forward with the solution:
  Reference the PeopleModel via PeopleController in the facelet, so the PeopleController will be initialized, it's @PostConstruct method will be called






2nd approach (almost solved it):

  So we came to the conclusion that in order to initialize our model, we have to reference it from the controller
  1. The user visits the page the first time
    The list is initialized and shown on the gui, yeah!
  2. User changes the filter value and immediately sees the updated list
    Yeaaa, not so great :(

    • JSF sets the PeopleModel.liked field to whatever is chosen from selectOneMenu list AFTER it has called PeopleController.init().
      Why? Because we reference the PeopleModel.liked in the selectOneMenu via the PeopleController, thus this bean will be initialized first and will have it's @PostConstruct method called first. Problem? The list will be initialized according to the old value of liked field.
    • We could reference the PeopleModel directly within selectOneMenu. That would probably solve the problem, but would you rely on such a mechanism where the chain of events are determined by how you statically reference the backing beans? I would not.
  3. User have changed the filter and visits the page again: the filter remains
    Works great, the PeopleModel.liked is persisted within session.





3rd approach (a quicky, working code)

  In order to have the immediate impact of changing the filter on the UI, a convenient way is to put changeEventListener on the selectOneMenu component. We know by specification that it will be called after the value is set, seems to be the right choice to update our list.

  This code meets the three constraints we defined earlier. But don't be so happy. On filter change event, the list will be initialized twice: once with previous value, second with current value of filter. In this example it is not a big problem, but when you have  a complicated UI, you will try to avoid it.




 

4th The final (how it should have been done in the first place)

  And finally we realized the existence of JSF Lifecycle Phase Event Listeners and how every misery and suffering could have been avoided with this pure and elegant way.

  Notice the <f:event /> component in the facelet and the new PeopleController.updatePeopleList() method.

  What <f:event /> does is subscribes the defined method to the preRenderView event. This event will be triggered just before point 4) in the chart of architecture. So the PeopleController.init() method will be called every time right before the rendering phase, and after every backing bean properties are set.





More on JSF lifecycle phase event listeners


It must be noted, that JSF distinguishes between Application scoped lifecycle events and listeners and Component scoped lifecycle events and listeners. This example shows the use of a Component scoped lifecycle event listener that gets called by the framework when the request processing reaches the component in which the <f:event /> is specified (in this case in the <ice:form id="form">) in the same lifecycle phase as the <f:event /> specifies.

If you want to create Application scoped lifecycle event listener, you must specify your class that implements the javax.faces.event.PhaseListener interface and register your class in faces-config.xml such as:

<lifecycle>
    <phase-listener>com.example.MyPhaseListener</phase-listener>
</lifecycle>
You can also use @ListenerFor and @ListenersFor annotations if you create custom UIComponents or Renderers, but they are not intended to be used in ManagedBeans.

You can specify phase listeners programmatically instead of declaratively with
Application.subscribeToEvent() or UIComponent.subscibeToEvent() methods.

(More on lifecycle phase event listeners in JSF 2.0 specification sections 2.5.10 and 3.4.3)

Conclusion

  One would assume that JSF ManagedBean's lifecycle callback methods (PostConstruct, PrePersist) are integral parts of the JSF lifecycle. Why they really exist is because you cannot rely on your ManagedBean's constructor when you want to use injected resources while you are setting up a class (that you would normally do in a constructor), because resource injection comes after the class is instantiated, therefore cannot be accessed in the constructor. You should avoid using them the control your application's request flow.
  As I showed you in the last version of my code, you are assumed and encouraged to use JSF lifecycle phase listeners do such tasks as having a default action in your backing bean.

10 comments :

  1. Dude ive been having this problem almost 2 months.

    Your solution works PERFECTLY!!

    Many Thanks

    ReplyDelete
  2. Your problem could have been solved using a `@ViewScoped` annotation, but since you work with CDI you can't use it until now with the new shiny JSF 2.2 (currently JSF 2.2.1).

    ReplyDelete
    Replies
    1. It's a shame, but I haven't tried JSF 2.2 features yet. But in e.g. OmniFaces there is an extension for ViewScoped CDI beans that works with JSF 2.1

      Delete
  3. ViewScope is available even on pre JSF 2.2, you just have to define a CDI extension that makes it work for you. ;)

    ReplyDelete
  4. There is a point I wish to clarify but first of all, I will like to appreciate this posting. It's been helpful. A job well done.
    Please clarify this statement: "We could reference the PeopleModel directly within selectOneMenu. That would probably solve the problem, but would you rely on such a mechanism where the chain of events are determined by how you statically reference the backing beans? I would not."
    Reason being that, it just occurred to me that thru this post that exposing backing beans directly to the view is not a very gud idea. It's better thru the Controller...like #{peopleController.model.liked} for example. But towards the latter end of the post you still used this:
    exposing the backingBean directly to the view. Please shed more light on this.

    ReplyDelete
    Replies
    1. I might gave you the wrong impression of referencing the PeopleModel bean directily in the facelet is bad practice. What I really meant at that point is, the behaviour is determined by how you grab a reference to that same backing bean; it is determined by a side effect if you will.

      You can and it's simpler to reference the PeopleModel model bean directly.

      Delete
  5. Thanks a lot it's been very helpful !

    ReplyDelete
  6. 1st approach
    The controller is not created at all because is not referenced anywhere in the xhtml.

    2nd approach
    The fact that you reference the model *through* the controller is not important. It is simply the *existence* of a controller reference in the xhtml file that triggers the framework to try instantiate and invoke its @PostContruct init() method.
    The bigger problem here is that the controller initialization method is used as an event handler for a component of the page. This approach works, because the controller is declared with a request scope. If you had used a wider scope, such as view or session, the initialization method would be called only once, hence the problem would be immediately apparent.

    3rd approach
    In the right direction. I believe that an event listener is needed, in order to update the list, along with a wider scope for the controller. However calling the init method from the listener is not a good approach. Instead both the init and the listener should call the people repo.findPeopleLike(liked);

    4th approach
    I agree that the preRenderView event is better for initializing the controller bean. Besides the case you describe (request scoper controller) it also allows you to use mapped parameters from the request, which are not available during a call to the @PostContruct initialization method.

    ReplyDelete
  7. Thanks a lot for the post.

    However, when I tried method 4, I got the error :

    Target Unreachable, identifier 'employeeController' resolved to null: javax.el.PropertyNotFoundException

    Everything worked okay until I added @EJB in my application. Guide me please. Thanks

    ReplyDelete

Note: Only a member of this blog may post a comment.