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

Java 如何以固定的时间间隔(每小时)删除会话数据?,java,spring,jsp,session,quartz-scheduler,Java,Spring,Jsp,Session,Quartz Scheduler,出于本文中提到的原因,我正在生成随机令牌,这些令牌被放置在java.util.List中,而这个List保存在会话范围中 经过一些谷歌搜索后,我决定在会话中每小时删除此列表中包含的所有元素(令牌) 我可以考虑使用API,但这样做似乎不可能操纵用户的会话。下面是我在Spring中使用Quartz API(1.8.6,2.x与我正在使用的Spring 3.2不兼容)所做的尝试 package quartz; import org.quartz.JobExecutionContext; import

出于本文中提到的原因,我正在生成随机令牌,这些令牌被放置在
java.util.List
中,而这个
List
保存在会话范围中

经过一些谷歌搜索后,我决定在会话中每小时删除此
列表中包含的所有元素(令牌)

我可以考虑使用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.");
    }
}
它在
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>
<bean id="sessionTokenCleanerService" class="token.SessionToken" scope="session">
    <aop:scoped-proxy proxy-target-class="false"/>
</bean>
这需要以下附加名称空间:

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"
以下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();
        }
    }
}
这个bean在
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>
<bean id="sessionTokenCleanerService" class="token.SessionToken" scope="session">
    <aop:scoped-proxy proxy-target-class="false"/>
</bean>
application context.xml
文件中

<task:annotation-driven executor="taskExecutor" scheduler="taskScheduler"/>
<task:executor id="taskExecutor" pool-size="5"/>
<task:scheduler id="taskScheduler" pool-size="10"/>
<bean id="preventDuplicateSubmissionService" class="token.PreventDuplicateSubmission"/>

要清除存储在用户会话中的数据,执行此任务的方法应在每个用户会话的定义时间间隔定期调用,但本次尝试并非如此。怎么走?问题可能只是:如何从每个用户的会话中触发一个固定的时间间隔?Spring或Servlet API支持实现这一点吗?

我对quartz了解不多,但在您的情况下,我会这么做: 春天3.2,对吗? 在应用程序上下文中:

<task:annotation-driven executor="taskExecutor" scheduler="taskScheduler"/>
<task:scheduler id="taskScheduler" pool-size="10"/>
我要做的是将会话数据用作Spring管理的会话范围bean:

<bean id="mySessionData" class="MySessionDataBean" scope="session">
</bean>
评论 祝贺您使用令牌阻止重新提交(“引入同步令牌”从《核心J2EE模式》一书中重构)。:^

石英对于复杂或精确的调度很有价值。但您的需求不需要石英。学习CDI、java.util.Timer、ScheduledExecutor和/或EJB计时器可能更有用

如前所述,如果使用调度器,最好让所有用户共享一个单例调度器,而不是让每个用户会话都有一个调度器和线程实例

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

答复 我建议您的问题有两种解决方案:

  • 使用
    java.util.Timer
    singleton实例

    您可以将
    java.util.Timer
    (在JavaSE1.3中引入)替换为
    ScheduledExecutor
    (在JavaSE5中引入),以获得几乎相同的解决方案

    这在JVM中是可用的。它不需要jar设置和配置。 调用
    timerTask.schedule
    ,传入
    timerTask
    实例。当计划到期时,调用
    timerTask.run
    。您可以通过
    timerTask.cancel
    timerTask.purge
    删除计划任务,从而释放已用内存

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

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

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

    调用全局作用域的singleton
    timer.schedule
    来调度TimerTask实例以清除会话作用域引用的内容

  • 使用具有上限大小的令牌列表(无清理)

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

    这可能是最简单的解决方案。将元素添加到列表中时,请检查是否已超过最大大小,如果超过,请从列表中最早的索引环绕并插入:

    if (++putIndex > maxSize) {
        putIndex = 0;
    }
    list.put(putIndex, newElement);
    
    这里不需要调度器,也不需要形成和维护一组所有用户会话


  • 我的想法是:

    短版:

  • 将任务间隔改为1分钟
  • unsetAllTokensIfRequired
    方法添加到
    SessionTokenService
    中,该方法将每分钟调用一次,但如果确实需要,只会清理令牌列表(在这种情况下,决策是基于时间完成的)
  • 详细版本:

    您的计划任务将每分钟运行一次,调用由
    SessionToken
    实现的
    unsetAllTokensIfNeeded
    方法。方法实现将检查最后一次
    <bean id="mySessionData" class="MySessionDataBean" scope="session">
    </bean>
    
    class SessionCleaner{
            @Autowired
            private MySessionDataBean sessionData;
    
            @Scheduled(fixedRate=3600000)
            public void clearSession(){
                sessionData.getList().clear();//something like that    
            }
    }
    
    if (++putIndex > maxSize) {
        putIndex = 0;
    }
    list.put(putIndex, newElement);
    
    public interface SessionTokenService extends Serializable {
        public boolean isTokenValid(String token);
        public String getLatestToken();
        public boolean unsetToken(String token);
        public void unsetAllTokens();
        public boolean unsetAllTokensIfNeeded();
    }
    
    @Service
    @Scope("session")
    public final class SessionToken implements SessionTokenService, DisposableBean, InitializingBean {
    
        private List<String> tokens;
        private Date lastCleanup = new Date();
    
    // EDIT {{{
    
        @Autowired
        private SessionTokenFlusherService flusherService;
    
        public void afterPropertiesSet() {
            flusherService.register(this);
        }
    
        public void destroy() {
            flusherService.unregister(this);
        }
    
    // }}}
    
        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());            
    
            return 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();
                lastCleanup = new Date();
            }
        }
    
        @Override
        public void unsetAllTokensIfNeeded() {
            if (lastCleanup.getTime() < new Date().getTime() - 3600000) {
               unsetAllTokens();
            }
        }
    
    }
    
    public interface SessionTokenFlusherService {
        public void register(SessionToken sessionToken);
        public void unregister(SessionToken sessionToken);
    }
    
    @Service
    public class DefaultSessionTokenFlusherService implements SessionTokenFlusherService {
    
        private Map<SessionToken,Object> sessionTokens = new WeakHashMap<SessionToken,Object>();
    
        public void register(SessionToken sessionToken) {
            sessionToken.put(sessionToken, new Object());
        }
    
        public void unregister(SessionToken sessionToken) {
            sessionToken.remove(sessionToken);
        }
    
        @Scheduled(fixedDelay=60000) // each minute
        public void execute() {
            for (Entry<SessionToken, Object> e : sessionToken.entrySet()) {
                e.getKey().unsetAllTokensIfNeeded();
            }
        }
    
    }
    
    <task:annotation-driven executor="taskExecutor" scheduler="taskScheduler"/>
    <task:scheduler id="taskScheduler" pool-size="10"/>
    
    package quartz;
    
    import org.quartz.JobExecutionContext;
    import org.quartz.JobExecutionException;
    import org.springframework.scheduling.quartz.QuartzJobBean;
    
    public final class RemoveTokens extends QuartzJobBean implements javax.sevlet.http.HttpSessionListener {
    
        static java.util.Map<String, HttpSession> httpSessionMap = new HashMap<String, HttpSession>();
    
        void sessionCreated(HttpSessionEvent se) {
            HttpSession ss = se.getSession();
            httpSessionMap.put(ss.getId(), ss);
        }
    
        void sessionDestroyed(HttpSessionEvent se) {
            HttpSession ss = se.getSession();
            httpSessionMap.remove(ss.getId());
        }
    
        @Override
        protected void executeInternal(JobExecutionContext context) throws JobExecutionException {
            Set<String> keys = httpSessionMap.keySet();
    
            for (String key : keys) {
                HttpSession ss = httpSessionMap.get(key);
                Long date = (Long) ss.getAttribute("time");
    
                if (date == null) {
                    date = ss.getCreationTime();
                }
    
                long curenttime = System.currentTimeMillis();
                long difference = curenttime - date;
    
                if (difference > (60 * 60 * 1000)) {
                    /*Greater than 1 hour*/
                    List l = ss.getAttribute("YourList");
                    l.removeAll(l);
                    ss.setAttribute("time", curenttime);
                }
            }
        }
    }