说明
-
本文内容
- 使用 Servlet 技术的 监听器类(ServletContextListener)作为触发器;
- 使用 TimerTask(任务处理器)和 Timer(任务调度器)实现定时任务的周期执行;
- 使用 ServletContext 类的 log 方法打印输出日志,本文代码依旧实现的是 删除 web 项目中的临时文件 功能;
- 此外,对临时文件的删除规则做了处理,删除 超出指定过期时间 的文件;
-
Timer类
- 作为任务调度器,被监听器触发启动;
- Timer 类的内部维护了一个 final 类型 的线程,保证其是 线程安全 的(类在创建实例对象时,其 final 类型的成员变量不会被初始化);
- 创建 Timer 类的实例时,需要将其设置为守护线程(isDaemon 参数设置为 true),保证线程一直在 后台运行;
-
Timer类 的调度方法
schedule(TimerTask task, Date firstTime, long period);schedule(TimerTask task, long delay, long period);scheduleAtFixedRate(TimerTask task, long delay, long period);scheduleAtFixedRate(TimerTask task, Date firstTime, long period);- 方法说明:参数一指定时任务的类,参数二指任务初次执行的时间,参数三指任务执行的周期(值大于0,否则抛异常);
- 同方法,不同参数 的区别:第二个参数代表 任务初次执行的时间。为 Date类型 时,指的是 定时时间(在指定的时间点执行任务);为 long类型 时,指的是 延时时间(在指定的延时时间之后执行任务,值大于等于0);本代码中使用的是定时时间;
- 不同方法,同参数 的区别:schedule 方法,每一次任务的执行时间会按照上一次,任务实际执行完成的时间点进行计算;scheduleAtFixedRate 方法,每一次任务执行的时间会按照上一次,任务开始执行的时间点来计算;因此,需要注意的是,当执行任务的时间大于周期间隔时间 时,任务的执行周期中必定会产生延迟(delay),此时, 使用 schedule 方法,会使得 任务的执行时间不断延后;而使用 scheduleAtFixedRate 方法,任务执行时间不会延后,且因为是单线程的,也不会导致任务并发执行等问题,而是会等到下个预定的开始时间来执行任务;
-
TimerTask 类
TimerTask 类继承了 Runnable 接口,其继承的 run 方法就是我们需要重写的任务执行体; -
欢迎留言讨论,相互提升。
所使用的jar包
- servlet-api.jar
源代码
TaskListener.java
package web.timertask;
import java.util.Calendar;
import java.util.Date;
import java.util.Timer;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
/**
* 任务监听器
* @ClassName: TaskListener
* @Author chalice
* @DateTime 2019年12月25日
*/
public class TaskListener implements ServletContextListener {
private Timer timer = null;
@Override
public void contextDestroyed(ServletContextEvent contextEvent) {
// 使用ServletContext对象进行日志输出
contextEvent.getServletContext().log("监听器销毁中……");
timer.cancel();
}
@Override
public void contextInitialized(ServletContextEvent contextEvent) {
contextEvent.getServletContext().log("监听器启动中……");
contextEvent.getServletContext().log("定时任务调度器启动中……");
// Timer内部维护了一个final类型的线程,保证其是线程安全的
timer = new Timer("定时任务调度器", true); // 第二个参数设置该调度器为守护线程(后台线程)
CoreTask task = new CoreTask(contextEvent.getServletContext());
contextEvent.getServletContext().log("定时任务调度器启动成功,准备执行定时任务……");
Date date = getFirstTime(contextEvent);
// long period = 24 * 60 * 60 * 1000; // 间隔周期:1天
long period = 60 * 1000; // 间隔周期:1分钟
// 参数一:待执行的任务对象;参数二:首次的执行时间;参数三:任务执行的周期时间(必须大于0)
timer.schedule(task, date, period);
}
/**
* 获取初始化日期
* @Author chalice
* @DateTime 2019年12月25日
* @param contextEvent
* @return
*/
private Date getFirstTime(ServletContextEvent contextEvent) {
Date date = null;
try {
// 方法一
//SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
//date = sdf.parse("2019-12-25 10:18:00");
// 方法二(推荐)
Calendar cal = Calendar.getInstance();
int year = cal.get(Calendar.YEAR);
int month = cal.get(Calendar.MONTH);
int day = cal.get(Calendar.DAY_OF_MONTH);
// 设置每天任务执行的初始时间
int hour = 18; // 时
int minute = 33; // 分
int second = 0; // 秒
cal.set(year, month, day, hour, minute, second);
date = cal.getTime();
} catch (Exception e) {
contextEvent.getServletContext().log("日期时间转化异常,异常原因:" + e.getMessage());
}
return date;
}
}
CoreTask.java
package web.timertask;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.TimerTask;
import javax.servlet.ServletContext;
/**
* 定时任务处理器
* @ClassName: CoreTask
* @Author chalice
* @DateTime 2019年12月25日
*/
public class CoreTask extends TimerTask
{
private ServletContext context = null;
private static boolean flag = false; // 保证线程是单例执行的(再次执行的时候,该语句不会生效,保证单例执行)
//private static final long intervalTime = 7 * 24 * 60 * 60 * 1000; // 过期时间(单位:毫秒):7天
private static final long intervalTime = 2 * 60 * 1000; // 过期时间(单位:毫秒):2分钟
public CoreTask(ServletContext context) {
this.context = context;
}
@Override
public void run() {
if (!flag){
flag = true;
this.context.log("开始执行定时任务……");
doTask();
flag = false;
this.context.log("定时任务执行完毕……");
}
}
/**
* 定时任务
* @Author chalice
* @DateTime 2019年12月25日
*/
private void doTask() {
String targetDir = "D://delete"; // 指定删除目录
File file = new File(targetDir);
if (file.exists()) {
// 测试一,删除所有文件
//deleteFiles(file);
// 测试二,删除过期文件
deleteOutTimeFiles(file);
}
}
/**
* 删除过期文件
* @Author chalice
* @DateTime 2019年12月25日
* @param dir 指定的删除目录
* @return
*/
private Boolean deleteOutTimeFiles(File dir) {
long nowTime = System.currentTimeMillis(); // 获取当前时间(单位:毫秒)
List<File> fileList = new ArrayList<File>();
addOutTimeFiles(fileList, dir, nowTime); // 筛选存放过期文件路径
// 执行删除
for (File file : fileList) {
boolean success = file.delete();
if (!success) {
this.context.log("删除失败,文件路径为:" + file.getAbsolutePath());
continue;
}
this.context.log("删除成功,文件路径为:" + file.getAbsolutePath());
}
this.context.log("****成功删除文件个数:" + fileList.size() + "个****");
return true;
}
/**
* 添加过期文件到指定集合中
* @Author chalice
* @DateTime 2019年12月25日
* @param fileList 待删除的文件集合
* @param dir 指定的删除目录
* @param nowTime 当前时间(单位:毫秒)
*/
private void addOutTimeFiles(List<File> fileList, File dir, long nowTime) {
if (dir.isDirectory()) {
File[] files = dir.listFiles();
if (files != null) {
for (File file : files) {
addOutTimeFiles(fileList, file, nowTime);
}
}
}
isOutTimeFile(fileList, dir, nowTime);
}
/**
* 判断文件是否过期,过期则存入集合中
* @Author chalice
* @DateTime 2019年12月25日
* @param fileList 待删除的文件集合
* @param dir 指定的删除目录
* @param nowTime 当前时间(单位:毫秒)
*/
private void isOutTimeFile(List<File> fileList, File dir, long nowTime) {
long alterTime = dir.lastModified(); // 文件的修改时间(单位:毫秒)
if ((nowTime - alterTime) > intervalTime) {
fileList.add(dir);
}
}
/**
* 遍历删除x文件夹下所有文件
* @Author chalice
* @DateTime 2019年12月25日
* @param dir
* @return
* 备注:功能单一,只能删除所有
*/
@SuppressWarnings("unused")
private Boolean deleteFiles(File dir) {
if (!dir.isDirectory()) {
dir.delete();
return true;
}
File[] files = dir.listFiles();
if (files != null) {
for (File file : files) {
boolean success = deleteFiles(file);
if (!success) {
this.context.log("删除失败,失败文件路径为:" + file.getAbsolutePath());
continue;
}
}
}
return deleteFiles(dir); // 删除相对父目录
}
}
配置文件
web.xml
<listener>
<display-name>监听器实现定时器任务2</display-name>
<listener-class>web.timertask.TaskListener</listener-class>
</listener>
优缺
- Timer对调度的支持是基于绝对时间,而非相对时间,所以它对系统时间的改变非常敏感,相对的,cron表达式就可以基于相对时间
- Timer线程不会捕获异常,如果TimerTask抛出的了未检查异常则会导致Timer线程终止,同时Timer也不会重新恢复线程的执行,此时,安排执行但未执行的任务、新的任务都将不能执行