Apr 21, 2015

JSF view scope for Spring beans

Hello everyone.

If you are using JSF with spring beans, than you may notice that there are no alternative for JSF view scope for spring beans.

If you will try to add JSF annotations to Spring beans - you will fail. It will not work because native JSF  beans actually have another context environment than spring. Thus all JSF annotations and configuration from faces-config.xml can not be applied to Spring beans.

If you think that you are already binded Srping with JSF like this:
<!-- somwhere in faces-config.xml -->
<application> 
    <el-resolver>org.springframework.web.jsf.el.SpringBeanFacesELResolver</el-resolver>
</application>
Then you probably missing the point, that this trick allowing only access Spring beans from JSF pages by id. It just skipt JSF scope with all it features and goes through to the Srping.

So what we have to do about this?


Solution 1 - forget about Spring beans in JSF pages

I will write how to do it a little bit later

Solution 2 - let's code

The main idea is to use original JSF context view map for storing Spring beans. The simplest solution to do this is implement your own Spring Scope to interact with FacesContext.getCurrentInstance().getViewRoot().getViewMap().

import java.util.Map;
import javax.faces.context.FacesContext;
import org.springframework.beans.factory.ObjectFactory;
import org.springframework.beans.factory.config.Scope;

public class SpringViewJsfScope implements Scope {

    @Override
    public Object get(String name, ObjectFactory objectFactory) {
        Map viewMap = FacesContext.getCurrentInstance().getViewRoot().getViewMap();

        if (viewMap.containsKey(name)) {
            return viewMap.get(name);
        } else {
            Object object = objectFactory.getObject();
            viewMap.put(name, object);
            return object;
        }
    }

    @Override
    public Object remove(String name) {
        return FacesContext.getCurrentInstance().getViewRoot().getViewMap().remove(name);
    }

    @Override
    public String getConversationId() {
        return null;
    }

    @Override
    public void registerDestructionCallback(String name, Runnable callback) {
        //Not supported
    }

    @Override
    public Object resolveContextualObject(String key) {
        return null;
    }
}

Next you just have to add this scope to your applicationContext.xml like this:
<bean class="org.springframework.beans.factory.config.CustomScopeConfigurer">
    <property name="scopes">
        <map>
            <entry key="view">
                <bean class="your.package.SpringViewJsfScope"/>
            </entry>
        </map>
    </property>
</bean>

WARNING! 
Above example covers only basic functionality and may not be useful on highly loaded projects.

Solution 3 - handy artifact

There is a code from one guy. I've take it, do some patches and publish as maven artifact. 

This is the same idea as in solution 2, but with a little bit more coding. This plugin will allow you to integrate Spring view scope for JSF very easy. All you need to to is:
1. add artifact to your maven or gradle project
2. put next line into your Spring config
<import resource="classpath:/com/github/javaplugs/jsf/jsfSpringScope.xml"/>
Now you will be able to use:
  • scope="view" in xml config
  • @SpringScopeView annotation
  • @SpringScopeRequest @SpringScopeSession annotations for other scopes