1. 程式人生 > >SSH框架中的使用OpenSessionInView的問題

SSH框架中的使用OpenSessionInView的問題

觀點一: 今天有一個朋友問了我一個問題,他使用的是Hibernate/Spring/Struts架構,配置使用Spring的 OpenSessionInView Filter,但是發現不生效,lazy的集合屬性在頁面訪問的時候仍然報session已經關閉的錯誤。我和他一起檢查了所有的配置和相關的程式碼,但是 沒有發現任何問題。經過除錯發現,應用程式使用的Session和OpenSessionInView Filter開啟的Session不是同一個,所以OpenSessionInView模式沒有生效,但是為什麼他們不使用同一個Session呢? 檢查了一遍Spring的相關原始碼,發現了問題的根源: 通常在Web應用中初始化Spring的配置,我們會在web.xml裡面配置一個Listener,即:
Xml程式碼  複製程式碼
  1. <listener>  
  2.     <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>  
  3. </listener>  
[xml]  view plain
copy
  1. <listener>        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>  </listener>  
如果使用Struts,那麼需要在Struts的配置檔案struts-config.xml裡面配置一個Spring的plugin:ContextLoaderPlugIn。 實際上ContextLoaderListener和ContextLoaderPlugIn的功能是重疊的,他們都是進行Spring配置的初 始化工作的。因此,如果你不打算使用OpenSessionInView,那麼你並不需要在web.xml裡面配置 ContextLoaderListener。 好了,但是你現在既需要Struts整合Spring,又需要OpenSessionInView模式,問題就來了! 由於ContextLoaderListener和ContextLoaderPlugIn功能重疊,都是初始化Spring,你不應該進行兩次 初始化,所以你不應該同時使用這兩者,只能選擇一個,因為你現在需要整合Struts,所以你只能使用ContextLoaderPlugIn。 但是令人困惑的是,ContextLoaderListener和ContextLoaderPlugIn有一個非常矛盾的地方! ContextLoaderListener初始化spring配置,然後把它放在ServletContext物件裡面儲存: [code:1]servletContext.setAttribute( WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);[/code:1] 請注意,儲存的物件的key是WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE! 但是ContextLoaderPlugIn初始化spring配置,然後把它放在ServletContext物件裡面儲存: [code:1] String attrName = getServletContextAttributeName(); getServletContext().setAttribute(attrName, wac);[/code:1] 這個attrName和WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE名字是不一樣的! 如果僅僅是名字不一樣,問題還不大,你仍然可以放心使用ContextLoaderPlugIn,但是當你使用OpenSessionInView的時候,OpenSessionInViewFilter是使用哪個key取得spring配置的呢? [code:1]WebApplicationContext wac = WebApplicationContextUtils.getRequiredWebApplicationContext(getServletContext());[/code:1] 顯然,OpenSessionInViewFilter是按照WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE這個key去拿spring配置的! 我們整理一下思路: ContextLoaderPlugIn儲存spring配置的名字叫做attrName; ,ContextLoaderListener儲存spring配置的名字叫做WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE; 而OpenSessionInView是按照WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE這個名字去取得spring配置的! 而你的應用程式卻是按照attrName去取得spring的配置的! 所以,OpenSessionInView模式失效! 解決辦法: 修改ContextLoaderPlugIn程式碼,在getServletContext().setAttribute(attrName, wac);這個地方加上一行程式碼: getServletContext().setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, wac); 或者修改OpenSessionInViewFilter,讓它按照attrName去取得spring配置。 觀點二: 我原來用struts/spring/hibernate的時候同樣使用OpenSessionInView,但是似乎沒有robbin所說的問題啊。而 且我在使用的時候,是ContextLoaderListener和ContextLoaderPlugIn一起用的。整個配置如下: 1.首先是web.xml [code:1] <filter> <filter-name>OpenSessionInViewFilter</filter-name> <filter-class>org.springframework.orm.hibernate.support.OpenSessionInViewFilter</filter-class> </filter> <filter-mapping> <filter-name>OpenSessionInViewFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> ...... [/code:1] 2. 然後是struts-config.xml: [code:1] <plug-in className="org.springframework.web.struts.ContextLoaderPlugIn"> <set-property property="contextConfigLocation" value="/WEB-INF/action-servlet.xml" /> </plug-in> [/code:1] 其餘部分省略。 在上述配置下,使用OpenSessionInView似乎沒有問題。 不知道robbin所說的ContextLoaderListener和ContextLoaderPlugIn不應該同時使用是不是做得是如下的配置:(struts-config.xml) [code:1] <plug-in className="org.springframework.web.struts.ContextLoaderPlugIn"> <set-property property="contextConfigLocation" value="/WEB-INF/applicationContext.xml, /WEB-INF/action-servlet.xml"/> </plug-in> [/code:1] 我嘗試了一下,用這種配置時,OpenSessionInView的確失效了。 我猜想,原因大概是這樣:struts的這個plugIn,可能只是為了整合一個action-servlet.xml,將action- servlet.xml中的定義當作Spring的bean來使用,因此,在儲存時,只要有action-servlet.xml的配置,就被儲存到 robbin所提到的那個attrName中,而不是 WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE中,所 以,OpenSessionInView是取不到這個配置的。 那麼這個配置什麼時候被取到呢?直覺告訴我,可能是和Action的Proxy有關。於是,查看了org.springframework.web.struts.DelegatingActionProxy的原始碼,果然: [code:1] /** * Return the delegate Action for the given mapping. * <p>The default implementation determines a bean name from the * given ActionMapping and looks up the corresponding bean in the * WebApplicationContext. * @param mapping the Struts ActionMapping * @return the delegate Action * @throws BeansException if thrown by WebApplicationContext methods * @see #determineActionBeanName */ protected Action getDelegateAction(ActionMapping mapping) throws BeansException { WebApplicationContext wac = getWebApplicationContext(getServlet(), mapping.getModuleConfig()); String beanName = determineActionBeanName(mapping); return (Action) wac.getBean(beanName, Action.class); } /** * Fetch ContextLoaderPlugIn's WebApplicationContext from the * ServletContext, containing the Struts Action beans to delegate to. * @param actionServlet the associated ActionServlet * @param moduleConfig the associated ModuleConfig * @return the WebApplicationContext * @throws IllegalStateException if no WebApplicationContext could be found * @see DelegatingActionUtils#getRequiredWebApplicationContext * @see ContextLoaderPlugIn#SERVLET_CONTEXT_PREFIX */ protected WebApplicationContext getWebApplicationContext( ActionServlet actionServlet, ModuleConfig moduleConfig) throws IllegalStateException { return DelegatingActionUtils.getRequiredWebApplicationContext(actionServlet, moduleConfig); } [/code:1] 仔細看其中的取wac的程式碼,它並不是從WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE取的wac。 由此,我相信,除了robbin講的修改原始碼以外,同時使用ContextLoaderListener和 ContextLoaderPlugIn,但是不要在ContextLoaderPlugIn裡面加入applicationContext.xml,只 要加入你的action-servlet.xml,我相信,同樣也可以非常流暢的使用OpenSessionInView