基于Spring的SchedulingConfigurer实现动态定时任务

我们在日常工作中避免不了使用定时任务做一些事情。除了可以使用类似XXL-job之类的中间件添加。我们还可以利用Spring的SchedulingConfigurer来实现。下面来从代码的角度介绍SchedulingConfigurer的用法。
要实现Spring的定时任务,我们都知道,可以直接使用注解来实现的@Scheduled(cron = "* * * * * ?")。但我们不能因为修改个定时时间,再上线一次吧。我们也不能动态的来添加定时任务。

使用SchedulingConfigurer 方式来实现定时任务,代码如下

@Configuration
public class ScheduledConfig implements SchedulingConfigurer {
    //笔者这里配合数据库来实现动态添加。方便查询数据库这里使用JdbcTemplate 
    @Autowired
    JdbcTemplate jdbcTemplate;

    private ScheduledTaskRegistrar taskRegistrar;

    private Set> scheduledFutures = null;
    private Map> taskFutures = new ConcurrentHashMap<>();
    
    /**
     * 这个方法在Spring初始化的时候会帮我们执行,这里也会拉取数据库内需要执行的任务,进行添加到定时器里。
     * @param scheduledTaskRegistrar
     */
    @Override
    public void configureTasks(ScheduledTaskRegistrar scheduledTaskRegistrar) {
        List list= new ArrayList<>();
        //查询出来当前数据库中存储的所有有效的任务
        List> maps = jdbcTemplate.queryForList("select * from corn  where status=1");
        //循环添加任务
        maps.forEach(t->{
            TriggerTask triggerTask = new TriggerTask(()->{
                System.out.println("执行定时任务:"+ LocalDateTime.now().toLocalTime());
            },triggerContext -> {
                System.out.println("执行Cron:"+t.get("corn").toString()+",id="+t.get("id").toString());
                //如果需要动态的指定当前定时任务的执行corn。这里可以增加一步,查询数据库操作。如果任务corn不需要精确修改,corn可进行缓存。到期在去查询数据库。这里根据读者的需求自行取舍。
                return new CronTrigger(t.get("corn").toString()).nextExecutionTime(triggerContext);
            });
            list.add(triggerTask);
        });
        //将任务列表注册到定时器
        scheduledTaskRegistrar.setTriggerTasksList(list);
        this.taskRegistrar = scheduledTaskRegistrar;

    }


    /**
     * 添加任务
     * @param taskId
     * @param triggerTask
     */
    public void addTask(String taskId, TriggerTask triggerTask) {
        //如果定时任务id已存在,则取消原定时器,从新创建新定时器,这里也是个更新定时任务的过程。
        if (taskFutures.containsKey(taskId)) {
            System.out.println("the taskId[" + taskId + "]  取消,重新添加");
            cancelTriggerTask(taskId);
        }
        TaskScheduler scheduler = taskRegistrar.getScheduler();
        ScheduledFuture future = scheduler.schedule(triggerTask.getRunnable(), triggerTask.getTrigger());
        getScheduledFutures().add(future);
        taskFutures.put(taskId, future);
    }

    /**
     * 获取任务列表
     */
    private Set> getScheduledFutures() {
        if (scheduledFutures == null) {
            try {
                scheduledFutures = (Set>) Utils.getProperty(taskRegistrar, "scheduledTasks");
            } catch (NoSuchFieldException e) {
                throw new SchedulingException("not found scheduledFutures field.");
            }
        }
        return scheduledFutures;
    }
    /**
     * 取消任务
     */
    public void cancelTriggerTask(String taskId) {
        ScheduledFuture future = taskFutures.get(taskId);
        if (future != null) {
            future.cancel(true);
        }
        taskFutures.remove(taskId);
        getScheduledFutures().remove(future);
    }
}

config代码中使用到的反射工具类

public static Object getProperty(Object obj, String name) throws NoSuchFieldException {
    Object value = null;
    Field field = findField(obj.getClass(), name);
    if (field == null) {
        throw new NoSuchFieldException("no such field [" + name + "]");
    }
    boolean accessible = field.isAccessible();
    field.setAccessible(true);
    try {
        value = field.get(obj);
    } catch (Exception e) {
        throw new RuntimeException(e);
    }
    field.setAccessible(accessible);
    return value;
}

public static Field findField(Class clazz, String name) {
    try {
        return clazz.getField(name);
    } catch (NoSuchFieldException ex) {
        return findDeclaredField(clazz, name);
    }
}
public static Field findDeclaredField(Class clazz, String name) {
    try {
        return clazz.getDeclaredField(name);
    } catch (NoSuchFieldException ex) {
        if (clazz.getSuperclass() != null) {
            return findDeclaredField(clazz.getSuperclass(), name);
        }
        return null;
    }
}

添加测试类

@RestController
public class test {

    @Autowired
    ScheduledConfig scheduledConfig;

    @Autowired
    JdbcTemplate jdbcTemplate;


    @GetMapping("/test")
    public void test(String id) throws NoSuchFieldException {
        Map> taskRegistrar = (Map>) Utils.getProperty(scheduledConfig, "taskFutures");
        System.out.println(taskRegistrar.size());
        scheduledConfig.addTask(id,triggerTask(id,"* * * * * ?"));
        //这里可根据需要自行存库jdbcTemplate.insert()。
    }

    private TriggerTask triggerTask(String taskId,String corn){
        return new TriggerTask(()->{
            System.out.println("执行定时任务:"+ LocalDateTime.now().toLocalTime());
        },triggerContext -> {
            System.out.println("执行corn:"+"* * * * * ?"+",id="+taskId);
            //这里与config类的代码类似。根据需要自行处理
            return new CronTrigger(corn).nextExecutionTime(triggerContext);
        });
    }
}

测试一下我们的定时任务:

数据库中的定时任务
image.png

项目启动时,定时任务注册。就会按照corn去执行。

修改corn的值。定时任务在下次执行的时候会加载新的时间。

image.png

访问接口添加一个定时任务http://127.0.0.1:8080/test?id=10

任务生效,定时执行,如下图。

image.png

值得注意的是,我们并不能直接在数据库中修改status的值,使其生效。需要在定时器中移除这个任务,可以使任务失效,并且修改数据库status的值,使其下次加载的时候不生效即可。

这里仅供学习,真正生产环境。还是使用xxl-job之类的中间件比较好。毕竟我们这里需要考虑分布式环境下。任务多次并发执行的情况,需要加分布式锁。这样会使功能的实现复杂度更进一步。实现起来复杂,后期不易维护。

你可能感兴趣的:(基于Spring的SchedulingConfigurer实现动态定时任务)