Spring Framework 目前不支持 JSF ViewScope,必須使用 Spring Bean 自定義之 Scope 來代理 JSF 2 的 ViewScope,使其成為一個 Spring Bean 的 Scope。
以下為實現方式與步驟:
- 定義 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(); } }
- 定義 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; } }
- 註冊自定義之 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; } ... }
- 註冊自定義 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>
- 如何使用?
@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 也可以以同樣方式來實現。