监听器实现定时器任务(二)

1,094 阅读6分钟

说明

  1. 本文内容

    • 使用 Servlet 技术的 监听器类ServletContextListener)作为触发器;
    • 使用 TimerTask(任务处理器)和 Timer(任务调度器)实现定时任务的周期执行;
    • 使用 ServletContext 类的 log 方法打印输出日志,本文代码依旧实现的是 删除 web 项目中的临时文件 功能;
    • 此外,对临时文件的删除规则做了处理,删除 超出指定过期时间 的文件;
  2. Timer类

    • 作为任务调度器,被监听器触发启动;
    • Timer 类的内部维护了一个 final 类型 的线程,保证其是 线程安全 的(类在创建实例对象时,其 final 类型的成员变量不会被初始化);
    • 创建 Timer 类的实例时,需要将其设置为守护线程(isDaemon 参数设置为 true),保证线程一直在 后台运行
  3. 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 方法,任务执行时间不会延后,且因为是单线程的,也不会导致任务并发执行等问题,而是会等到下个预定的开始时间来执行任务
  4. TimerTask
    TimerTask 类继承了 Runnable 接口,其继承的 run 方法就是我们需要重写的任务执行体;

  5. 欢迎留言讨论,相互提升。

所使用的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也不会重新恢复线程的执行,此时,安排执行但未执行的任务、新的任务都将不能执行

参考链接

www.ewdna.com/2011/12/jav…