Welcome

Spring View Scope For JSF 2 Users

In JSF 2 there are several new scopes introduced for managed beans and one of them is view scope, and as any developer who deal with JSF and Spring knows that it is much better to define your JSF managed beans in Spring application context instead of dealing with primitive DI container of JSF itself. Those who know me that I am not using JSF in my own projects any more. However, JSF is used for UI development in a project which I have responsibility on backend side, and in that project JSF managed beans were defined in JSF side. Some of them were scoped with view. When we decided to move those beans into Spring application context, we needed a view scope implementation. When I searched for a ready to use view scope implementation around Internet, I only came up with one or two code pieces which were not supporting life-cycle callbacks from Spring side. Therefore, I decided to implement a new one from scratch and put it here for those who may need it in their own projects as well.

public class ViewScope implements Scope {
	public static final String VIEW_SCOPE_CALLBACKS = "viewScope.callbacks";
	public synchronized Object get(String name, ObjectFactory<?> objectFactory) {
		Object instance = getViewMap().get(name);
		if(instance == null) {
			instance = objectFactory.getObject();
			getViewMap().put(name,instance);
		}
		return instance;
	}
	public Object remove(String name) {
		Object instance = getViewMap().remove(name);
		if(instance != null) {
			Map<string,Runnable> callbacks = (Map<string, Runnable>) getViewMap().get(VIEW_SCOPE_CALLBACKS);
			if(callbacks != null) {
				callbacks.remove(name);
			}
		}
		return instance;
	}
	public void registerDestructionCallback(String name, Runnable runnable) {
		Map<string,Runnable> callbacks = (Map<string, Runnable>) getViewMap().get(VIEW_SCOPE_CALLBACKS);
		if(callbacks != null) {
			callbacks.put(name,runnable);
		}
	}
	public Object resolveContextualObject(String name) {
		FacesContext facesContext = FacesContext.getCurrentInstance();
		FacesRequestAttributes facesRequestAttributes = new FacesRequestAttributes(facesContext);
		return facesRequestAttributes.resolveReference(name);
	}
	public String getConversationId() {
		FacesContext facesContext = FacesContext.getCurrentInstance();
		FacesRequestAttributes facesRequestAttributes = new FacesRequestAttributes(facesContext);
		return facesRequestAttributes.getSessionId() + "-" + facesContext.getViewRoot().getViewId();
	}
	private Map<string,Object> getViewMap() {
		return FacesContext.getCurrentInstance().getViewRoot().getViewMap();
	}
}

As you may have noticed destruction callbacks are also kept in JSF view map. I also made use of Spring’s FacesRequestAttributes in order to give answer for contextual object queries like view, request, session, application etc. Conversation id have to be unique for each view instance, therefore I prefixed current view id with session id so that it became unique accross different users who access same view.

We need a point in order to initialize callbacks map. Fortunately JSF 2 provides a system event facility which notify view map post construction and pre destruction phases. Therefore I also implemented a ViewMapListener in order to initialize callbacks map after view map construction, and execute destruction callbacks just before view map cleanup.

public class ViewScopeCallbackRegistrar implements ViewMapListener {
	public void processEvent(SystemEvent event) throws AbortProcessingException {
		if(event instanceof PostConstructViewMapEvent) {
			PostConstructViewMapEvent viewMapEvent = (PostConstructViewMapEvent)event;
			UIViewRoot viewRoot = (UIViewRoot)viewMapEvent.getComponent();
			viewRoot.getViewMap().put(ViewScope.VIEW_SCOPE_CALLBACKS,new HashMap<string,Runnable>());
		} else if(event instanceof PreDestroyViewMapEvent) {
			PreDestroyViewMapEvent viewMapEvent = (PreDestroyViewMapEvent)event;
			UIViewRoot viewRoot = (UIViewRoot)viewMapEvent.getComponent();
			Map<string,Runnable> callbacks = (Map<string, Runnable>) viewRoot.getViewMap().get(ViewScope.VIEW_SCOPE_CALLBACKS);
			if(callbacks != null) {
				for(Runnable c:callbacks.values()) {
					c.run();
				}
				callbacks.clear();
			}
		}
	}

	public boolean isListenerForSource(Object source) {
		return source instanceof UIViewRoot;
	}
}

Finally we have to configure those two parts in Spring and JSF sides. In order to be able to use view scope, we have to introduce it to Spring in some way. I prefer declarative way. We need to define CustomScopeConfigurer bean in Spring application context, and add “view” scope as new entry.

<bean class="org.springframework.beans.factory.config.CustomScopeConfigurer">
    <property name="scopes">
        <map>
            <entry key="view">
                <bean class="com.example.ViewScope"/>
            </entry>
        </map>
    </property>
</bean>

For the JSF side, we have to register our ViewMapListener in application element of faces-config.xml.

<system-event-listener>
    <system-event-listener-class>com.example.ViewScopeCallbackRegistrar</system-event-listener-class>
    <system-event-class>javax.faces.event.PostConstructViewMapEvent</system-event-class>
    <source-class>javax.faces.component.UIViewRoot</source-class>
</system-event-listener>

<system-event-listener>
    <system-event-listener-class>com.example.ViewScopeCallbackRegistrar</system-event-listener-class>
    <system-event-class>javax.faces.event.PreDestroyViewMapEvent</system-event-class>
    <source-class>javax.faces.component.UIViewRoot</source-class>
</system-event-listener>

Bir cevap yazın

E-posta hesabınız yayımlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir

This site uses Akismet to reduce spam. Learn how your comment data is processed.