2012-08-15 128 views
2

我需要獲取代理會話作用域的012,317,517,476,676,838,上的bean。目標是當它被破壞時(通過invalidate()或超時)進行會話清理。我添加了ContextLoaderListener來公開上下文,並通過WebApplicationContextUtils.getWebApplicationContext()獲得了bean。在會話超時時無法獲取會話作用域bean

一切工作正常,如果我自己在Servlet中使自己無效,但會話超時時,我得到一個Scope 'session' is not active for the current thread;。我明白,問題發生在清理由Servlet引擎的內部線程完成,但我仍然需要能夠從HttpSessionListener獲取此bean。

我似乎有很多相同的問題,沒有人得到解決方案,這就是爲什麼我再次問。

我的applicationContext.xml沒有bean聲明,因爲我使用了註釋。

這個我需要會話時候訪問了:

@Component 
@Scope(value="session", proxyMode=ScopedProxyMode.TARGET_CLASS) 
public class Access { 

    static private int SERIAL = 0; 
private int serial; 

    public Access() { 
      serial = SERIAL++; 
    } 

    public int getSerial() { 
      return serial; 
    } 
} 

這是控制器,要麼create或手動destroy會話。

@Controller 
public class Handler { 

    @Autowired 
    Access access; 


    @RequestMapping("/create") 
    public @ResponseBody String create() { 
     return "Created "+access.getSerial(); 
    } 

    @RequestMapping("/destroy") 
    public @ResponseBody String destroy(HttpSession sess) { 
     int val = access.getSerial(); 
     sess.invalidate(); 
     return "Destroyed "+val; 
    } 

} 

而這正是我需要訪問Access會議的內容的HttpSessionListener是聽會遭到破壞,範圍的bean

public class SessionCleanup implements HttpSessionListener { 

@Override 
public void sessionDestroyed(HttpSessionEvent ev) { 

    // Get context exposed at ContextLoaderListener 
    WebApplicationContext ctx = WebApplicationContextUtils 
     .getWebApplicationContext(ev.getSession().getServletContext()); 

    // Get the beans 
    Access v = (Access) ctx.getBean("access"); 

    // prints a not-null object 
    System.out.println(v); 

    // this line raise the exception 
    System.out.println(v.getSerial()); 

    } 


    @Override 
    public void sessionCreated(HttpSessionEvent ev) {/*Nothing to do*/} 

} 

以下例外情況發生在v.getSerial()

Ago 14, 2012 11:44:58 PM org.apache.catalina.session.StandardSession expire 
SEVERE: Session event listener threw exception 
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'scopedTarget.access': Scope 'session' is not active for the current thread; consider defining a scoped proxy for this bean if you intend to refer to it from a singleton; nested exception is java.lang.IllegalStateException: No thread-bound request found: Are you referring to request attributes outside of an actual web request, or processing a request outside of the originally receiving thread? If you are actually operating within a web request and still receive this message, your code is probably running outside of DispatcherServlet/DispatcherPortlet: In this case, use RequestContextListener or RequestContextFilter to expose the current request. 
    at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:342) 
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:193) 
    at org.springframework.aop.target.SimpleBeanTargetSource.getTarget(SimpleBeanTargetSource.java:33) 
    at org.springframework.aop.framework.Cglib2AopProxy$DynamicAdvisedInterceptor.getTarget(Cglib2AopProxy.java:654) 
    at org.springframework.aop.framework.Cglib2AopProxy$DynamicAdvisedInterceptor.intercept(Cglib2AopProxy.java:605) 
    at model.Access$$EnhancerByCGLIB$$438f41a5.toString(<generated>) 
    at java.lang.String.valueOf(String.java:2902) 
    at java.io.PrintStream.println(PrintStream.java:821) 
    at org.apache.tomcat.util.log.SystemLogHandler.println(SystemLogHandler.java:242) 
    at service.SessionCleanup.sessionDestroyed(SessionCleanup.java:24) 
    at org.apache.catalina.session.StandardSession.expire(StandardSession.java:709) 
    at org.apache.catalina.session.StandardSession.isValid(StandardSession.java:576) 
    at org.apache.catalina.session.ManagerBase.processExpires(ManagerBase.java:712) 
    at org.apache.catalina.session.ManagerBase.backgroundProcess(ManagerBase.java:697) 
    at org.apache.catalina.core.ContainerBase.backgroundProcess(ContainerBase.java:1364) 
    at org.apache.catalina.core.ContainerBase$ContainerBackgroundProcessor.processChildren(ContainerBase.java:1649) 
    at org.apache.catalina.core.ContainerBase$ContainerBackgroundProcessor.processChildren(ContainerBase.java:1658) 
    at org.apache.catalina.core.ContainerBase$ContainerBackgroundProcessor.processChildren(ContainerBase.java:1658) 
    at org.apache.catalina.core.ContainerBase$ContainerBackgroundProcessor.run(ContainerBase.java:1638) 
    at java.lang.Thread.run(Thread.java:722) 
Caused by: java.lang.IllegalStateException: No thread-bound request found: Are you referring to request attributes outside of an actual web request, or processing a request outside of the originally receiving thread? If you are actually operating within a web request and still receive this message, your code is probably running outside of DispatcherServlet/DispatcherPortlet: In this case, use RequestContextListener or RequestContextFilter to expose the current request. 
    at org.springframework.web.context.request.RequestContextHolder.currentRequestAttributes(RequestContextHolder.java:131) 
    at org.springframework.web.context.request.SessionScope.get(SessionScope.java:90) 
    at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:328) 
    ... 19 more 

最後,這裏是我的的web.xml

<?xml version="1.0" encoding="UTF-8"?> 
<web-app 
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
    xmlns="http://java.sun.com/xml/ns/javaee" 
    xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" 
    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" 
    id="WebApp_ID" version="2.5"> 

    <display-name>session-listener-cleanup</display-name> 


    <context-param> 
     <param-name>contextConfigLocation</param-name> 
     <param-value>/WEB-INF/spring-config.xml</param-value> 
    </context-param> 


    <listener> 
     <listener-class>org.springframework.web.context.request.RequestContextListener</listener-class> 
    </listener> 

    <listener> 
     <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> 
    </listener> 


    <listener> 
     <listener-class>service.SessionCleanup</listener-class> 
    </listener> 


    <servlet> 
     <servlet-name>appServlet</servlet-name> 
     <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> 
     <init-param> 
      <param-name>contextConfigLocation</param-name> 
      <param-value>/WEB-INF/spring-config.xml</param-value> 
     </init-param> 
     <load-on-startup>1</load-on-startup> 
    </servlet> 

    <servlet-mapping> 
     <servlet-name>appServlet</servlet-name> 
     <url-pattern>/*</url-pattern> 
    </servlet-mapping> 


    <session-config> 
     <session-timeout>1</session-timeout> 
    </session-config> 


</web-app> 

正如我已經說過了,一切都很好,當我在失效控制器的方法destroy會話。

更新1:可能的解決方案中

的問題發生,因爲需要以Spring能夠訪問會話bean的請求。事件雖然我們有一個關聯到線程的上下文,但沒有請求。

這裏有一些可能的選擇:

  1. 實現接口DisposableBean的,由alexwen建議。這意味着將業務邏輯移至模型對象 [here];
  2. 執行DestructionAwareBeanPostProcessor也建議alexwen。這意味着在做任何處理之前,你需要檢查被丟棄的豆是否是Access; [here];
  3. 直接從會話中檢索bean。這種方式並不是很好,因爲您使用無證行爲來實現結果,但確實有效 [here];
  4. 模擬一個servlet請求並通過RequestContextHolder將其屬性綁定到Thread。這也導致無證行爲,在將來的版本中可以改變 [here];

我沒有選擇最後兩個,因爲他們沒有記錄。另外,我不喜歡在特定的bean之後清理每個bean的想法。因爲我也不想將業務邏輯混合到我的模型bean中,所以我最終創建了一個創建bean的@Service,並且還有一個destroy方法。

該方法由處理訪問bean負責。我在Access上實現DisposableBean接口,並將AccessManager服務注入Access bean並調用服務destroy方法。該服務是這樣的:

@Service 
public class AccessManager { 

    @Bean(name="access", destroyMethod="destroy") 
    @Scope(value="session", proxyMode=ScopedProxyMode.TARGET_CLASS) 
    @Autowired 
    public Access create(HttpServletRequest request) { 
     // creation logic 
    } 


    public void destroy(Access access) { 
     // disposal logic 
    } 

} 
+0

正如例外情況所示,在web.xml中,應該使用DispatcherServlet或RequestContextListener/RequestContextFilter公開所有http請求並將請求綁定到線程。 – yorkw 2012-08-15 04:17:05

+0

@yorkw監聽器已經註冊,如果我使會話無效,一切都會正常進行。問題是超時。我已更新該帖子以包含我的** web.xml **。 – 2012-08-15 12:24:47

回答

3

如果您實現您的訪問類春接口,DisposableBean將調用方法「消滅」的時候,會話被銷燬。

或者,您可以向Spring註冊一個DestructionAwareBeanPostProcessor,當範圍被銷燬時,它將有機會處理會話作用域中的所有bean。 Docs and instructions here

如果你對Spring如何做所有這些事情感到好奇,我會鼓勵你看看Spring在Session中註冊爲HttpSessionBindingListener的DisposableBeanAdapter。

+0

謝謝。摧毀方法確實有效,我已經試過了。但是這意味着將業務邏輯轉移到我的模型bean中。我想避免它,並提供可以清理混亂的服務。我會看看'DestructionAwareBeanPostProcessor'和'HttpSessionBidingListener'。 – 2012-08-15 12:37:08

+0

它與'DestructionAwareBeanPostProcessor'一起工作,但是我需要觀察應用程序中的每個bean並查找'Access'實例?沒有辦法監視特定實例?去看看並尋找範圍破壞意識。謝謝。 – 2012-08-15 14:51:19

+0

Spring沒有辦法過濾傳遞給PostProcessor的bean,我可以看到。一般來說,我已經看到在Spring中的* Aware接口是它們在上下文中的所有bean上運行。圍繞這一點的一種可能的方式(因爲你似乎不喜歡做instanceof檢查)將創建一個特定的上下文,你想要知道它們的銷燬。 – alexwen 2012-08-16 05:12:38

1

問題發生在沒有綁定到監聽器的請求上,儘管綁定了一個上下文。會話範圍需要一個請求來解決。

閱讀,我發現範圍豆存儲到HttpSession本身。如果我只需要獲取一個會話範圍的bean,那麼我只需要使用bean名稱(session.getAttribute('beanName'))獲取會話屬性。

這裏唯一的竅門就是代理bean(如上例)以字符串scopedTarget.爲前綴,所以要獲取Access bean,我只需要調用名爲scopedTarget.access的屬性。也不需要ContextLoaderListener

所有我需要做的,讓上面的豆,是落實HttpSessionListener,並編寫以下方法:

@Override 
public void sessionDestroyed(HttpSessionEvent ev) { 

    // Get the session 
    HttpSession session = ev.getSession(); 

    // Get the access bean 
    Access access = (Access) session.getAttribute("scopedTarget.access"); 

    // Print the serial 
    System.out.println(access.getSerial()); 

} 

重要:我不知道這是不是最好的方法,雖然。我認爲這是有風險的,因爲沒有保證bean名稱將保持與現在相同的規則。

+1

我認爲你最後的說法是最重要的。由於這是沒有記錄的行爲,只是它們在Spring中實現的副作用,所以在未來的版本中可能不支持它。 – alexwen 2012-08-16 05:15:41

+0

我會嘗試調查抓住這些屬性的最佳方式,並且如果推薦這樣做。 – 2012-08-16 13:06:03