spring 定时器任务,动态配置 cron表达式并且立即生效,无需重启服务!(单机,非分布式调度,无需quartz)

最近做项目需求中,需要生成6-12数的用户邀请码,并且邀请码可根据用户灵活配置的cron(比如一小时更新一次,一天更新一次)等条件进行更新。因为我是参照这篇文章

https://blog.csdn.net/w1047667241/article/details/109527671   注:仅限于 单机下的调度,不是分布式调度的管理(这个要看清楚哦)

进行复制性开发...,而且功能也是达到了。所以记录下,以后肯定也会用到。

1.要用到定时器任务肯定要在spring-application.xml配置文件先配置好,也可以单独建 spring-task.xml配置,我就是单独建spring-task.xml方式(这个不多说了,网上一大堆)

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:task="http://www.springframework.org/schema/task"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/task
       http://www.springframework.org/schema/task/spring-task-4.1.xsd" default-lazy-init="true">

    <!--<context:component-scan base-package="cn.pw.manager.*.*.task" />-->

    <!-- 定时器开关-->
    <task:executor id="executor" pool-size="5" />
    <task:scheduler id="scheduler" pool-size="10" />
    <task:annotation-driven executor="executor" scheduler="scheduler" />

</beans>

2. 建一个 “ITriggerTask” 接口类代码如下:(这个是完全我是完全复制过来的)

package cn.pw.manager.admin.task;


import org.springframework.scheduling.support.CronTrigger;

/**
 * TriggerTask 必须实现的方法,为了支持动态配置 cron表达式
 * @author liuYY
 * @date 2021-03-11  11:21
 */
public interface ITriggerTask {
    /**
     * 获取 类别,区分 不同的Bean 对象
     * @return
     */
     String type();

    /**
     * 获取 run 方法
     * @return
     */
    Runnable getTask();

    /**
     * 获取触发器,一般是 CronTrigger
     * @return
     */
    CronTrigger getTrigger();

    /**
     * 接口 动态修改 定时任务的表达式
     */
    CronTrigger setTrigger(String cron);
}

3. TriggerTaskSupport抽象类实现 ITriggerTask 接口

package cn.pw.manager.admin.task;


import lombok.extern.slf4j.Slf4j;

/**
 * @author liuYY
 * @date 2021-3-11  10:29
 */
@Slf4j
public abstract class TriggerTaskSupport implements ITriggerTask {

    @Override
    public String type() {
        return this.getClass().getSimpleName().toLowerCase();
    }

    @Override
    public String toString() {
        return "TriggerTask{" +
                "type=" + type() +
                ", task=" + getTask() +
                "cronTrigger=" + getTrigger().getExpression() +
                '}';
    }
}

4.JobsConfigUtil 支持 停止、重启、更新定时任务,反正我只要用到更新定时任务 (如果你们想省事不用管复制过去拿来用就行了,我是直接复制原博客的..)

package cn.pw.manager.admin.task.utils;


import cn.pw.manager.admin.task.ITriggerTask;
import lombok.NonNull;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.TaskScheduler;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.SchedulingConfigurer;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import org.springframework.scheduling.config.ScheduledTaskRegistrar;
import org.springframework.scheduling.support.CronTrigger;

import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ScheduledFuture;

/**
 * <p>定时任务控制动态 cron表达式 工具类</p>
 * @author liuYY
 * @date 2021-3-11  10:29
 */
@EnableScheduling
@Configuration
@Slf4j
public class JobsConfigUtil implements SchedulingConfigurer, DisposableBean {

    // 自定义,参考 TriggerTask,为了统一在实现类中,调用 getTrigger() 和 getTask()
    public Collection<ITriggerTask> scheduledServices;
    // 句柄,方便后期获取 future
    TaskScheduler taskScheduler;

    // spring特性: 初始化该类时,自动获取和装配 项目中 所有的子类 ITriggerTask
    public JobsConfigUtil(Collection<ITriggerTask> scheduledServices) {
        this.scheduledServices = scheduledServices;
    }

    /**
     * Future handles, to cancel the running jobs
     */
    private static final Map<String, ScheduledFuture> FUTURE_MAP = new ConcurrentHashMap<>();
    /**
     * 获取 定时任务的具体的类,用于后期 重启,更新等操作
     */
    private static final Map<String, ITriggerTask> SERVICE_MAP = new ConcurrentHashMap<>();

    /**
     * 线程池任务调度器
     * <p>
     * 支持注解方式,@Scheduled(cron = "0/5 * * * * ?")
     */
    @Bean
    public TaskScheduler taskScheduler() {
        ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
        scheduler.setPoolSize(Runtime.getRuntime().availableProcessors() / 3 + 1);
        scheduler.setThreadNamePrefix("TaskScheduler-");
        scheduler.setRemoveOnCancelPolicy(true);  // 保证能立刻丢弃运行中的任务
        taskScheduler = scheduler; // 获取 句柄,方便后期获取 future
        return scheduler;
    }

    /**
     * @see <a href='https://www.codota.com/code/java/methods/org.springframework.scheduling.config.ScheduledTaskRegistrar/addTriggerTask'>codota 代码提示工具</a>
     */
    @Override
    public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
        taskRegistrar.setScheduler(taskScheduler());
        if (null != scheduledServices && scheduledServices.size() > 0) {
            for (final ITriggerTask service : scheduledServices) {
                // old 方式,不推荐,因为无法获取 调度任务的 future 对象
                // taskRegistrar.addTriggerTask(scheduledService.getTask(),scheduledService.getTrigger());
                ScheduledFuture<?> schedule = taskScheduler.schedule(service.getTask(), service.getTrigger());
                FUTURE_MAP.put(service.type(), schedule);
                SERVICE_MAP.put(service.type(), service);
            }
        }
    }

    //=============================动态配置 cron 表达式,立刻生效,支持 停止、重启、更新cron==============================================

    public Object get() {
        final Set<String> names = FUTURE_MAP.keySet();
        HashMap<String, Object> map = new HashMap<String, Object>();
        map.put("futures", names);
        map.put("services", new HashMap<Object, Object>() {{
            for (Map.Entry<String, ITriggerTask> entry : SERVICE_MAP.entrySet()) {
                put(entry.getKey(), entry.getValue().getTrigger().getExpression());
            }
        }});
        return map.toString();
    }

    /**
     * 新增
     */
    public Object add(@NonNull ITriggerTask task) {
        String type = task.type(), cron = task.getTrigger().getExpression();
        if (FUTURE_MAP.containsKey(type)) {
            return "请重新指定 任务的 type 属性";
        }
        ScheduledFuture<?> future = taskScheduler.schedule(task.getTask(), task.getTrigger());
        FUTURE_MAP.put(type, future);
        SERVICE_MAP.put(type, task);
        String format = String.format("添加新任务成功: :[%s],[%s]", type, cron);
        log.info(format);
        return format;
    }

    /**
     * 更新
     */
    public void update(@NonNull final String type, @NonNull final String cron) {

        if (!FUTURE_MAP.containsKey(type)) {
            return;
        }
        ScheduledFuture future = FUTURE_MAP.get(type);
        if (future != null) {
            future.cancel(true);
        }
        ITriggerTask service = SERVICE_MAP.get(type);
        CronTrigger old = service.getTrigger(), newTri = service.setTrigger(cron);
          future= taskScheduler.schedule(service.getTask(), newTri);
        // 必须更新一下对象,否则下次cencel 会失败
         FUTURE_MAP.put(type, future);
    }

    /**
     * 取消
     */
    public Object cancel(@NonNull String type) {
        if (!FUTURE_MAP.containsKey(type)) {
            return "取消失败,不存在该任务,请检查 type: " + type;
        }
        ScheduledFuture future = FUTURE_MAP.get(type);
        if (future != null) {
            future.cancel(true);
        }
        FUTURE_MAP.remove(type);
        return "成功取消执行中的任务 : " + type;
    }

    /**
     * 重启已经存在的任务
     */
    public Object restart(@NonNull String type) {
        ITriggerTask service = SERVICE_MAP.get(type);
        if (service == null) {
            return "无法启动任务,请检查 type: " + type;
        }
        if (FUTURE_MAP.containsKey(type)) {
            ScheduledFuture future = FUTURE_MAP.get(type);
            if (future != null) {
                future.cancel(true);
            }
        }
        ScheduledFuture<?> future = taskScheduler.schedule(service.getTask(), service.getTrigger());
        FUTURE_MAP.put(type, future); // 必须更新一下对象,否则下次cencel 会失败

        return "成功重启任务 type: " + type + ",cron: " + service.getTrigger().getExpression();
    }

    @Override
    public void destroy() throws Exception {
        for (ScheduledFuture future : FUTURE_MAP.values()) {
            if (future != null) {
                future.cancel(true);
            }
        }
        FUTURE_MAP.clear();
        SERVICE_MAP.clear();
        ((ThreadPoolTaskScheduler) taskScheduler).destroy();
    }


}

5.自己定时器的具体任务类(我的叫InvitationCodeTaskService,因为这个是我自定义的定时器任务类,你们复制过去会报错,因为很多类是我的比如什么UserService RedisDao,InvitationCodeMapper 啊什么的,你们删掉就行)
package cn.pw.manager.admin.task.customTask;

import cn.pw.common.redis.RedisDao;
import cn.pw.common.redisKey.UserConstants;
import cn.pw.manager.admin.setting.entity.InvitationCode;
import cn.pw.manager.admin.setting.mapper.InvitationCodeMapper;
import cn.pw.manager.admin.setting.service.InvitationCodeService;
import cn.pw.manager.admin.setting.service.impl.InvitationCodeServiceImpl;
import cn.pw.manager.admin.task.TriggerTaskSupport;
import cn.pw.manager.admin.task.utils.JobsConfigUtil;
import cn.pw.manager.admin.userManage.entity.User;
import cn.pw.manager.admin.userManage.service.UserService;
import cn.pw.manager.admin.userManage.utils.InviteCodeUtil;
import lombok.Builder;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.scheduling.support.CronTrigger;
import org.springframework.stereotype.Service;

import javax.annotation.PostConstruct;
import java.util.*;

@Service
@Slf4j(topic = "推销员邀请码更新定时任务")
public class InvitationCodeTaskService extends TriggerTaskSupport {

    @Autowired
    private InvitationCodeMapper invitationCodeMapper;

    @Autowired
    private RedisDao redisDao;

    @Autowired
    private UserService userService;

    @Getter
    @Builder.Default
    private CronTrigger trigger;

    // 默认为每天更新一次
     private  String  cron="0 0 0 * * ?";

     // 邀请码生成长度默认为6位
     private  Integer codeLength=6;

     @PostConstruct
     public void   init(){
         // 启用的规则
        InvitationCode invitationCode= invitationCodeMapper.findInvitationCode();
         if (Objects.nonNull(invitationCode)){
             cron=invitationCode.getCronName();
             codeLength=invitationCode.getCodeLength();
         }

         // 系统启动时应读取 数据库启用的cron规则
         trigger= new CronTrigger(cron);
     }



    @Override
    public CronTrigger setTrigger(String expression) {
        String old = trigger.getExpression();
        this.trigger = new CronTrigger(expression);
        log.info("邀请码更新cron表达式成功, 旧值: {} , 新值: {}", old, trigger.getExpression());
        return this.trigger;
    }

    @Getter
    @Builder.Default
    private Runnable task = new Runnable() {
        @Override
        public void run() {
             //自己的具体业务写在run()方法这里  updateUserInviteCode就是我的定时器任务具体要执行的代码,你们自己可以写自己的业务代码
            //updateUserInviteCode();
        }
    };


    /**
     * 更新邀请码
     */
    private  void  updateUserInviteCode(){
     try {
         // 启用的规则
         InvitationCode invitationCode=invitationCodeMapper.findInvitationCode();
         if(Objects.nonNull(invitationCode)){
             codeLength=invitationCode.getCodeLength();
             cron=invitationCode.getCronName();
         }
         log.info("推广邀请码更新cron规则:{}",cron);
         // 清除存在redis里面的邀请码
         if (redisDao.exists(UserConstants.USER_INVITATION_CODE_LIST)&&redisDao.exists(UserConstants.USER_INVITATION_CODE)){
             redisDao.del(UserConstants.USER_INVITATION_CODE_LIST);
             redisDao.del(UserConstants.USER_INVITATION_CODE);
         }
         // 查询分销员用户重新生成邀请码
         List<User> userList=userService.listByPromoterFlag();
         if (Objects.isNull(userList)) return;

         Set set = InviteCodeUtil.generateInviteCodeToSet(codeLength,userList.size());
         Iterator iterator = set.iterator();
         for (Integer i=0;i<userList.size();i++){
             String  code=iterator.next().toString();
             redisDao.hset(UserConstants.USER_INVITATION_CODE,String.valueOf(userList.get(i).getId()),code);
             redisDao.hset(UserConstants.USER_INVITATION_CODE_LIST,code,String.valueOf(userList.get(i).getId()));
         }
       }catch (Exception e){
           log.error("更新邀请码时出错了:{}",e.getMessage());
      }
   }

}

6 接下来就是写一个Controller来接收一个修改cron表达式的请求,动态接收最新的Cron表达式。我写的Controller 如下:

 @Autowired
    JobsConfigUtil jobsConfigUtil;
    /**
     * 更新更新邀请码 cron表达式
     * @param cron
     * @return
     */
    @RequestMapping("/updateCron")
    @ResponseBody
    public TableResult updateCron(String cron){
        InvitationCodeTaskService taskService=new InvitationCodeTaskService();//自己定义的定时器任务
       // taskService.getClass().getSimpleName().toLowerCase() 获取类的名称
        // 也可以直接 jobsConfigUtil.update("invitationCodeTaskService".toLowerCase(),,cron);
        jobsConfigUtil.update(taskService.getClass().getSimpleName().toLowerCase(),cron);
        return  TableResult.Result();
    }

这样就完成动态接收cron并且不用重启服务就能重新更新定时任务的cron表达式功能。注:仅限于 单机下的调度,不是分布式调度的管理(这个要看清楚哦)

最后说明下 我没有找任何的存在感啊(而且我也是复制),我只是自己记录下这个功能 说不定以后还会用到。可以看原文章

https://blog.csdn.net/w1047667241/article/details/109527671。如果你们更好的解决方案可以分享给我么?。。。我想这个肯定不是最好的!!,但是能完成自己的需求就行了。


版权声明:本文为qq_41999204原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。