- <listener>
- <listener-class>contextListener.ContextListener</listener-class>
- </listener>
- import javax.servlet.ServletContextEvent;
- import javax.servlet.ServletContextListener;
- public class ContextListener implements ServletContextListener {
- java.util.Timer timer = Time.getSingle();
- public void contextInitialized(ServletContextEvent event) {
- timer = new java.util.Timer(true);
- event.getServletContext().log("定时器已启动");
- System.out.println("*******************定时器已启动");
- timer.schedule(new MyTask(event.getServletContext()), 0, 10*1000);
- System.out.println("********************已经添加任务调度表");
- event.getServletContext().log("已经添加任务调度表");
- }
- public void contextDestroyed(ServletContextEvent event) {
- timer.cancel();
- System.out.println("*************定时器销毁");
- event.getServletContext().log("定时器销毁");
- }
- }
- class Time{
- private Time() {}
- private static java.util.Timer timer = null;
- public static java.util.Timer getSingle() {
- if(timer == null){
- timer = new java.util.Timer();
- }
- return timer;
- }
- }
- import javax.servlet.ServletContext;
- public class MyTask extends java.util.TimerTask{
- private ServletContext context = null;
- public MyTask(ServletContext context) {
- this.context = context;
- }
- public void run() {
- System.out.println("开始执行方法");
- }
- }
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>
- import java.util.Timer;
- import javax.servlet.ServletContextEvent;
- import javax.servlet.ServletContextListener;
- public class TimerTestAction implements ServletContextListener{
- private Timer timer;
- public void contextDestroyed(ServletContextEvent servletcontextevent) {
- timer.cancel();
- servletcontextevent.getServletContext().log("定时器销毁~~~");
- System.out.println("定时任务结束~~");
- }
- public void contextInitialized(ServletContextEvent servletcontextevent) {
- System.out.println("定时任务开始~~");
- timer = new Timer(true);
- timer.schedule(new TimerRunAction(servletcontextevent.getServletContext()), 0, 5*1000);
- }
- }
定时器----
- import java.util.Calendar;
- import java.util.TimerTask;
- import javax.servlet.ServletContext;
- public class TimerRunAction extends TimerTask{
- private static final int C_SCHEDULE_HOUR = 0;
- private static boolean isRunning = false;
- private ServletContext context = null;
- public TimerRunAction(ServletContext context){
- this.context = context;
- }
- @Override
- public void run() {
- Calendar c = Calendar.getInstance();
- if(!isRunning){
- if(C_SCHEDULE_HOUR == c.get(Calendar.HOUR_OF_DAY)){
- context.log("kaishi zhixing zhiding renwu~~");
- }
- else{
- context.log("shangyici renwu zhixing haiwei jieshu~~~");
- }
- }
- }
- }
我在做一个门户系统的时候遇到webService的性能问题,当时由于设计中webService传递的数据是非结构化的,因此需要建立大量的链接获取数据。后期测试时webService访问很慢,大概要7秒钟才能完成一个页面的数据。当时不想再更改webService服务器以及客户端代码了,就想着实现一个缓存,用户访问门户页面的时候,不是直接访问webService来获取数据,而是直接从缓存中查找,然后每5分钟调用webService更新一下门户系统的缓存,这样来优化页面的响应时间。
首先要注册一个ServletContextListener,这个监听器有两个方法(contextInitialized,contextDestroyed)分别是web应用启动和销毁的时候调用的。
在web应用启动的时候,调用webService,获取初始数据,放在ServletConetxt中
- package com.leec.yetsoon.listener;
- import java.util.Date;
- import java.util.Map;
- import javax.servlet.ServletContext;
- import javax.servlet.ServletContextEvent;
- import javax.servlet.ServletContextListener;
- public class CacheListenter implements ServletContextListener{
- public void contextDestroyed(ServletContextEvent event) {
- System.out.println("contextDestroyed");
- }
- public void contextInitialized(ServletContextEvent event) {
- //查询数据库获得所要共享的信息,获取需要缓存的信息,以map形式保存
- Map<String, Object> cacheMap=CacheMapFactory.getCacheMap();
- //获得ServletContext实例
- ServletContext context = event.getServletContext();
- //将查询到的共享信息保存到ServletContext中 context.setAttribute();
- context.setAttribute("cacheMap", cacheMap);
- //将更新时间加入,以便实现定时刷新
- context.setAttribute("preDate", new Date());
- context.setAttribute("isRefreshing", false);
- }
- }
- package com.leec.yetsoon.listener;
- import java.util.Date;
- import java.util.Map;
- import javax.servlet.ServletContext;
- import javax.servlet.ServletRequestEvent;
- import javax.servlet.ServletRequestListener;
- public class TimeCountListener implements ServletRequestListener {
- private final static float CACHE_MAX_AGE = 5 * 60 * 1000;//定时5分钟
- public void requestDestroyed(ServletRequestEvent arg0) {
- }
- public void requestInitialized(ServletRequestEvent event) {
- ServletContext context = event.getServletContext();
- if(!(Boolean)context.getAttribute("isRefreshing")
- && ((new Date()).getTime() - ((Date)context.getAttribute("preDate")).getTime()) > CACHE_MAX_AGE){
- context.setAttribute("isRefreshing", true);
- //在这里再次查询数据库,并将ServletContext中的信息更新
- Map<String, Object> cacheMap=CacheMapFactory.getCacheMap();
- <span style="white-space:pre"> </span>context.setAttribute("cacheMap", cacheMap);
- context.setAttribute("preDate", new Date());//每次更新缓存的同时也更新时间
- context.setAttribute("isRefreshing", false);
- }
- }
- }
这样就不用每次都消耗大量资源访问webService了~
这个缓存还存在一些问题,就是某个用户请求页面的时候,监听器接收到请求,并且满足
- !(Boolean)context.getAttribute("isRefreshing")
- && ((new Date()).getTime() - ((Date)context.getAttribute("preDate")).getTime()) > CACHE_MAX_AGE
条件时,进行缓存更新,这个过程是同步的,只有等待更新完毕,页面才能显示出来,这样对某些运气不好的个别客户端来讲,这个页面响应的时间是不可忍受的。
因此可以把更新缓存的动作改成异步的。以下代码没有进行过测试:
- package com.leec.yetsoon.listener;
- import java.util.Date;
- import java.util.Map;
- import javax.servlet.ServletContext;
- import javax.servlet.ServletRequestEvent;
- import javax.servlet.ServletRequestListener;
- public class TimeCountListener implements ServletRequestListener {
- private final static float CACHE_MAX_AGE = 5 * 60 * 1000;//定时5分钟
- public void requestDestroyed(ServletRequestEvent arg0) {
- }
- public void requestInitialized(ServletRequestEvent event) {
- final ServletContext context = event.getServletContext();
- if(!(Boolean)context.getAttribute("isRefreshing")
- && ((new Date()).getTime() - ((Date)context.getAttribute("preDate")).getTime()) > CACHE_MAX_AGE){
- context.setAttribute("isRefreshing", true);
- //在这里再次查询数据库,并将ServletContext中的信息更新
- Thread t = new Thread(new Runnable(){
- public void run(){
- Map<String, Object> cacheMap=CacheMapFactory.getCacheMap();
- context.setAttribute("cacheMap", cacheMap);
- }
- });
- t.start();
- context.setAttribute("preDate", new Date());
- context.setAttribute("isRefreshing", false);
- }
- }
- }
另外一个问题,我在context中保存了一个状态--isRefreshing,每次在更新前
- context.setAttribute("isRefreshing", true);
把状态设为正在更新,更新完毕之后,把状态再修改回去
context.setAttribute("isRefreshing", false);
每次更新的时候是要检查这个状态的,如果是正在更新,就不会再次更新,但是setAttribute的操作不是原子的,因此也可能有多个用户进入到更新缓存的状态,这个进入的会不会经常发生也没有在生产条件下测试过,因此上面的这个缓存并发性很弱,能不能应用到生产环境很难保证~