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
调用全局作用域的singletontimer.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);
}
}
}
}