springboot基于spring task实现动态定时任务(@Scheduled)

前言

平时我们使用@Scheduled注解,每次都需要去修改代码。

现在有需求,需要通过在数据库配置cron表达式,以及控制是否开启关闭、

gitee地址:https://gitee.com/nothix/springboot-dynamic-task.git

github地址: https://github.com/shan165310175/springboot-dynamic-task

数据库表 

create table dynamic_task_config
(
    id          int auto_increment
        primary key,
    name        varchar(255)  null comment '名称',
    remark      varchar(255)  null comment '描述',
    bean_name   varchar(255)  not null comment 'bean名称',
    cron        varchar(56)   not null,
    status      int default 0 null comment '0 ok 1 禁用',
    create_time datetime      null,
    update_time datetime      null,
    constraint dynamic_task_config_beanName_uindex
        unique (bean_name)
);

pom.xml文件

<dependencies>

	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-web</artifactId>
	</dependency>

	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-test</artifactId>
		<scope>test</scope>
	</dependency>

	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-actuator</artifactId>
	</dependency>

	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-cache</artifactId>
	</dependency>

	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-data-redis</artifactId>
	</dependency>

	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-configuration-processor</artifactId>
	</dependency>

	<dependency>
		<groupId>org.apache.commons</groupId>
		<artifactId>commons-pool2</artifactId>
	</dependency>

	<dependency>
		<groupId>commons-io</groupId>
		<artifactId>commons-io</artifactId>
		<version>2.6</version>
	</dependency>

	<dependency>
		<groupId>mysql</groupId>
		<artifactId>mysql-connector-java</artifactId>
	</dependency>

	<dependency>
		<groupId>org.apache.httpcomponents</groupId>
		<artifactId>httpclient</artifactId>
	</dependency>

	<dependency>
		<groupId>io.springfox</groupId>
		<artifactId>springfox-swagger2</artifactId>
		<version>2.9.2</version>
	</dependency>

	<dependency>
		<groupId>io.springfox</groupId>
		<artifactId>springfox-swagger-ui</artifactId>
		<version>2.9.2</version>
	</dependency>

	<dependency>
		<groupId>com.thetransactioncompany</groupId>
		<artifactId>cors-filter</artifactId>
		<version>1.3.2</version>
	</dependency>

	<dependency>
		<groupId>com.alibaba</groupId>
		<artifactId>druid-spring-boot-starter</artifactId>
		<version>1.1.18</version>
	</dependency>

	<dependency>
		<groupId>com.baomidou</groupId>
		<artifactId>mybatis-plus-boot-starter</artifactId>
		<version>3.0.7.1</version>
	</dependency>

	<dependency>
		<groupId>org.projectlombok</groupId>
		<artifactId>lombok</artifactId>
	</dependency>

	<dependency>
		<groupId>commons-lang</groupId>
		<artifactId>commons-lang</artifactId>
		<version>2.6</version>
	</dependency>


</dependencies>

<dependencyManagement>
	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-dependencies</artifactId>
			<version>2.0.4.RELEASE</version>
			<type>pom</type>
			<scope>import</scope>
		</dependency>
	</dependencies>
</dependencyManagement>

<build>
	<finalName>service-task</finalName>
	<plugins>
		<plugin>
			<groupId>org.apache.maven.plugins</groupId>
			<artifactId>maven-compiler-plugin</artifactId>
			<configuration>
				<source>1.8</source>
				<target>1.8</target>
			</configuration>
		</plugin>
		<plugin>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-maven-plugin</artifactId>
			<executions>
				<execution>
					<goals>
						<goal>repackage</goal>
					</goals>
				</execution>
			</executions>
		</plugin>
	</plugins>
</build>

动态定时任务接口

DyTask接口,所有定时任务类,实现该接口

/**
 * 动态任务
 */
public interface DyTask {
    void run();
}

测试定时任务类

@RegisterTask是自定义类,通过该注解,启动时候可以自动把配置信息存入数据库。

@Component
@Slf4j
public class TestDyTask implements DyTask {

    /**
     * 启动后自动将信息注入到数据库
     */
    @RegisterTask(enable = true, override = false, name = "动态任务1 - 测试", remark = "这是备注", cron = "0 */5 * * * ?", status = 0)
    @Override
    public void run() {
        log.info("TestDyTask 测试任务。。。" + LocalDateTime.now());
    }
}

定时任务操作接口

public interface DyTaskManager {

    /**
     * 直接执行定时任务
     */
    boolean runTask(String beanName);

    /**
     * 开始定时任务
     *
     * @param beanName
     * @param cron
     *
     * @return
     */
    boolean scheduleTask(String beanName, String cron);

    /**
     * 取消定时任务
     *
     * @param beanName
     *
     * @return
     */
    boolean cancelTask(String beanName);

}

操作类接口实现

@Service
@Slf4j
public class DefaultDyTaskManager implements DyTaskManager, CommandLineRunner, ApplicationContextAware {

    private ApplicationContext context;

    Map<String, DyTaskInfo> taskMap = new ConcurrentHashMap<>();

    @Autowired
    private ThreadPoolTaskScheduler taskScheduler;

    @Autowired
    private IDynamicTaskConfigService dynamicTaskConfigService;

    @Override
    public boolean cancelTask(String beanName) {
        if (StringUtils.isBlank(beanName)) {
            return false;
        }
        DyTaskInfo task = taskMap.remove(beanName);
        if (task != null && task.getFuture() != null) {
            task.getFuture().cancel(true);
            return true;
        }
        return false;
    }

    @Override
    public boolean runTask(String beanName) {
        DyTask target = null;
        try {
            target = (DyTask) context.getBean(beanName);
        }
        catch (Throwable e) {
            throw new ServiceException("无法获取bean信息, 无法执行任务:" + e.getMessage());
        }

        // 执行
        target.run();

        return true;
    }

    @Override
    public boolean scheduleTask(String beanName, String cron) {
        DyTask target = null;
        try {
            target = (DyTask) context.getBean(beanName);
        }
        catch (Throwable e) {
            throw new ServiceException("无法获取bean信息, 无法执行任务:" + e.getMessage());
        }

        Method method = null;
        try {
            method = target.getClass().getMethod("run");
        }
        catch (NoSuchMethodException e) {
            throw new ServiceException("无法获取run方法, 无法执行任务:" + e.getMessage());
        }

        try {
            CronTask cronTask = new CronTask(createRunnable(target, method),
                    new CronTrigger(cron, TimeZone.getTimeZone(ZoneId.systemDefault())));
            DyTaskInfo taskInfo = new DyTaskInfo(cronTask);
            ScheduledFuture<?> future = taskScheduler.schedule(cronTask.getRunnable(), cronTask.getTrigger());
            taskInfo.setFuture(future);

            cancelTask(beanName);

            taskMap.put(beanName, taskInfo);

            return true;
        }
        catch (Throwable e) {
            log.info("配置异常:{}", e.getMessage());
        }

        return true;
    }

    private Runnable createRunnable(Object target, Method method) {
        Method invocableMethod = AopUtils.selectInvocableMethod(method, target.getClass());
        return new ScheduledMethodRunnable(target, invocableMethod);
    }

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

    @Override
    public void run(String... args) throws Exception {
        List<DynamicTaskConfig> taskConfigs = dynamicTaskConfigService.findAllByStatus(0);
        taskConfigs.forEach(config -> {
            if (StringUtils.isNotBlank(config.getBeanName()) && StringUtils.isNotBlank(config.getCron())) {
                log.info(">>> 初始化定时任务: {},{},{}", config.getName(), config.getBeanName(), config.getCron());
                try {
                    scheduleTask(config.getBeanName(), config.getCron());
                }
                catch (Throwable e) {
                    log.error("初始化失败: {}", e.getMessage());
                }
            }
        });
    }
}

 

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值