JSF2 ViewScope with Spring

Spring Framework 目前不支持 JSF ViewScope,必須使用 Spring Bean 自定義之 Scope 來代理 JSF 2 的 ViewScope,使其成為一個 Spring Bean 的 Scope。

以下為實現方式與步驟:

  1. 定義 ViewScope 實現類:
    package com.cy.commons.web.scopes;
    
    import java.util.Map;
    import javax.faces.context.FacesContext;
    import org.omnifaces.util.Faces;
    import org.springframework.beans.factory.ObjectFactory;
    import org.springframework.beans.factory.config.Scope;
    import org.springframework.web.context.request.FacesRequestAttributes;
    
    /**
     * 
    
     * 依賴此jar後, 需使用Spring custom ViewScope需設置如下兩項設定:
     * 
    
     *
     * 
    
     * 1.於web project之Spring javaConfig上import commons-web的javaConfig:
     * 
    
     *
     * 
    
     * @Configuration
     * 
    @Import({com.cy.commons.web.config.AppConfig.class})
     * 
    ...
     * 
    public class AppConfig {
     * 
    ...
     * 
    }
     * 
    
     *
     * 
    
     * 2.於web project之faces-config.xml上註冊自定義ViewMapListener:
     * 
    
     *
     * 
    
     * <faces-config xmlns="http://java.sun.com/xml/ns/javaee" * xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" * xsi:schemaLocation="http://java.sun.com/xml/ns/javaee * http://java.sun.com/xml/ns/javaee/web-facesconfig_2_1.xsd" * version="2.1">
     * 
    ...
     * 
    <application>
     * 
    <el-resolver>org.springframework.web.jsf.el.SpringBeanFacesELResolver</el-resolver>
     * 
    <system-event-listener>
     * 
    <source-class>javax.faces.component.UIViewRoot</source-class>
     * 
    <system-event-listener-class>com.cy.yourproject.web.scopes.listeners.ViewScopeCallbackRegistrar</system-event-listener-class>
     * 
    <system-event-class>javax.faces.event.PostConstructViewMapEvent</system-event-class>
     * 
    </system-event-listener>
     * 
    <system-event-listener>
     * 
    <source-class>javax.faces.component.UIViewRoot</source-class>
     * 
    <system-event-listener-class>com.cy.yourproject.web.scopes.listeners.ViewScopeCallbackRegistrar</system-event-listener-class>
     * 
    <system-event-class>javax.faces.event.PreDestroyViewMapEvent</system-event-class>
     * 
    </system-event-listener>
     * 
    </application>
     * 
    ...
     * 
    </faces-config>
     * 
    
     *
     * 
    
     * 如何使用:
     * 
    
     *
     * 
    
     * 
    @Scope("view")
     * 
    @Controller public class Adm02Bean implements Serializable {
     * 
    
     * @Autowired private PaymentAccountSetManager manager;
     * 
    @Autowired private OrgManager orgManager;
     * 
    @Autowired private AccountManager accountManager;
     * 
    
     * ...
     * 
    
     * }
     * 
    
     *
     * @see
     * <a href="http://confluence.bb.com:8090/x/noB2AQ">http://confluence.bb.com:8090/x/noB2AQ</a>
     *
     * @author Eden Liu <eden90267@gmail.com>
     */
    public class ViewScope implements Scope {
    
        public static final String VIEW_SCOPE_CALLBACKS = "viewScope.callbacks";
    
        @Override
        public Object get(String name, ObjectFactory<?> objectFactory) {
            Map<String, Object> viewMap = getViewMap();
            Object instance = viewMap.get(name);
            if (instance == null) {
                instance = objectFactory.getObject();
                synchronized (viewMap) {
                    viewMap.put(name, instance);
                }
            }
            return instance;
        }
    
        @SuppressWarnings("unchecked")
        @Override
        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;
        }
    
        @Override
        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);
            }
        }
    
        @Override
        public Object resolveContextualObject(String name) {
            FacesContext fc = Faces.getContext();
            FacesRequestAttributes facesRequestAttributes = new FacesRequestAttributes(fc);
            return facesRequestAttributes.resolveReference(name);
        }
    
        @Override
        public String getConversationId() {
            FacesContext fc = Faces.getContext();
            FacesRequestAttributes facesRequestAttributes = new FacesRequestAttributes(fc);
            return facesRequestAttributes.getSessionId() + "-" + Faces.getViewId();
        }
    
        private Map<String, Object> getViewMap() {
            return Faces.getViewMap();
        }
    
    }
    
  2. 定義 ViewMapListener 實現類:
    package com.cy.commons.web.listeners;
    
    import com.cy.commons.web.scopes.ViewScope;
    import java.util.HashMap;
    import java.util.Map;
    import javax.faces.component.UIViewRoot;
    import javax.faces.event.AbortProcessingException;
    import javax.faces.event.PostConstructViewMapEvent;
    import javax.faces.event.PreDestroyViewMapEvent;
    import javax.faces.event.SystemEvent;
    import javax.faces.event.ViewMapListener;
    
    /**
     *
     * @author Eden Liu <eden90267@gmail.com>
     */
    public class ViewScopeCallbackRegistrar implements ViewMapListener {
    
        @Override
        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();
                }
            }
        }
    
        @Override
        public boolean isListenerForSource(Object source) {
            return source instanceof UIViewRoot;
        }
    
    }
    
  3. 註冊自定義之 Scope:
    @Configuration
    ...
    public class AppConfig {
      
        ...
    
        @Bean
        public static CustomScopeConfigurer customScopeConfigurer() {
            CustomScopeConfigurer customScopeConfigurer = new CustomScopeConfigurer();
            Map<String, Object> map = Maps.newHashMap();
            map.put("view", new ViewScope());
            customScopeConfigurer.setScopes(map);
            return customScopeConfigurer;
        }
    
        ...
    }
    
  4. 註冊自定義 ViewMapListener (於 faces-config.xml 上):
    <faces-config xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-facesconfig_2_1.xsd" version="2.1">
    
        ...
    
        <application>
            <el-resolver>org.springframework.web.jsf.el.SpringBeanFacesELResolver</el-resolver>
            <system-event-listener>
                <source-class>javax.faces.component.UIViewRoot</source-class>
                <system-event-listener-class>com.cy.yourproject.web.scopes.listeners.ViewScopeCallbackRegistrar</system-event-listener-class>
                <system-event-class>javax.faces.event.PostConstructViewMapEvent</system-event-class>
            </system-event-listener>
            <system-event-listener>
                <source-class>javax.faces.component.UIViewRoot</source-class>
                <system-event-listener-class>com.cy.yourproject.web.scopes.listeners.ViewScopeCallbackRegistrar</system-event-listener-class>
                <system-event-class>javax.faces.event.PreDestroyViewMapEvent</system-event-class>
            </system-event-listener>
        </application>
    
        ...
    
    </faces-config>
    
  5. 如何使用?
    @Scope("view")
    @Controller
    public class Adm02Bean implements Serializable {
    
        @Autowired
        private PaymentAccountSetManager manager;
        @Autowired
        private OrgManager orgManager;
        @Autowired
        private AccountManager accountManager;
    
        ...
    
    }
    

結語:

讓 Spring 提供 JSF 可以使用的 ViewScope 的 backing beans,可享受到 Spring 容器提供的好處,同時也可以使用這個很方便的 scope。對於 JSF2 中的 FlashScope 也可以以同樣方式來實現。

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *

這個網站採用 Akismet 服務減少垃圾留言。進一步了解 Akismet 如何處理網站訪客的留言資料