Monday, July 16, 2012

Event-based communication in JSF. Old-school approach.

Web applications written in JSF consist of beans which interact among each other. Communication between beans is one of the main design patterns when developing a web application. Sometimes, one bean needs to send events to other beans to inform them about some changes or whatever else. We can normally inject a managed or Spring bean into the property of another bean, so that another bean can notify injected bean directly. Injection is good, but it was not introduced for the purpose of communication. It is far away from a dynamic loosely coupled system where each bean doesn't know about other beans. In a loosely coupled system we need a good event-based communication mechanism. This post will cover two design patterns: Observer / Event Listener and Mediator pattern. These patters are widely used in many web applications nowadays, but they have disadvantages. The system is not really loosely-coupled with them. There are much better and modern approaches. Therefore, I wrote "Old-school approach" in the post name. New-school approaches will be disclosed in the next post.

Observer / Event Listener
We will start with the Observer (also called as Event Listener) pattern. An object, called the subject or observable object, maintains a list of its dependents, called observers, and notifies them automatically of any state changes. In Java there are classes java.util.Observer and java.util.Observable which help to implement this pattern. Other related constructs for event-based communication by means of this pattern are the class java.util.EventObject and the interface java.util.EventListener. Let's start coding. Assume we have a I18N web application and user can choose a language (Locale) somewhere in user settings. Assume we have a bean called UserSettingsForm which is responsible for user settings. Some session scoped beans can keep I18N text / messages, so that when user changes the current languages, a reset of previous text / messages in the last selected language is needed. Firstly, we need a LocaleChangeEvent.
public class LocaleChangeEvent extends EventObject {
    
    Locale locale;

    public LocaleChangeEvent(Object source, Locale locale) {
        super(source);
        this.locale = locale;
    }

    public Locale getLocale() {
        return locale;
    }
}
Secondly, we need an interface LocaleChangeListener.
public interface LocaleChangeListener extends EventListener {
    
    void processLocaleChange(LocaleChangeEvent event);
}
Our UserSettingsForm can manage now instances of type LocaleChangeListener by registring and notifying them.
@ManagedBean
@SessionScoped
public class UserSettingsForm implements Serializable {

    private Locale selectedLocale;
    private List<SelectItem> locales;
    private List<LocaleChangeListener> localeChangeListeners = new ArrayList<LocaleChangeListener>();

    public void addLocaleChangeListener(LocaleChangeListener listener) {
        localeChangeListeners.add(listener);
    }

    public void localChangeListener(ValueChangeEvent e) {
        ...
        // notify listeners
        LocaleChangeEvent lce = new LocaleChangeEvent(this, this.selectedLocale);
        for (LocaleChangeListener lcl : localeChangeListeners) {
            lcl.processLocaleChange(lce);
        }
    }
    ...
}
The method localChangeListener() is an JSF ValueChangeListener and can be applied e.g. in h:selectOneMenu. Every bean which implements LocaleChangeListener should be registered by UserSettingsForm in order to be notified by locale changes.
@ManagedBean
@SessionScoped
public MyBean implements LocaleChangeListener, Serializable {

    // UserSettingsForm can be injected e.g. via @ManagedProperty annotation or via Spring facility
    private UserSettingsForm userSettingsForm;

    @PostConstruct
    public void initialize() {
        userSettingsForm.addLocaleChangeListener(this);
    }

    public void processLocaleChange(LocaleChangeEvent event) {
        // reset something related to I18N data
        ...
    }
}
In terms of Observer pattern the UserSettingsForm is Observable and instances of LocaleChangeListener (like MyBean) are Observers. The discussed pattern comes with some important issues that you need to be aware of. Beans are tightly coupled. There are a lot of manually work to regsiter beans. Beans have to implement defined interfaces. If you have a bean informed by 100 semantic different changes, it has to implement 100 interfaces. It is not possible to notify a subset of registered listeners - always all listeners get notified even if they don't need to be notified. Last but not least - memory management issue. Martin Fowler wrote "Assume we have some screens observing some domain objects. Once we close a screen we want it to be deleted, but the domain objects actually carry a reference to the screen though the observer relationship. In a memory-managed environment long lived domain objects can hold onto a lot of zombie screens, resulting in a significant memory leak."

Mediator
The Mediator pattern improves the event-based communication in comparison to the Observer / Event Listener pattern. With the mediator pattern, communication between objects is encapsulated with a mediator object. Objects no longer communicate directly with each other, but instead communicate through the mediator. This reduces dependencies between communicating objects. We will see how it works for JSF-Spring beans (in examples above were standard managed beans). We will implement a Mediator class to manage the communication between scoped beans. It is important to understand that a bean only can notify another beans having broader scope(s). A view scoped bean can notify view, session and application scoped beans, but not request scoped beans with the narrower scope. Follow this rule to avoid troubles. This is a nature of scoped bean - you might remember that you can always inject a bean of wider scope into a bean of narrower scope, but not vice versa. To start working with Mediator we will introduce two interfaces MediatorEvent, MediatorListener and the centric class Mediator.
public interface MediatorEvent {
    ...
}

public interface MediatorListener {

    public void listenToEvent(MediatorEvent event);
}

public class Mediator implements Serializable {

    private Collection<MediatorListener> collaborators = new HashSet<MediatorListener>();

    public static Mediator getCurrentInstance() {
        // access Mediator bean by JSF-Spring facility
        return ContextLoader.getCurrentWebApplicationContext().getBean("mediator");
    }

    public void fireEvent(MediatorEvent event) {
        for (MediatorListener mediatorListener : collaborators) {
            mediatorListener.listenToEvent(event);
        }
    }

    public void addCollaborator(MediatorListener collaborator) {
        collaborators.add(collaborator);
    }

    public void removeCollaborator(MediatorListener collaborator) {
        collaborators.remove(collaborator);
    }
}
Mediator is a scoped bean which can register and notify collaborators. Collaborators register themself by Mediator. In Spring, a bean can implement the interface InitializingBean, so that the method afterPropertiesSet() will be called automatically after the bean's instantiation. This is similar to @PostConstruct. afterPropertiesSet() is a right place for such bean to register itself by Mediator. The bean should also implement MediatorListener in order to be notified (see listenToEvent()).
public MyBean implements MediatorListener, InitializingBean, Serializable {

    public void afterPropertiesSet() throws Exception {
        ...
        Mediator.getCurrentInstance().addCollaborator(this);
    }

    @Override
    public void listenToEvent(MediatorEvent event) {
        if (event instanceof LocaleChangeEvent) {
            // do something
        }
    }
}
We will use the same scenario with UserSettingsForm and locale changing. Beans registered by Mediator will be notified by fireEvent().
public class LocaleChangeEvent implements MediatorEvent {
    ...
}

public class UserSettingsForm implements Serializable {

    private Locale selectedLocale;
    private List<SelectItem> locales;

    public void localChangeListener(ValueChangeEvent e) {
        ...
        // notify listeners
        Mediator.getCurrentInstance().fireEvent(new LocaleChangeEvent(this, this.selectedLocale));
    }
    ...
}
Mediator pattern offers better coupling between beans, but they are still coupled with mediator. Further disadvantages: It is still necessary to register beans manually - see extra code Mediator.getCurrentInstance().addCollaborator(this). Every bean should still implement at least one MediatorListener and that brings another constraint - listenToEvent(). Every bean should implement this interface method! The probably biggest shortcoming of Mediator pattern in JSF is that it is a scoped bean. A view scoped Mediator would only work smoothly with view scoped beans. Registered view scoped beans are being removed automatically when the view scoped Mediator gets destroyed. Other scenarios can cause memory leaks or several issues. For instance, request scoped beans, registered by a view scoped Mediator, should be removed manually by calling removeCollaborator() (easy to forget). Session scoped beans should be registered by a session scoped Mediator, otherwise they will not be notified after destroying the view scoped Mediator. Etc, etc.

In the fact, the Mediator pattern is only one step better than a regular Observer / Event Listener concept. There are more flexible approaches where *any method* can catch thrown event and not only fix specified, like listenToEvent(). In the next post, we will see easy and unobtrusive ways how to catch multiply events by only one method and other advices.

3 comments:

  1. Nice article! Can't wait to see the New-school approaches with JSF

    ReplyDelete
  2. Hi Oleg. Is there sources code that we can download. A lots of places where you said "//do something" or "// reset something related to I18N data", still puzzle me. It would be great if I can see the whole picture here with all the sources code. Thank you

    ReplyDelete
  3. Hi,

    No, I don't have public code. All samples in the post only show scaffolding without much business code. The code in action / action listeners is not relevant. Thus, I write "//do something" or "//reset something". It's not important for understanding. "//do something" can mean e.g. "I reset my DataTable list - set it to null and force new load - because the table has i18n text".

    ReplyDelete

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