我需要在HttpSessionListener.sessionDestroyed()
上获得一个代理会话范围的bean。目标是在它被销毁时(通过invalidate()
或超时)进行会话清理。我添加了ContextLoaderListener
来公开上下文并通过WebApplicationContextUtils.getWebApplicationContext()
获得了bean。
如果我在Servlet中自己使会话无效,那么一切正常,但是当会话超时时,我得到Scope 'session' is not active for the current thread;
。我知道清理的问题是由Servlet引擎的内部线程完成的,但是我仍然需要能够从HttpSessionListener
获取这个bean。
我似乎有很多相同的问题,没有人得到解决方案,这就是为什么我要再问一次。
我的 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;
}
}
这是手动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;
}
}
这是 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。事件虽然我们有一个与Thread关联的Context,但是没有请求。
这里有一些可能的选择:
DestructionAwareBeanPostProcessor
。这意味着在进行任何处置之前,您需要检查所处理的bean是否为Access
{/ 3}}; RequestContextHolder
将其属性绑定到线程。这也会导致无证件行为,能够在将来的版本中进行更改 [here]; 我没有选择最后两个,因为他们没有记录。另外,我不喜欢在特定的bean之后清除每个bean的想法。由于我也不想将业务逻辑混合到我的模型bean中,因此我最终创建了一个@Service
来创建bean并且还有一个destroy
方法。
此方法由访问bean的处理负责。我在DisposableBean
上实现了Access
接口,并将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 :(得分:3)
如果你实现了接口,你的Access类上的DisposableBean Spring会在Session被销毁时调用方法“destroy”。
或者,您可以向Spring注册一个DestructionAwareBeanPostProcessor,当范围被销毁时,它将有机会处理Session范围内的所有bean。 Docs and instructions here
如果您对Spring如何做到这一点感到好奇,我建议您查看Spring在会话中注册为HttpSessionBindingListener的DisposableBeanAdapter。
答案 1 :(得分:1)
虽然存在绑定到它的上下文,但是没有绑定到侦听器的请求会发生问题。会话范围需要一个要求解决的请求。
阅读,我发现范围bean存储在HttpSession
本身。如果我只需要获取会话范围的bean,我只需要使用bean名称(session.getAttribute('beanName')
)获取session属性。
这里唯一的技巧是代理bean,如上所述,以字符串scopedTarget.
为前缀,因此要抓取Access
bean,我只需要调用名为{{的属性1}}。也不需要 ContextLoaderListener 。
为了获得上面的bean,我需要做的就是实现scopedTarget.access
并编写以下方法:
HttpSessionListener
重要提示:我不知道这是否是最佳方法。我相信这是有风险的,因为无法保证bean名称将保持与现在相同的规则。