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

1,772 阅读3分钟

说明

  1. 本文内容:使用 Servlet 技术,实现其 监听器ServletContextListener)接口,在应用上下文初始化时,触发定时任务,实现 定时删除web项目中的临时文件 功能;
  2. 文中使用的 ScheduledExecutorService 类是从 JDK1.5 的java.util.concurrent里,作为并发工具类被引进的,相比于 Timer 类的单线程,它是通过线程池的方式来执行任务的;
  3. 本代码中直接对 临时文件夹其下的所有文件和文件夹 进行了全部删除操作,若需要对临时文件进行其他功能处理(如:回收站功能、删除指定过期时间的文件和文件夹等),请关注查看 监听器实现定时器任务 第二弹;
  4. 欢迎留言讨论,相互提升。

所使用的jar包

  • servlet-api.jar
  • log4j-1.2.17.jar
  • slf4j-api-1.6.6.jar
  • slf4j-log4j12-1.6.6.jar

源码

FileHandleListener.java

package web.timertask;

import java.io.File;
import java.util.Calendar;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

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

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;


/**
 * 监听器实现定时器任务--删除项目临时文件
 * @ClassName: FileHandleListener
 * @Author chalice
 * @DateTime 2019年12月23日
 * 使用ScheduledExecutorService的缺点:
 * 因为任务的执行都交给了线程池来管理,在不终止整个线程池的情况下,无法手动结束任务
 */
public class FileHandleListener implements ServletContextListener
{
    private static final Logger LOGGER = LoggerFactory.getLogger(ServletContextListener.class);
    private static ScheduledExecutorService service = null;
    private static int outTime = 5; // 线程关闭时指定的超时时间,单位随参数而定
    
    @Override
    public void contextDestroyed(ServletContextEvent contextEvent) {
        LOGGER.info("监听器销毁中……");
        // 通知线程池关闭(并不是真正的关闭了),若池中的线程未执行完,等待;若不进行该操作,会报警告:可能会内存泄漏
        service.shutdown();
        // 强行关闭线程池中的线程(使用时可针对业务场景调整调用的顺序,或不强行关闭线程)
        /*try {
            // 判断线程终止的时间是否超过预定时间,若任务都结束:true;反之:false;
            boolean b = service.awaitTermination(outTime, TimeUnit.MINUTES); // 单位:分钟
            if (!b) {
                service.shutdownNow();
            }
        } catch (InterruptedException e) {
            // 向线程池中所有执行中的线程发出interrupted以终止线程的运行
            service.shutdownNow();
        }*/
    }
    
    @Override
    public void contextInitialized(ServletContextEvent contextEvent) {
        LOGGER.info("监听器启动中……");
        // 作为接口实现类生成Runnable对象
        Runnable runnable = new Runnable() {
            public void run() {
                cleanFiles();
            }
        };
        service = Executors.newSingleThreadScheduledExecutor();
        // 获取初次启动并执行的延时时间(单位:分钟)
        int delayMins = getFirstDelayTime();
        // 间隔时间(单位:分钟)
        //int intervalMins = 60 * 24;
        int intervalMins = 1;
        // 参数二:“首次执行”的延时时间;参数三:“定时执行”的间隔时间;参数四:时间类型;
        service.scheduleAtFixedRate(runnable, delayMins, intervalMins, TimeUnit.MINUTES);
        
    }
    
    /**
     * 根据每日定时任务执行的时间点,计算出初次启动并执行的延时时间(单位:分钟)
     * @Author chalice
     * @DateTime 2019年12月23日
     * @return 初次启动并执行的延时时间(单位:分钟)
     */
    private int getFirstDelayTime() {
        int targetMins = 11 * 60 + 18; // 定时任务执行时间点(单位:分钟),样例:目标钟点数 * 60 + 目标分钟数
        int result = 0;
        // 获取当前时区、地区的日期时间的日期对象
        Calendar cal = Calendar.getInstance();
        int hour = cal.get(Calendar.HOUR_OF_DAY);
        int min = cal.get(Calendar.MINUTE);
        int nowMins = hour * 60 + min;
        if (nowMins > targetMins) {
            result = (24 * 60) - nowMins + targetMins; // 次日
        } else {
            result = targetMins - nowMins; // 今日
        }
        return result;
    }
    
    /**
     * 执行删除之前的处理
     * 如:
     * 1、处理删除x天前的文件;
     * 2、将需要删除的文件移动到另一个临时文件夹中,再删除整个临时文件夹,避免因为删除错误而引起其他问题(回收站功能);
     * @Author chalice
     * @DateTime 2019年12月23日
     * 备注:此处暂未作任何处理
     */
    private void cleanFiles() {
        String fileRootPath = "D:/delete";
        File fileDir = new File(fileRootPath);
        if (fileDir.exists())
        {
            deleteDir(fileDir);
        }
    }
    
    /**
     * 递归删除某文件夹
     * @Author chalice
     * @DateTime 2019年12月23日
     * @param dir 删除的文件目录
     * @return
     */
    public void deleteDir(File dir) {
        if (dir.isDirectory()) {
            String[] children = dir.list();
            if (children != null) {
                for (String str : children) {
                    File file = new File(dir, str);
                    if (file.exists())
                    {
                        deleteDir(file);
                    }
                }
            }
        }
        boolean success = dir.delete(); // 删除相对父目录(删除自身)
        if (success) {
            LOGGER.info("删除成功,文件路径为:{};" + dir.getAbsolutePath());
        } else {
            LOGGER.info("删除失败,文件路径为:{};" + dir.getAbsolutePath());
        }
    }
}

配置文件

web.xml

<listener>
	<display-name>监听器实现定时器任务</display-name>
	<listener-class>web.timertask.FileHandleListener</listener-class>
</listener>

log4j.properties

# Global logging configuration
log4j.rootLogger=INFO, stdout, B
# Console output...
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%5p [%t] - %m%n

log4j.appender.B=org.apache.log4j.RollingFileAppender
# 指定日志的输出目录
log4j.appender.B.File=D:\\test.log
log4j.appender.B.MaxFileSize=10MB
log4j.appender.B.MaxBackupIndex=5
log4j.appender.B.layout=org.apache.log4j.PatternLayout
log4j.appender.B.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} [%c]-[%p] %m%n