2

我需要在HttpSessionListener.sessionDestroyed(). 目标是在会话被销毁时进行清理(通过invalidate()或超时)。我添加了ContextLoaderListener以公开上下文并让 bean 通过WebApplicationContextUtils.getWebApplicationContext().

如果我自己在 Servlet 中使会话无效,一切都会正常工作,但是当会话超时时,我会得到一个Scope 'session' is not active for the current thread;. 我知道清理问题是由 Servlet 引擎的内部线程完成的,但我仍然需要能够从HttpSessionListener.

我似乎有很多相同的问题,没有人有解决方案,这就是我再次问的原因。

我的applicationContext.xml没有 bean 声明,因为我正在使用注释。

这是会话超时时我需要访问的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;
    }
}

这是手动或会话的控制器。createdestroy

@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;
    }

}

这是监听会话销毁的HttpSessionListener ,我需要访问Access会话范围 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. 按照alexwen的建议实现接口 DisposableBean 。这意味着将业务逻辑移动到模型对象[here]
  2. 实施alexwenDestructionAwareBeanPostProcessor也建议的。这意味着您需要在进行任何处置[here]之前检查要处置的 bean 是否为 a ;Access
  3. 直接从会话中检索 bean。这种方式不是一个很好的方式,因为您使用未记录的行为来实现结果,但确实有效[here]
  4. 模拟一个 servlet 请求并将其属性绑定到 Thread 通过RequestContextHolder. 这也会导致未记录的行为,可以在未来的版本中进行更改[这里]

我没有选择最后两个,因为它们没有记录。另外,我不喜欢在一个特定的豆子之后清除每个豆子的想法。由于我也不想将业务逻辑混合到我的模型 bean 中,所以我最终创建了一个@Service创建 bean 并且还有一个方法的destroy方法。

此方法由访问 bean 的处置负责。我在 beanDisposableBean上实现了接口并将服务Access注入AccessManagerAccessbean 并调用服务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
    }

}
4

2 回答 2

3

如果您实现该接口,则您的 Access 类 Spring 上的DisposableBean将在 Session 被销毁时调用方法“destroy”。

或者,您可以向 Spring 注册一个DestructionAwareBeanPostProcessor,当范围被销毁时,它将有机会处理 Session 范围内的所有 bean。文档和说明在这里

如果您对 Spring 是如何完成所有这些工作感到好奇,我鼓励您查看 Spring在 Session 中注册为HttpSessionBindingListener的 DisposableBeanAdapter。

于 2012-08-15T06:33:41.183 回答
2

问题发生在没有请求绑定到侦听器的情况下,尽管有一个上下文绑定到它。会话范围需要一个请求来解决。

仔细阅读,我发现范围 bean 存储在HttpSession自身中。如果我只需要获取一个会话范围的 bean,我只需要获取带有 bean 名称 ( session.getAttribute('beanName')) 的会话属性。

这里唯一的技巧是代理 bean,就像上面的例子一样,以 string 为前缀scopedTarget.,所以要抓取Accessbean,我只需要调用属性 named scopedTarget.access。也不需要ContextLoaderListener

为了得到上面的bean,我需要做的就是实现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 名称将保持与现在相同的规则。

于 2012-08-16T00:36:30.597 回答