欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

JAVA WEB服务监听与周期执行某一任务 博客分类: JAVA与JAVA WEB javatomcatServletContextListenerTimerTimerTask 

程序员文章站 2024-03-22 20:04:40
...
说明:本人为菜鸟,此文主要用于记录个人学习笔记,也希望给予同时菜鸟的朋友一点帮助,文中解决方法有些采用了他人文章,感谢原创者的付出。由于其他原因,本人只给出本人查阅采用的文章链接,对于参考文章非该作者原创的情况,希望原创者能够理解。
应用背景:在做一个java web工程服务器为tomcat,里面有个功能大致要求如下,周期性的检查计算机目录下的某个文件是否更新,若更新则对该文件进行分析。
关键词:ServletContextListener;Timer;TimerTask
解决方法:
    搜索资料后,发现采用ServletContextListener接口能够监听ServletContext对象的生命周期,实际上就是监听Web应用的生命周期。当Servlet容器启动或终止Web应用时,会触发ServletContextEvent事件,该事件由ServletContextListener 来处理。这样我们可以根据这个特性实现在web应用程序初始化时,自动运行一些初始化程序。在 ServletContextListener 接口中定义了处理ServletContextEvent事件的两个方法。该接口中有两个主要方法:contextDestroyed()与contextInitialized(),前者由Servlet容器调用在web应用的“初始化”阶段,后者的调用在web应用的“结束”阶段。在部署ServletContextListene的时候,需要在web.xml中对相关参数进行配置,相关更详细地信息,可以自行搜索。现就本文具体代码进行分析。
首先是web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5" 
	xmlns="http://java.sun.com/xml/ns/javaee" 
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
	xsi:schemaLocation="http://java.sun.com/xml/ns/javaee 
	http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
  <display-name></display-name>	
   <listener>   
    <listener-class>Analyze</listener-class>
  </listener>  
  <context-param>
    <description>Analyze的监听周期以秒为单位</description>
  	<param-name>period</param-name>
  	<param-value>5</param-value>
  </context-param>
  <welcome-file-list>
    <welcome-file>index.jsp</welcome-file>
  </welcome-file-list>
 </web-app>

<listener-class>用于设定执行监听的类,后面的<param-name>和<param-value>用来设定后续schedule()方法中所需的周期。

Analyze.java
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;

public class Analyze implements ServletContextListener {

	private static java.util.Timer timer = null;

	public void contextDestroyed(ServletContextEvent event) {
		timer.cancel();
		event.getServletContext().log("定时器已销毁,任务执行结束");
	}

	public void contextInitialized(ServletContextEvent event) {
		timer = new java.util.Timer(true);
		javax.servlet.ServletContext ctx = event.getServletContext();
		ctx.log("定时器已启动,任务开始执行");

		//读取web.xml配置文件中的参数值
		long period = Long.valueOf((String) ctx.getInitParameter("period"))
				.longValue() * 1000;
		timer.schedule(new NewTask(event.getServletContext()), 0, period);
		//schedule方法中三个参数各自意义:所需要执行的任务;延迟时间(0表示起动Web容器(或服务器)时就立即执行此任务);任务的执行间隔时间[单位:毫秒]
	}
}

Analyze类实现了ServletContextListener接口,实现了其中的两个方法,对于contextInitialized方法,主要说明的是,通过该方法,调用Timer类执行其schedule()方法,并读取web.xml中<context-param>
信息,作为参数放在Timer.schedule()方法中,并执行任务。对于Timer.schedule()方法需要说明一下:
schedule()方法根据参数的不同,带来的效果也不一样,主要说明如下:
schedule(TimerTask task, long delay)文档说明:Schedules the specified task for execution after the specified delay。大意是在延时delay毫秒后执行task,且只执行一次
schedule(TimerTask task, long delay, long period)的注释:Schedules the specified task for repeated fixed-delay execution, beginning after the specified delay。大意是在延时delay毫秒后重复的执行task,周期是period毫秒。本文采用的是后者。
监听启动后,调用Timer类,利用schedule调用NewTask类,该类继承TimerTask类。

NewTask.java
import java.util.List;
import java.util.TimerTask;
import javax.servlet.ServletContext;
import wstate.State;

public class NewTask extends TimerTask {
	public NewTask(ServletContext servletContext) {
		this.servletContext = servletContext;
	}

	private ServletContext servletContext = null;
	private static boolean isRunning = false; // 运行标志(表示是否正在运行计划的任务)

	@Override
	public void run() {
		if (!isRunning) { // 当未执行此任务时则开始执行
			List<State> l = NodesAnalysis.GetStates();
			if (l.size() != 0) {
				servletContext.setAttribute("states", l);
			}
			isRunning = false; // 将任务执行标志设置为执行完毕
		} else {
			System.out.println("任务正在运行中");
		}
	}
}

对于Timer和TimerTask的介绍,个人觉得http://hi.baidu.com/wmqxyh/item/a386395ba03db50ce6c4a5a9所写非常详细,贴出来,感谢该作者:
Timer与TimerTask都在java.util包中,它们是与任务调度相关的类。Timer是一个定时器,可以设置成在特定时间或按特定时间周期产生信号;TimerTask负责定义所要执行的任务。将Timer和TimerTask关联起来,在Timer发出信号的时候执行TimerTask定义的任务。 TimerTask是一个抽象类,只要继承TimerTask类并实现run()方法就行了。run()方法体就是定义所要执行的任务的地方,在Timer发出信号的时候,这个run()方法就会执行。要想知道本次run()方法再何时被启动,可以使用TimerTask类中的方法long scheduledExecutionTime(),使用这个方法不会造成时间精度的误差,它比System.currentTimeMillis()更节省资源。 可以通过Timer类的schedule(...)方法来设定发出信号的特定时间或特定时间周期,并与一个TimerTask类的实例相关联。使用void scheduleAtFixedRate(...)方法可以产生固定周期信号。可以使用Timer类的cancle()方法停止Timer,不再发出信号,此时,Timer与TimerTask也就脱离关系了。也可以通过TimerTask类中的cancle()方法达到同样的目的。 有一个问题,如果run()方法里面的任务在下一次信号出现之前还没有完成怎么办?有下面两种情况:(1)在用schedule(...)方法调度时:如果run()方法里面的任务在信号周期之内完成,那么下一次的信号就会在预定的时间产生;如果run()方法里面的任务在下一次信号出现之前还没有完成,那么下一次的信号就会延迟,并且上一个run()执行完成之后就立即产生下一个信号,下一个run()立即开始执行。(2)在用scheduleAtFixedRate(...)方法调度时:如果run()方法里面的任务在信号周期之内完成,那么下一次的信号就会在预定的时间产生;如果run()方法里面的任务在下一次信号出现之前还没有完成,那么等到run()执行完成之后,下一次的信号产生就尽量赶上本应该产生信号的时间,意思就是如果有累计的(从第一次信号算起)超时存在,那么下一次的信号就会在run()方法执行完之后立即产生;如果没有累计的超时存在,那么就按照周期产生信号。 另外,根据规范,如果调用cancle()时run()仍在执行,那么run()会继续执行直到完成。
感谢以下作者的辛勤劳动。
http://www.cnblogs.com/soarwell/archive/2009/03/18/1415206.html
http://www.cnblogs.com/secret1998/archive/2010/05/26/1744432.html
http://blog.csdn.net/blueling51/article/details/6931026
http://hi.baidu.com/wmqxyh/item/a386395ba03db50ce6c4a5a9