2

でプロキシされたセッションスコープの Bean を取得する必要がありHttpSessionListener.sessionDestroyed()ます。invalidate()目的は、(またはタイムアウトによって) 破棄されたときにセッションのクリーンアップを行うことです。ContextLoaderListenerコンテキストを公開するために を追加し、を介して Bean を取得しましたWebApplicationContextUtils.getWebApplicationContext()

サーブレットで自分でセッションを無効にするとすべて正常に動作しますが、セッションがタイムアウトするとScope 'session' is not active for the current thread;. サーブレットエンジンの内部スレッドによってクリーンアップが行われているために問題が発生することは理解していますが、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;
    }
}

これは、コントローラー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であり、セッション スコープ Beanの内容にアクセスする必要がありますAccess

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 を実装します。これは、ビジネス ロジックをモデル オブジェクト[ここ]に移動することを意味します。
  2. alexwenDestructionAwareBeanPostProcessorによっても提案されているを実装します。これは、破棄される Bean が[here]であるかどうかを確認する必要があることを意味します。Access
  3. セッションから Bean を直接取得します。文書化されていない動作を使用して結果を達成するため、この方法はあまり良い方法ではありませんが、[ここ] で機能します。
  4. サーブレット リクエストをモックし、その属性を を通じてスレッドにバインドしますRequestContextHolder。これはまた、文書化されていない動作につながり、将来のリリース[こちら]で変更される可能性があります。

最後の 2 つは文書化されていないため、選択しませんでした。また、特定の Bean の後にすべての Bean を清掃するという考えも好きではありませんでした。また、モデル Bean にビジネス ロジックを混在させたくないので@Service、Bean を作成し、メソッドも持つ を作成することになりましたdestroy

このメソッドは、アクセス Bean の破棄を担当します。DisposableBeanにインターフェースを実装し、Bean にサービスを Access注入して、サービスメソッドを呼び出しました。サービスは次のようになります。AccessManagerAccessdestroy

@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は、セッションが破棄されたときにメソッド「destroy」を呼び出します。

または、スコープが破棄されたときに Session スコープ内のすべての Bean を処理する機会を持つDestructionAwareBeanPostProcessorをSpring に登録することもできます。ドキュメントと手順はこちら

Spring がこれらすべてをどのように行うかについて興味がある場合は、Spring がセッションでHttpSessionBindingListenerとして登録する DisposableBeanAdapter を確認することをお勧めします。

于 2012-08-15T06:33:41.183 に答える
2

問題は、リスナーにバインドされたコンテキストはあるものの、リスナーにバインドされた要求がない場合に発生します。セッションスコープを解決するには、リクエストが必要です。

読んでみると、スコープBeanはHttpSessionそれ自体に格納されていることがわかりました。セッションスコープのBeanを取得するだけの場合は、Bean名()を使用してセッション属性を取得する必要がありますsession.getAttribute('beanName')

ここでの唯一のトリックは、プロキシされたBeanの前に文字列が付いていることですscopedTarget.。したがって、Beanを取得するAccessには、という名前の属性を呼び出す必要がありますscopedTarget.accessContextLoaderListenerも必要ありません。

上記の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 に答える