IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> Java知识库 -> Springboot定时任务热部署设计及实现 -> 正文阅读

[Java知识库]Springboot定时任务热部署设计及实现

1. 需求来源

业务上需要将定时器的执行周期进行配置(新增、编辑、删除),并支持热部署(直接生效,无需重启)

2. 设计

2.1 表结构(postgresql)

drop table if exists t_scheduler;
create table t_scheduler (
    id int4 not null,
    task_name varchar(32) not null,
    cron varchar(32) not null,
    delete int2 not null default 0,
    constraint t_scheduler_pkey primary key ("id")
);
comment on column t_scheduler.id is '任务Id';
comment on column t_scheduler.task_name is '任务名称';
comment on column t_scheduler.cron is '任务执行周期';
comment on column t_scheduler.delete is '任务是否删除0未删除1已删除';

-- 任务数据
insert into t_scheduler values(1, 'SyncUser', '0/10 * * * * ?', 0);
insert into t_scheduler values(2, 'WeatherTask', '0/10 * * * * ?', 0);

2.2 程序设计

主配置类

里面包含一个定时任务,扫描上述任务配置表,检查有没有更新

任务接口(该接口继承Runnable)

任务定义接口,自定义的任务,均实现该接口

任务对象工厂

用来通过配置的任务名,反射获取任务对象

任务Dao

包含任务查询等

3. 代码

3.1 主配置类

package com.hz.basepro.schedule.start;

import com.hz.basepro.bean.ScheduleTask;
import com.hz.basepro.schedule.dao.businessmapper.ScheduleMapper;
import com.hz.basepro.schedule.task.TaskHelper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import org.springframework.scheduling.support.CronTrigger;
import org.springframework.stereotype.Component;

import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

@Component
@EnableScheduling
public class ScheduleTaskConfig {

    private static final Logger LOGGER = LoggerFactory.getLogger(ScheduleTaskConfig.class);

    @Autowired
    private ScheduleMapper scheduleMapper;

    /**
     * 任务Map
     *
     */
    private Map<Integer, ScheduleTask> taskMap = new HashMap<>();

    /**
     * 任务对应的定时器Map
     */
    private Map<Integer, ThreadPoolTaskScheduler> schedulerMap = new HashMap<>();

    @Scheduled(initialDelay = 0, fixedRate = 1 * 1000)
    public void checkTask() {

        List<ScheduleTask> scheduleTaskList = scheduleMapper.getAllScheduleTask();
        for (ScheduleTask scheduleTask : scheduleTaskList) {
            if (taskMap.containsKey(scheduleTask.getId())) {
                ScheduleTask oldTask = taskMap.get(scheduleTask.getId());

                // cron发生改变 1. 移除现有的任务 2. 重新生成新的任务
                if (!scheduleTask.getCron().equals(oldTask.getCron())) {

                    removeTask(oldTask.getId());
                    addTask(scheduleTask);

                }
                taskMap.remove(scheduleTask.getId());
            } else {
                // cron未改变,添加新任务
                addTask(scheduleTask);
            }

            taskMap.put(scheduleTask.getId(), scheduleTask);
        }

        // 移除已经删除的任务
        List<Integer> taskIds = scheduleTaskList.stream().map(task -> task.getId()).collect(Collectors.toList());
        for(Iterator<Map.Entry<Integer, ScheduleTask>> it = taskMap.entrySet().iterator();it.hasNext();) {
            Map.Entry<Integer, ScheduleTask> entry = it.next();
            Integer oldTaskId = entry.getKey();
            if (!taskIds.contains(oldTaskId)) {
                // 1. 移除任务
                removeTask(oldTaskId);
                // 2. map移除任务
                it.remove();
            }
        }
    }


    /**
     * 添加定时任务
     *
     * @param task
     */
    public void addTask(ScheduleTask task) {
        ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();

        scheduler.setThreadNamePrefix(task.getTaskName());
        scheduler.setPoolSize(1);
        scheduler.initialize();
        scheduler.schedule(TaskHelper.getTask(task.getTaskName()), new CronTrigger(task.getCron()));

        schedulerMap.put(task.getId(), scheduler);

        LOGGER.info("定时器创建:{}.", task.getId());
    }

    /**
     * 移除定时任务
     *
     * @param taskId
     */
    public void removeTask(Integer taskId) {
        try {
            ThreadPoolTaskScheduler scheduler = schedulerMap.get(taskId);
            if (scheduler == null) {
                LOGGER.info("定时器不存在:{}.", taskId);
            } else {
                try {
                    scheduler.shutdown();
                } catch (Exception e) {
                    LOGGER.error("线程池关闭失败, taskId: {}.", taskId, e);

                    try {
                        scheduler.shutdown();
                    } catch (Exception ex) {
                        LOGGER.error("线程池再次关闭失败, taskId: {}.", taskId, e);
                    }
                }
            }
        } finally {
            schedulerMap.remove(taskId);
        }

        LOGGER.info("定时器销毁:{}.", taskId);
    }
}

3.2 任务对象工厂TaskHelper

package com.hz.basepro.schedule.task;

import com.hz.basepro.bean.CommonException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class TaskHelper {

    private static final Logger LOGGER = LoggerFactory.getLogger(TaskHelper.class);
    private static final String basePackage = "com.hz.basepro.schedule.task.impl.";

    public static Task getTask(String taskName) {
        try {
            Class<?> clazz = Class.forName(basePackage + taskName);
            Task task = (Task)clazz.newInstance();
            return task;
        } catch (Exception e) {
            throw new CommonException("找不到对应的任务:" + taskName,  e);
        }
    }

}

3.3 任务对象

package com.hz.basepro.schedule.task;

public interface Task extends Runnable {
}
package com.hz.basepro.schedule.task.impl;

import com.hz.basepro.schedule.task.Task;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.text.SimpleDateFormat;
import java.util.Date;

public class SyncUser implements Task {

    private static final Logger LOGGER = LoggerFactory.getLogger(SyncUser.class);

    private static final ThreadLocal<SimpleDateFormat> FORMAT_LOCAL = ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));


    @Override
    public void run() {
        SimpleDateFormat sdf = FORMAT_LOCAL.get();
        LOGGER.info("同步用户任务进行中:{}", sdf.format(new Date()));
    }
}
package com.hz.basepro.schedule.task.impl;

import com.hz.basepro.schedule.task.Task;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.text.SimpleDateFormat;
import java.util.Date;

public class WeatherTask implements Task {

    private static final Logger LOGGER = LoggerFactory.getLogger(WeatherTask.class);

    private static final ThreadLocal<SimpleDateFormat> FORMAT_LOCAL = ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));

    @Override
    public void run() {
        SimpleDateFormat sdf = FORMAT_LOCAL.get();
        LOGGER.info("天气任务进行中:{}", sdf.format(new Date()));
    }
}

3.4 ScheduleMapper

package com.hz.basepro.schedule.dao.businessmapper;

import com.hz.basepro.bean.ScheduleTask;

import java.util.List;

public interface ScheduleMapper {

    /**
     * 获取所有任务
     *
     * @return
     */
    List<ScheduleTask> getAllScheduleTask();

}

3.5 查询sql

<select id="getAllScheduleTask" resultType="com.hz.basepro.bean.ScheduleTask">
     select id, task_name, cron from t_scheduler where delete = 0
</select>

3.6涉及的Bean

package com.hz.basepro.bean;

public class ScheduleTask {

    private int id;

    private String taskName;

    private String cron;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getTaskName() {
        return taskName;
    }

    public void setTaskName(String taskName) {
        this.taskName = taskName;
    }

    public String getCron() {
        return cron;
    }

    public void setCron(String cron) {
        this.cron = cron;
    }

    @Override
    public String toString() {
        return "SchedulerTask{" +
                "id=" + id +
                ", taskName='" + taskName + '\'' +
                ", cron='" + cron + '\'' +
                '}';
    }
}

4. 执行结果

4.1 初始执行结果

2022-06-29 14:54:23,281 INFO [scheduling-1] (ScheduleTaskConfig.java:99)- 定时器创建:2.
2022-06-29 14:54:23,283 INFO [scheduling-1] (ScheduleTaskConfig.java:99)- 定时器创建:1.
2022-06-29 14:54:30,001 INFO [WeatherTask1] (WeatherTask.java:19)- 天气任务进行中:2022-06-29 14:54:30
2022-06-29 14:54:30,001 INFO [SyncUser1] (SyncUser.java:20)- 同步用户任务进行中:2022-06-29 14:54:30
2022-06-29 14:54:40,000 INFO [SyncUser1] (SyncUser.java:20)- 同步用户任务进行中:2022-06-29 14:54:40
2022-06-29 14:54:40,000 INFO [WeatherTask1] (WeatherTask.java:19)- 天气任务进行中:2022-06-29 14:54:40
2022-06-29 14:54:50,001 INFO [SyncUser1] (SyncUser.java:20)- 同步用户任务进行中:2022-06-29 14:54:50

4.2 修改任务执行周期

update t_scheduler set cron = '0/5 * * * * ?' where id = 1;

4.3?修改后的执行日志

2022-06-29 14:56:22,629 INFO [scheduling-1] (ScheduleTaskConfig.java:125)- 定时器销毁:1.
2022-06-29 14:56:22,631 INFO [scheduling-1] (ScheduleTaskConfig.java:99)- 定时器创建:1.
2022-06-29 14:56:25,001 INFO [SyncUser1] (SyncUser.java:20)- 同步用户任务进行中:2022-06-29 14:56:25
2022-06-29 14:56:30,000 INFO [WeatherTask1] (WeatherTask.java:19)- 天气任务进行中:2022-06-29 14:56:30
2022-06-29 14:56:30,000 INFO [SyncUser1] (SyncUser.java:20)- 同步用户任务进行中:2022-06-29 14:56:30
2022-06-29 14:56:35,001 INFO [SyncUser1] (SyncUser.java:20)- 同步用户任务进行中:2022-06-29 14:56:35
2022-06-29 14:56:40,001 INFO [SyncUser1] (SyncUser.java:20)- 同步用户任务进行中:2022-06-29 14:56:40
2022-06-29 14:56:40,001 INFO [WeatherTask1] (WeatherTask.java:19)- 天气任务进行中:2022-06-29 14:56:40

4.4 结论

? ? ? ?符合期望结果,修改的周期的任务按照期望5s执行一次。未修改的任务仍然是10s执行一次。

  Java知识库 最新文章
计算距离春节还有多长时间
系统开发系列 之WebService(spring框架+ma
springBoot+Cache(自定义有效时间配置)
SpringBoot整合mybatis实现增删改查、分页查
spring教程
SpringBoot+Vue实现美食交流网站的设计与实
虚拟机内存结构以及虚拟机中销毁和新建对象
SpringMVC---原理
小李同学: Java如何按多个字段分组
打印票据--java
上一篇文章      下一篇文章      查看所有文章
加:2022-07-03 10:36:15  更:2022-07-03 10:38:58 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/23 16:27:50-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码