ServletContextListener定时任务的使用

说明:每隔10秒执行一次方法。 

Xml代码   收藏代码
  1. <listener>  
  2.      <listener-class>contextListener.ContextListener</listener-class>  
  3. </listener>  


Java代码   收藏代码
  1. import javax.servlet.ServletContextEvent;  
  2. import javax.servlet.ServletContextListener;  
  3.   
  4. public class ContextListener implements ServletContextListener {  
  5.       
  6.     java.util.Timer timer = Time.getSingle();  
  7.       
  8.     public void contextInitialized(ServletContextEvent event) {  
  9.         timer = new java.util.Timer(true);  
  10.         event.getServletContext().log("定时器已启动");  
  11.         System.out.println("*******************定时器已启动");  
  12.         timer.schedule(new MyTask(event.getServletContext()), 010*1000);  
  13.         System.out.println("********************已经添加任务调度表");  
  14.         event.getServletContext().log("已经添加任务调度表");  
  15.     }  
  16.     public void contextDestroyed(ServletContextEvent event) {  
  17.         timer.cancel();  
  18.         System.out.println("*************定时器销毁");  
  19.         event.getServletContext().log("定时器销毁");  
  20.     }  
  21. }  
  22. class Time{  
  23.     private Time() {}  
  24.     private static java.util.Timer timer = null;  
  25.     public static java.util.Timer getSingle() {  
  26.         if(timer == null){  
  27.             timer = new java.util.Timer();  
  28.         }  
  29.         return timer;  
  30.     }  
  31. }  


Java代码   收藏代码
  1. import javax.servlet.ServletContext;  
  2.   
  3. public class MyTask extends java.util.TimerTask{  
  4.   
  5.     private ServletContext context = null;  
  6.     public MyTask(ServletContext context) {  
  7.         this.context = context;  
  8.     }  
  9.   
  10.     public void run() {  
  11.         System.out.println("开始执行方法");  
  12.     }  
  13. }  


有个项目需要在WEB服务器上实现定时从不同的数据库中取出更新以后的数据,提供给客户端JS读取,一开始考虑的是用Servlet来实现,在Servlet里面来开线程处理这个问题。后来,找到了这些资料:
http://wuyue37307.blog.163.com/blog/static/27583712010074564493/

http://blog.sina.com.cn/s/blog_4679d9850100987y.html

发现,可以使用ServletContextListener来完成这个功能。因为Servlet毕竟需要一个启动或者称为叫激活的操作,但这个操作什么时候完成,由谁来完成,是个问题。而ServletContextListener则在应用程序一开始启动时就会自动调用 public void contextInitialized(ServletContextEvent event);而在应用程序停止时会自动调用 public void contextDestroyed(ServletContextEvent event)。而使用Timer和TimerTask则可以指定一个任务的开始时间和执行间隔时间。

代码基本是前面两个博客上的内容:

package mking;

import java.util.TimerTask;

import javax.servlet.ServletContext;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;

/**
 * 上下文监听器,需要在web.xml中进行配置,请参见<listener></listener>结点
 * @author Administrator
 *
 */
public class MyContextListener implements ServletContextListener {
private java.util.Timer timer = null;
private ServletContext context = null;

public void contextInitialized(ServletContextEvent event) {
this.context = event.getServletContext();
timer = new java.util.Timer(true);
event.getServletContext().log("定时器已启动");
//设定MyTask中任务每5秒执行一次,0表示马上执行,可以改为2000,则表示2秒以后开始执行
//以后都按后面指定的每5秒执行一次
timer.schedule(new MyTask(this.context), 0, 5 * 1000);
event.getServletContext().log("已经添加任务调度表");
}

public void contextDestroyed(ServletContextEvent event) {
timer.cancel();
this.context.log("定时器销毁");
this.context = null;
}

private static class MyTask extends TimerTask {
private static boolean isRunning = false;
private ServletContext context = null;

public MyTask(ServletContext context) {
this.context = context;
}

//下面的方法会按之前设定的每5秒执行一次,所以,此处不需要循环
public void run() {
if (!isRunning) {
isRunning = true;
context.log("开始执行指定任务");

// TODO 添加自定义的详细任务,以下只是示例
// 这里完成从数据库取数据,然后存放到MySQL数据库中
int i = 0;
while (i++ < 10) {
context.log("已完成任务的" + i + "/" + 10);
}

isRunning = false;
context.log("指定任务执行结束");
} else {
context.log("上一次任务执行还未结束");
}
}
}
}

需要注意的是,该监听器必须要在web.xml中配置:

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee 
http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
<!-- 下面是配置上下文的监听器,在监听器中完成任务的注册和撤销 -->
<listener>   
<listener-class>mking.MyContextListener</listener-class>
</listener>
</web-app>












Java代码   收藏代码
  1. import java.util.Timer;  
  2.   
  3. import javax.servlet.ServletContextEvent;  
  4. import javax.servlet.ServletContextListener;  
  5.   
  6. public class TimerTestAction implements ServletContextListener{  
  7.   
  8.     private Timer timer;  
  9.     public void contextDestroyed(ServletContextEvent servletcontextevent) {  
  10.         timer.cancel();  
  11.         servletcontextevent.getServletContext().log("定时器销毁~~~");  
  12.         System.out.println("定时任务结束~~");  
  13.     }  
  14.   
  15.     public void contextInitialized(ServletContextEvent servletcontextevent) {  
  16.         System.out.println("定时任务开始~~");  
  17.         timer = new Timer(true);  
  18.         timer.schedule(new TimerRunAction(servletcontextevent.getServletContext()), 05*1000);  
  19.     }  
  20.       
  21.       
  22.   
  23. }  

定时器---- 
Java代码   收藏代码
  1. import java.util.Calendar;  
  2. import java.util.TimerTask;  
  3.   
  4. import javax.servlet.ServletContext;  
  5.   
  6. public class TimerRunAction extends TimerTask{  
  7.     private static final int C_SCHEDULE_HOUR = 0;  
  8.     private static boolean isRunning = false;  
  9.     private ServletContext context = null;  
  10.   
  11.     public TimerRunAction(ServletContext context){  
  12.         this.context = context;  
  13.     }  
  14.     @Override  
  15.     public void run() {  
  16.         Calendar c = Calendar.getInstance();  
  17.         if(!isRunning){  
  18.             if(C_SCHEDULE_HOUR == c.get(Calendar.HOUR_OF_DAY)){  
  19.                                 context.log("kaishi zhixing zhiding renwu~~");  
  20.                             }  
  21.             else{  
  22.                 context.log("shangyici renwu zhixing haiwei jieshu~~~");  
  23.             }  
  24.         }  
  25.   
  26.     }  
  27.   
  28. }  









我在做一个门户系统的时候遇到webService的性能问题,当时由于设计中webService传递的数据是非结构化的,因此需要建立大量的链接获取数据。后期测试时webService访问很慢,大概要7秒钟才能完成一个页面的数据。当时不想再更改webService服务器以及客户端代码了,就想着实现一个缓存,用户访问门户页面的时候,不是直接访问webService来获取数据,而是直接从缓存中查找,然后每5分钟调用webService更新一下门户系统的缓存,这样来优化页面的响应时间。

首先要注册一个ServletContextListener,这个监听器有两个方法(contextInitialized,contextDestroyed)分别是web应用启动和销毁的时候调用的。

在web应用启动的时候,调用webService,获取初始数据,放在ServletConetxt中

[java]  view plain copy
  1. package com.leec.yetsoon.listener;  
  2.   
  3. import java.util.Date;  
  4. import java.util.Map;  
  5.   
  6. import javax.servlet.ServletContext;  
  7. import javax.servlet.ServletContextEvent;  
  8. import javax.servlet.ServletContextListener;  
  9.   
  10. public class CacheListenter implements ServletContextListener{  
  11.   
  12.     public void contextDestroyed(ServletContextEvent event) {  
  13.         System.out.println("contextDestroyed");  
  14.           
  15.     }  
  16.   
  17.     public void contextInitialized(ServletContextEvent event) {  
  18.         //查询数据库获得所要共享的信息,获取需要缓存的信息,以map形式保存  
  19.         Map<String, Object> cacheMap=CacheMapFactory.getCacheMap();  
  20.           
  21.         //获得ServletContext实例     
  22.         ServletContext context = event.getServletContext();     
  23.         //将查询到的共享信息保存到ServletContext中 context.setAttribute();    
  24.         context.setAttribute("cacheMap", cacheMap);  
  25.         //将更新时间加入,以便实现定时刷新  
  26.         context.setAttribute("preDate"new Date());     
  27.         context.setAttribute("isRefreshing"false);     
  28.           
  29.     }  
  30.   
  31. }  
其次,还要再注册一个ServletRequestListener,监听每次客户端请求,当请求到来的时间比缓存中的上次更新时间超过5分钟的时候,调用webService,更新缓存。

[java]  view plain copy
  1. package com.leec.yetsoon.listener;  
  2.   
  3. import java.util.Date;  
  4. import java.util.Map;  
  5. import javax.servlet.ServletContext;  
  6. import javax.servlet.ServletRequestEvent;  
  7. import javax.servlet.ServletRequestListener;  
  8.   
  9. public class TimeCountListener implements  ServletRequestListener {  
  10.       
  11.     
  12.     private final static float CACHE_MAX_AGE = 5 * 60 * 1000;//定时5分钟    
  13.   
  14.     public void requestDestroyed(ServletRequestEvent arg0) {  
  15.           
  16.     }  
  17.   
  18.     public void requestInitialized(ServletRequestEvent event) {  
  19.         ServletContext context = event.getServletContext();     
  20.         if(!(Boolean)context.getAttribute("isRefreshing")      
  21.                 && ((new Date()).getTime() - ((Date)context.getAttribute("preDate")).getTime()) > CACHE_MAX_AGE){     
  22.         context.setAttribute("isRefreshing"true);     
  23.         //在这里再次查询数据库,并将ServletContext中的信息更新     
  24.         Map<String, Object> cacheMap=CacheMapFactory.getCacheMap();  
  25. <span style="white-space:pre">      </span>context.setAttribute("cacheMap", cacheMap);  
  26.         context.setAttribute("preDate"new Date());//每次更新缓存的同时也更新时间  
  27.         context.setAttribute("isRefreshing"false);     
  28.         }     
  29.           
  30.     }  
  31.   
  32. }  

这样就不用每次都消耗大量资源访问webService了~

这个缓存还存在一些问题,就是某个用户请求页面的时候,监听器接收到请求,并且满足

[java]  view plain copy
  1. !(Boolean)context.getAttribute("isRefreshing")      
  2.                 && ((new Date()).getTime() - ((Date)context.getAttribute("preDate")).getTime()) > CACHE_MAX_AGE  

条件时,进行缓存更新,这个过程是同步的,只有等待更新完毕,页面才能显示出来,这样对某些运气不好的个别客户端来讲,这个页面响应的时间是不可忍受的。

因此可以把更新缓存的动作改成异步的。以下代码没有进行过测试:

[java]  view plain copy
  1. package com.leec.yetsoon.listener;  
  2.   
  3. import java.util.Date;  
  4. import java.util.Map;  
  5. import javax.servlet.ServletContext;  
  6. import javax.servlet.ServletRequestEvent;  
  7. import javax.servlet.ServletRequestListener;  
  8.   
  9. public class TimeCountListener implements  ServletRequestListener {  
  10.       
  11.     
  12.     private final static float CACHE_MAX_AGE = 5 * 60 * 1000;//定时5分钟    
  13.   
  14.     public void requestDestroyed(ServletRequestEvent arg0) {  
  15.           
  16.     }  
  17.   
  18.     public void requestInitialized(ServletRequestEvent event) {  
  19.         final ServletContext context = event.getServletContext();     
  20.         if(!(Boolean)context.getAttribute("isRefreshing")      
  21.                 && ((new Date()).getTime() - ((Date)context.getAttribute("preDate")).getTime()) > CACHE_MAX_AGE){     
  22.         context.setAttribute("isRefreshing"true);     
  23.         //在这里再次查询数据库,并将ServletContext中的信息更新  
  24.         Thread t = new Thread(new Runnable(){  
  25.             public void run(){  
  26.                 Map<String, Object> cacheMap=CacheMapFactory.getCacheMap();  
  27.                 context.setAttribute("cacheMap", cacheMap);  
  28.             }  
  29.         });     
  30.         t.start();  
  31.         context.setAttribute("preDate"new Date());  
  32.         context.setAttribute("isRefreshing"false);     
  33.         }     
  34.           
  35.     }  
  36.   
  37. }  

另外一个问题,我在context中保存了一个状态--isRefreshing,每次在更新前
[java]  view plain copy
  1. context.setAttribute("isRefreshing"true);  

把状态设为正在更新,更新完毕之后,把状态再修改回去

context.setAttribute("isRefreshing", false);

每次更新的时候是要检查这个状态的,如果是正在更新,就不会再次更新,但是setAttribute的操作不是原子的,因此也可能有多个用户进入到更新缓存的状态,这个进入的会不会经常发生也没有在生产条件下测试过,因此上面的这个缓存并发性很弱,能不能应用到生产环境很难保证~


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值