如何每隔一小时以固定的时间间隔删除会话数据?

Tin*_*iny 7 java session spring jsp quartz-scheduler

我正在生成随机令牌,原因在于这个问题中提到的放在a中的内容java.util.ListList保存在会话范围内.

经过一些谷歌搜索后,我决定List在会话中每隔一小时删除所有元素(令牌).

我可以想到使用Quartz API,但这样做,似乎不可能操纵用户的会话.我在Spring中尝试使用Quartz API(1.8.6,2.x与我正在使用的Spring 3.2不兼容)可以在下面看到.

package quartz;

import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.springframework.scheduling.quartz.QuartzJobBean;

public final class RemoveTokens extends QuartzJobBean
{    
    @Override
    protected void executeInternal(JobExecutionContext context) throws JobExecutionException
    {
        System.out.println("QuartzJobBean executed.");
    }
}
Run Code Online (Sandbox Code Playgroud)

它在application-context.xml文件中配置如下.

<bean name="removeTokens" class="org.springframework.scheduling.quartz.JobDetailBean">
    <property name="jobClass" value="quartz.RemoveTokens" />
    <property name="jobDataAsMap">
        <map>
            <entry key="timeout" value="5" />
        </map>
    </property>
</bean>

<bean id="simpleTrigger" class="org.springframework.scheduling.quartz.SimpleTriggerFactoryBean">
      <property name="jobDetail" ref="removeTokens"/>
      <property name="startDelay" value="10000"/>
      <property name="repeatInterval" value="3600000"/>
</bean>

<bean class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
  <property name="triggers">
      <list>
          <ref bean="simpleTrigger" />
      </list>
  </property>
</bean>
Run Code Online (Sandbox Code Playgroud)

RemoveTokens类中重写的方法每小时执行一次,初始间隔为10秒,如XML中所配置,但是不可能执行某些类的某些方法来删除List存储在用户会话中的可用标记.可能吗?

List以定义的时间间隔(每小时)删除存储在会话范围中的公平方法是什么?如果通过使用这个Quartz API来实现它会更好.


编辑:

根据下面的答案,我尝试了以下但不幸的是,它没有任何区别.

application-context.xml文件中,

<task:annotation-driven executor="taskExecutor" scheduler="taskScheduler"/>
<task:executor id="taskExecutor" pool-size="5"/>
<task:scheduler id="taskScheduler" pool-size="10"/>
Run Code Online (Sandbox Code Playgroud)

这需要以下额外的命名空间,

xmlns:task="http://www.springframework.org/schema/task"
xsi:schemaLocation="http://www.springframework.org/schema/task
                    http://www.springframework.org/schema/task/spring-task-3.2.xsd"
Run Code Online (Sandbox Code Playgroud)

以下bean被注册为会话范围的bean.

package token;

import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import org.apache.commons.lang.StringUtils;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Service;

@Service
//@Scope("session")
public final class SessionToken implements SessionTokenService
{
    private List<String> tokens;

    private static String nextToken()
    {
        long seed = System.currentTimeMillis(); 
        Random r = new Random();
        r.setSeed(seed);
        return Long.toString(seed) + Long.toString(Math.abs(r.nextLong()));
    }

    @Override
    public boolean isTokenValid(String token)
    {        
        return tokens==null||tokens.isEmpty()?false:tokens.contains(token);
    }

    @Override
    public String getLatestToken()
    {
        if(tokens==null)
        {
            tokens=new ArrayList<String>(0);
            tokens.add(nextToken());            
        }
        else
        {
            tokens.add(nextToken());
        }

        return tokens==null||tokens.isEmpty()?"":tokens.get(tokens.size()-1);
    }

    @Override
    public boolean unsetToken(String token)
    {                
        return !StringUtils.isNotBlank(token)||tokens==null||tokens.isEmpty()?false:tokens.remove(token);
    }

    @Override
    public void unsetAllTokens()
    {
        if(tokens!=null&&!tokens.isEmpty())
        {
            tokens.clear();
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

和它实现的接口,

package token;

import java.io.Serializable;

public interface SessionTokenService extends Serializable
{
    public boolean isTokenValid(String token);
    public String getLatestToken();
    public boolean unsetToken(String token);
    public void unsetAllTokens();
}
Run Code Online (Sandbox Code Playgroud)

并且该bean在application-context.xml文件中配置如下.

<bean id="sessionTokenCleanerService" class="token.SessionToken" scope="session">
    <aop:scoped-proxy proxy-target-class="false"/>
</bean>
Run Code Online (Sandbox Code Playgroud)

现在,我将在下面的类中注入此服务.

package token;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;

@Service
public final class PreventDuplicateSubmission
{    
    @Autowired
    private final SessionTokenService sessionTokenService=null;

    @Scheduled(fixedDelay=3600000)
    public void clearTokens()
    {
        System.out.println("Scheduled method called.");
        sessionTokenService.unsetAllTokens();            
    }
}
Run Code Online (Sandbox Code Playgroud)

application-context.xml文件中,

<bean id="preventDuplicateSubmissionService" class="token.PreventDuplicateSubmission"/>
Run Code Online (Sandbox Code Playgroud)

上述两个豆被注释与@Service他们应该是部分context:component-scandispatacher-servelt.xml的文件(或它的名字).

使用@Scheduled前面代码片段中的注释注释的方法以给定的速率定期调用,但它会引发以下明显的异常.

org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'scopedTarget.sessionTokenCleanerService': 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:343)
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:194)
    at org.springframework.aop.target.SimpleBeanTargetSource.getTarget(SimpleBeanTargetSource.java:33)
    at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:184)
    at $Proxy79.unsetAllTokens(Unknown Source)
    at token.PreventDuplicateSubmission.clearTokens(PreventDuplicateSubmission.java:102)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:601)
    at org.springframework.scheduling.support.ScheduledMethodRunnable.run(ScheduledMethodRunnable.java:64)
    at org.springframework.scheduling.support.DelegatingErrorHandlingRunnable.run(DelegatingErrorHandlingRunnable.java:53)
    at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:471)
    at java.util.concurrent.FutureTask$Sync.innerRunAndReset(FutureTask.java:351)
    at java.util.concurrent.FutureTask.runAndReset(FutureTask.java:178)
    at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access$301(ScheduledThreadPoolExecutor.java:178)
    at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:293)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1110)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:603)
    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:329)
    ... 19 more
Run Code Online (Sandbox Code Playgroud)

要清除存储在用户的会话数据,执行此任务的方法,应定期在规定的时间间隔调用来自每个用户的会话这是不符合这种尝试的情况.这是什么方式?问题可能只是:如何从每个用户的会话中触发一个固定的时间间隔?Spring或Servlet API是否支持完成此任务?

Gle*_*est 9

评论

恭喜使用令牌来防止重新提交("核心J2EE模式"一书中的"引入同步令牌"重构).:^)

Quartz对复杂或精确的调度很有价值.但是你的要求不需要Quartz.学习CDI,java.util.Timer,ScheduledExecutor和/或EJB计时器可能更有用.

如果使用调度程序,正如您所提到的,最好让所有用户共享一个单例调度程序,而不是每个用户会话都有一个调度程序和线程实例.

如果您存储对HttpSession的引用或将其作为参数传递,请务必小心.存储引用可防止会话完成时的垃圾收集 - 导致(大?)内存泄漏.尝试使用HttpSessionListener和其他技巧清理引用可能不起作用并且很混乱.将HttpSession作为参数传递给方法会使它们人为地依赖于Servlet容器而难以进行单元测试,因为您必须模拟复杂的HttpSession对象.将所有会话数据包装在单个对象UserSessionState中更清晰 - 在会话和对象实例变量中存储对此的引用.这也称为上下文对象模式 - 即将所有作用域数据存储在一个/少数POJO上下文对象中,独立于HTTP协议类.

回答

我建议您解决两个问题:

  1. 使用java.util.Timer单例实例

    您可以替换java.util.Timer(在Java SE 1.3中ScheduledExecutor引入)(在Java SE 5中引入)以获得几乎相同的解决方案.

    这适用于JVM.它不需要jar设置也不需要配置.你打电话timerTask.schedule,传入一个TimerTask实例.当计划到期时,timerTask.run被呼叫.您通过timerTask.cancel以及timerTask.purge哪些版本使用内存删除计划任务.

    您编写TimerTask,包括它的构造函数,然后创建一个新实例并将其传递给schedule方法,这意味着您可以存储所需的任何数据或对象引用TimerTask; 您可以保留对它的引用并在以后随时调用setter方法.

    我建议你创建两个全局单例:一个Timer实例和一个TimerTask实例.在您的自定义TimerTask实例中,保留所有用户会话的列表(以POJO形式UserSessionState或Spring/CDI bean,而不是形式HttpSession).向此类添加两个方法:addSessionObjectremoveSessionObject,参数为UserSessionState或类似.在该TimerTask.run方法中,遍历该组UserSessionState并清除数据.

    创建一个自定义HttpSessionListener - 从sessionCreated中,将一个新的UserSessionState实例放入会话并调用TimerTask.addUserSession; 从sessionDestroyed,调用TimerTask.removeUserSession.

    调用全局范围的单例timer.schedule以调度TimerTask实例以清除会话范围的引用的内容.

  2. 使用带有上限大小的令牌列表(无需清理)

    不要根据经过的时间清理令牌.而是限制列表大小(例如25个令牌)并存储最近生成的令牌.

    这可能是最简单的解决方案.向列表中添加元素时,请检查是否已超出最大大小,如果是这样,则从列表中最早的索引插入并插入:

    if (++putIndex > maxSize) {
        putIndex = 0;
    }
    list.put(putIndex, newElement);
    
    Run Code Online (Sandbox Code Playgroud)

    这里不需要调度程序,也不需要形成和维护一组所有用户会话.