SpringBoot定时任务动态执行

SpringBoot定时任务动态执行

1. 背景

之前遇到的项目中有个这样的需求,要求定时执行某些任务,但是执行时间是在页面上配置的,配置完之后入库。因此,我们就需要在数据库中获取到任务执行的时间,再进行动态配置执行。

2. CommandLineRunner简介

CommandLineRunner接口在Spring Boot应用程序中提供了一种在应用程序启动时运行代码的简单方法。当你实现了CommandLineRunner接口,并将其实例化为Spring容器中的Bean时,Spring Boot会在启动时自动调用run方法。

CommandLineRunner接口的实现类的启动顺序通常是通过它们在Spring容器中的定义顺序来确定的。这意味着,如果你在配置类中定义了多个实现了CommandLineRunner接口的Bean,那么这些Bean会按照它们在配置类中被声明的顺序依次执行。

下面是一个简单的例子,演示了如何定义多个CommandLineRunner Bean,并展示了它们的启动顺序:

@SpringBootApplication
public class CommandLineRunnerOrderApplication {
 
    public static void main(String[] args) {
        SpringApplication.run(CommandLineRunnerOrderApplication.class, args);
    }
 
    @Bean
    public CommandLineRunner runnerOne() {
        return args -> System.out.println("Runner One starts");
    }
 
    @Bean
    public CommandLineRunner runnerTwo() {
        return args -> System.out.println("Runner Two starts");
    }
}
在这个例子中,runnerOne 会首先被调用,然后是 runnerTwo。输出顺序会反映这一启动顺序。

3. 实现编码

  1. 首先定义任务实体类及其数据库操作mapper接口。
    实体类:
package com.cron.test.dao;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.io.Serializable;
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class MyTask implements Serializable {

    private String taskId; // 任务id
    private String taskName; // 任务名称
    private String taskCron; // 任务执行时间cron表达式
    private String classPath; // 任务类路径
    private Boolean taskSwitch; // 任务是否开启
}

数据库操作接口:

package com.cron.test.mapper;

import com.cron.test.dao.MyTask;
import org.apache.ibatis.annotations.Mapper;

import java.util.List;

@Mapper
public interface MyTaskMapper {

    List<MyTask> findAllTasks();
}
  1. Spring容器工具类,主要用于根据类路径获取实例
package com.cron.test.config;

import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;

/**
 * Spring容器工具类
 */
@Component
public class SpringBeanUtils implements ApplicationContextAware {

	private static ApplicationContext applicationContext;

	@Override
	public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
		SpringBeanUtils.applicationContext = applicationContext;
	}

	/**
	 * 获取applicationContext
	 */
	public static ApplicationContext getApplicationContext() {
		return applicationContext;
	}

	/**
	 * 通过name获取 Bean.
	 */
	public  Object getBean(String name) {
		return getApplicationContext().getBean(name);
	}

	/**
	 * 通过class获取Bean.
	 */
	public  <T> T getBean(Class<T> clazz) {
		return getApplicationContext().getBean(clazz);
	}

	/**
	 * 通过name,以及Clazz返回指定的Bean
	 */
	public  <T> T getBean(String name, Class<T> clazz) {
		return getApplicationContext().getBean(name, clazz);
	}
}
  1. ThreadPoolTaskScheduler配置类

通过源码得知,ThreadPoolTaskScheduler类实现了SchedulingTaskExecutor和TaskScheduler接口。

该类中schedule(Runnable task, Trigger trigger)方法,通过分别传入线程任务(业务逻辑)和Trigger触发器对象作为参数,支持动态创建指定 cron 表达式的定时任务,这就解决了每次修改完配置都需要重启的问题。

package com.cron.test.config;

import org.springframework.context.annotation.Bean;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import org.springframework.stereotype.Component;

import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ScheduledFuture;


@Component
public class ScheduingConfig {
	public static ConcurrentHashMap<String, ScheduledFuture> taskCache = new ConcurrentHashMap<>();

	public static  volatile  ThreadPoolTaskScheduler threadPoolTaskScheduler = null;
	@Bean
	public ThreadPoolTaskScheduler threadPoolTaskScheduler() {
		if (threadPoolTaskScheduler == null) {
			synchronized (ThreadPoolTaskScheduler.class) {
				if (threadPoolTaskScheduler == null) {
					threadPoolTaskScheduler = new ThreadPoolTaskScheduler();
					threadPoolTaskScheduler.setPoolSize(200);
					threadPoolTaskScheduler.setThreadNamePrefix("timingTaskExecutor-");
					threadPoolTaskScheduler.setAwaitTerminationSeconds(30);
					threadPoolTaskScheduler.setWaitForTasksToCompleteOnShutdown(true);
				}
			}
		}
		return threadPoolTaskScheduler;
	}
}
  1. 定时任务管理类(创建和删除)
package com.cron.test.config;

import com.cron.test.dao.MyTask;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.Trigger;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import org.springframework.scheduling.support.CronTrigger;
import org.springframework.scheduling.support.PeriodicTrigger;
import org.springframework.util.ObjectUtils;

import javax.annotation.Resource;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ScheduledFuture;

@Configuration
public class DynamicTask {
    private static final Logger LOGGER = LoggerFactory.getLogger(DynamicTask.class);
    @Resource
    private SpringBeanUtils springBeanUtils;

    @Resource
    private ThreadPoolTaskScheduler threadPoolTaskScheduler;

    // 存放定时任务信息
    public static ConcurrentHashMap<String, MyTask> timingTaskMap = new ConcurrentHashMap<>();

    /**
     * @param myTasks
     */
    public void refreshAllTask(List<MyTask> myTasks) {
        for (MyTask myTask : myTasks) {
            if (ObjectUtils.isEmpty(myTask) && ObjectUtils.isEmpty(myTask.getTaskId())) {
                continue;
            }
            try {
                refreshSingleTask(myTask);
            } catch (Exception e) {
                LOGGER.error("刷新定时任务失败");
                e.printStackTrace();
            }
        }
        LOGGER.info("统计任务数:{}", ScheduingConfig.taskCache.size());
    }

    /**
     * 刷新任务,添加或删除任务
     * @param myTask
     */
    void refreshSingleTask(MyTask myTask) {
        if (ObjectUtils.isEmpty(myTask)) {
            return;
        }
        String taskId = myTask.getTaskId();
        String taskTaskExecuteCron = myTask.getTaskCron();
        String taskName = myTask.getTaskName();

        boolean flag1 = ScheduingConfig.taskCache.containsKey(taskId);
        boolean flag2 = timingTaskMap.containsKey(taskId);
        ScheduledFuture scheduledFuture = null;
        if (flag1 && flag2) { // 任务id已存在
            if (!myTask.getTaskSwitch()) { // 任务关闭
                ScheduingConfig.taskCache.get(taskId).cancel(true); // 任务停止
                // 删除任务id
                ScheduingConfig.taskCache.remove(taskId);
                timingTaskMap.remove(taskId);
                LOGGER.info("任务删除,id:{},名称:{}", taskId, taskName);
            } else { // 任务开启
                ScheduledFuture future = ScheduingConfig.taskCache.get(taskId);
                future.cancel(true);
                future = threadPoolTaskScheduler.schedule(getRunnableInstance(myTask.getClassPath()),
                        getTrigger(null, taskTaskExecuteCron));
                timingTaskMap.put(taskId, myTask);
                ScheduingConfig.taskCache.put(taskId, future);
            }
        } else if (myTask.getTaskSwitch()) { // 任务id不存在,判断任务是否开启
            scheduledFuture = threadPoolTaskScheduler.schedule(getRunnableInstance(myTask.getClassPath()),
                    getTrigger(null, taskTaskExecuteCron));
            ScheduingConfig.taskCache.put(taskId, scheduledFuture);
            timingTaskMap.put(taskId, myTask);
        }
        LOGGER.info("处理定时任务----id:{},任务名称:{},taskTaskExecuteCron:{},放到定时任务池结束>>>>>>>>>>>>>>>>>", myTask.getTaskId(), taskName, taskTaskExecuteCron);

    }

    /**
     * @param classPath
     * @return
     */
    Runnable getRunnableInstance(String classPath) {
        Runnable runnable = null;
        try {
            Class<?> newInstance = Class.forName(classPath);
            runnable = (Runnable) springBeanUtils.getBean(newInstance);
        } catch (ClassNotFoundException exception) {
            LOGGER.error("getRunnableInstance 失败");
        }
        return runnable;
    }

    /**
     * 触发器类型 : 定时触发 | 间隔时间触发
     *
     * @param type
     * @param value
     * @return
     */
    static Trigger getTrigger(Integer type, String value) {
        if (null == type) return new CronTrigger(value);
        if (type == 1) return new CronTrigger(value);
        if (type == 2) return new PeriodicTrigger(1);
        return null;
    }
}

  1. 任务类
    1)任务1
package com.cron.test.task;

public class Task1Runnable implements Runnable{
  
    @Override
    public void run() {
        // TODO 任务1需要执行的内容
    }
}

2)任务2:

package com.cron.test.task;

public class Task2Runnable implements Runnable{
  
    @Override
    public void run() {
        // TODO 任务2需要执行的内容
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值