原文路径:https://zhuanlan.zhihu.com/p/79644891

在日常的项目开发中,往往会涉及到一些需要做到定时执行的代码,例如自动将超过24小时的未付款的单改为取消状态,自动将超过14天客户未签收的订单改为已签收状态等等,那么为了在Spring Boot中实现此类需求,我们要怎么做呢?

Spring Boot早已考虑到了这类情况,先来看看要怎么做。第一种方式是比较简单的,先搭建好Spring Boot微服务,加上这个注解 @EnableScheduling :

/**
* @author yudong
* @date 2019/8/24
*/
@EnableScheduling // 开启定时任务功能
@ComponentScan(basePackages = "org.javamaster.b2c")
@EnableTransactionManagement
@SpringBootApplication
public class ScheduledApplication {
static Logger logger = LoggerFactory.getLogger(ScheduledApplication.class); public static void main(String[] args) {
SpringApplication.run(ScheduledApplication.class, args);
logger.info("定时任务页面管理地址:{}", "http://localhost:8089/scheduled/task/taskList");
} }

然后编写定时任务类:

/**
* @author yudong
* @date 2019/8/24
*/
@Component
public class FixedPrintTask {
Logger logger = LoggerFactory.getLogger(getClass());
private int i; @Scheduled(cron = "*/15 * * * * ?")
public void execute() {
logger.info("FixedPrintTask execute times:{}", ++i);
} }

@Scheduled(cron ="*/15 * * * * ?")注解表明这是一个需要定时执行的方法,里面的cron属性接收的是一个cron表达式,这里我给的是 */15 * * * * ? ,这个的意思是每隔15秒执行一次方法,对cron表达式不熟悉的同学可以百度一下用法。项目跑起来后可以看到方法被定时执行了:

这种方式有个缺点,那就是执行周期写死在代码里了,没有办法动态改变,要想改变只能修改代码在重新部署启动微服务。其实Spring也考虑到了这个,所以给出了另外的解决方案,就是我下面说的第二种方式。

第二种方式需要用到数据库,先来建立一个定时任务表并插入三条定时任务记录:

drop table if exists `spring_scheduled_cron`;
create table `spring_scheduled_cron` (
`cron_id` int primary key auto_increment
comment '主键id',
`cron_key` varchar(128) not null unique
comment '定时任务完整类名',
`cron_expression` varchar(20) not null
comment 'cron表达式',
`task_explain` varchar(50) not null default ''
comment '任务描述',
`status` tinyint not null default 1
comment '状态,1:正常;2:停用',
unique index cron_key_unique_idx(`cron_key`)
)
ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4
COMMENT = '定时任务表'; insert into `spring_scheduled_cron`
values (1, 'org.javamaster.b2c.scheduled.task.DynamicPrintTask', '*/5 * * * * ?', '定时任务描述', 1);
insert into `spring_scheduled_cron`
values (2, 'org.javamaster.b2c.scheduled.task.DynamicPrintTask1', '*/5 * * * * ?', '定时任务描述1', 1);
insert into `spring_scheduled_cron`
values (3, 'org.javamaster.b2c.scheduled.task.DynamicPrintTask2', '*/5 * * * * ?', '定时任务描述2', 1);

编写一个配置类:

@Configuration
public class ScheduledConfig implements SchedulingConfigurer {
@Autowired
private ApplicationContext context;
@Autowired
private SpringScheduledCronRepository cronRepository;
@Override
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
for (SpringScheduledCron springScheduledCron : cronRepository.findAll()) {
Class<?> clazz;
Object task;
try {
clazz = Class.forName(springScheduledCron.getCronKey());
task = context.getBean(clazz);
} catch (ClassNotFoundException e) {
throw new IllegalArgumentException("spring_scheduled_cron表数据" + springScheduledCron.getCronKey() + "有误", e);
} catch (BeansException e) {
throw new IllegalArgumentException(springScheduledCron.getCronKey() + "未纳入到spring管理", e);
}
Assert.isAssignable(ScheduledOfTask.class, task.getClass(), "定时任务类必须实现ScheduledOfTask接口");
// 可以通过改变数据库数据进而实现动态改变执行周期
taskRegistrar.addTriggerTask(((Runnable) task),
triggerContext -> {
String cronExpression = cronRepository.findByCronId(springScheduledCron.getCronId()).getCronExpression();
return new CronTrigger(cronExpression).nextExecutionTime(triggerContext);
}
);
}
}
@Bean
public Executor taskExecutor() {
return Executors.newScheduledThreadPool(10);
}
}

这里我为了做到可以灵活处理,自定义了一个接口ScheduledOfTask:

/**
* @author yudong
* @date 2019/5/11
*/
public interface ScheduledOfTask extends Runnable {
/**
* 定时任务方法
*/
void execute();
/**
* 实现控制定时任务启用或禁用的功能
*/
@Override
default void run() {
SpringScheduledCronRepository repository = SpringUtils.getBean(SpringScheduledCronRepository.class);
SpringScheduledCron scheduledCron = repository.findByCronKey(this.getClass().getName());
if (StatusEnum.DISABLED.getCode().equals(scheduledCron.getStatus())) {
return;
}
execute();
}
}

所有定时任务类只需要实现这个接口并相应的在数据库插入一条记录,那么在微服务启动的时候,就会被自动注册到Spring的定时任务里,也就是这行代码所起的作用:

            // 可以通过改变数据库数据进而实现动态改变执行周期
taskRegistrar.addTriggerTask(((Runnable) task),
triggerContext -> {
String cronExpression = cronRepository.findByCronId(springScheduledCron.getCronId()).getCronExpression();
return new CronTrigger(cronExpression).nextExecutionTime(triggerContext);
}
);

具体的定时任务类(一共有三个,这里我只列出一个):

/**
* @author yudong
* @date 2019/5/10
*/
@Component
public class DynamicPrintTask implements ScheduledOfTask {
Logger logger = LoggerFactory.getLogger(getClass());
private int i;
@Override
public void execute() {
logger.info("DynamicPrintTask execute times:{}", ++i);
} }

项目跑起来后,可以看到类被定时执行了:

那么,要如何动态改变执行周期呢,没有理由去手工改动数据库吧?开发测试环境可以这么搞,生产环境就不可以了,所以为了做到动态改变数据库数据,很简单,提供一个Controller类供调用:

/**
* 管理定时任务(需要做权限控制),具体的业务逻辑应
* 该写在Service里,良好的设计是Controller本身
* 只处理很少甚至不处理工作,业务逻辑均委托给
* Service进行处理,这里我偷一下懒,都写在Controller
* @author yudong
* @date 2019/5/10
*/
@Controller
@RequestMapping("/scheduled/task")
public class TaskController {
@Autowired
private ApplicationContext context;
@Autowired
private SpringScheduledCronRepository cronRepository;
/**
* 查看任务列表
*/
@RequestMapping("/taskList")
public String taskList(Model model) {
model.addAttribute("cronList", cronRepository.findAll());
return "task-list";
}
/**
* 编辑任务cron表达式
*/
@ResponseBody
@RequestMapping("/editTaskCron")
public Integer editTaskCron(String cronKey, String newCron) {
if (!CronUtils.isValidExpression(newCron)) {
throw new IllegalArgumentException("失败,非法表达式:" + newCron);
}
cronRepository.updateCronExpressionByCronKey(newCron, cronKey);
return AppConsts.SUCCESS;
}
/**
* 执行定时任务
*/
@ResponseBody
@RequestMapping("/runTaskCron")
public Integer runTaskCron(String cronKey) throws Exception {
((ScheduledOfTask) context.getBean(Class.forName(cronKey))).execute();
return AppConsts.SUCCESS;
}
/**
* 启用或禁用定时任务
*/
@ResponseBody
@RequestMapping("/changeStatusTaskCron")
public Integer changeStatusTaskCron(Integer status, String cronKey) {
cronRepository.updateStatusByCronKey(status, cronKey);
return AppConsts.SUCCESS; }
}

这里我为了方便调用Controller接口,使用thymeleaf技术写了一个简易的html管理页面:

网页效果是这样的:

可以做到查看任务列表,修改任务cron表达式(也就实现了动态改变定时任务执行周期),暂停定时任务,以及直接执行定时任务。

最后如果对定时任务有更多其它要求,可以考虑使用xxljob这个开源的分布式任务调度平台,有兴趣的同学可以去了解,这里我就不展开了。

源码github地址:https://github.com/jufeng98/java-master

在Spring Boot中动态实现定时任务配置的更多相关文章

  1. 如何在Spring Boot 中动态设定与执行定时任务

    本篇文章的目的是记录并实现在Spring Boot中,动态设定与执行定时任务. 我的开发项目是 Maven 项目,所以首先需要在 pom.xml 文件中加入相关的依赖.依赖代码如下所示: <de ...

  2. Spring Boot中使用MyBatis注解配置详解(1)

    之前在Spring Boot中整合MyBatis时,采用了注解的配置方式,相信很多人还是比较喜欢这种优雅的方式的,也收到不少读者朋友的反馈和问题,主要集中于针对各种场景下注解如何使用,下面就对几种常见 ...

  3. spring boot中的约定优于配置

    Spring Boot并不是一个全新的框架,而是将已有的Spring组件整合起来. Spring Boot可以说是遵循约定优于配置这个理念产生的.它的特点是简单.快速和便捷. 既然遵循约定优于配置,则 ...

  4. spring boot 中logback多环境配置

    spring boot 配置logback spring boot自带了log打印功能,使用的是Commons logging 具体可以参考spring boot log 因此,我们只需要在resou ...

  5. Spring Boot 中使用 @Transactional 注解配置事务管理

    事务管理是应用系统开发中必不可少的一部分.Spring 为事务管理提供了丰富的功能支持.Spring 事务管理分为编程式和声明式的两种方式.编程式事务指的是通过编码方式实现事务:声明式事务基于 AOP ...

  6. Spring Boot中使用@Transactional注解配置事务管理

    事务管理是应用系统开发中必不可少的一部分.Spring 为事务管理提供了丰富的功能支持.Spring 事务管理分为编程式和声明式的两种方式.编程式事务指的是通过编码方式实现事务:声明式事务基于 AOP ...

  7. Spring Boot中以代码方式配置Tomcat

    在Spring Boot2.0以上配置嵌入式Servlet容器时EmbeddedServletContainerCustomizer类不存在,经网络查询发现被WebServerFactoryCusto ...

  8. Spring Boot中使用MyBatis注解配置详解

    传参方式 下面通过几种不同传参方式来实现前文中实现的插入操作. 使用@Param 在之前的整合示例中我们已经使用了这种最简单的传参方式,如下: @Insert("INSERT INTO US ...

  9. Spring Boot 中实现定时任务的两种方式

    在 Spring + SpringMVC 环境中,一般来说,要实现定时任务,我们有两中方案,一种是使用 Spring 自带的定时任务处理器 @Scheduled 注解,另一种就是使用第三方框架 Qua ...

随机推荐

  1. day10 基本数据类型(下)

    目录 一.集合 1.作用 2.定义 3.类型转换 4.内置方法 4.1交集:两者共有的 4.2合集:两者融合去重 4.3差集:某个集合单独有的 4.4对称差集:两个集合各自单独有的组成的集合 4.5父 ...

  2. Spring的学习与实战

    目录 一.Spring起步 学习路线图 Spring的基础知识 什么是Spring Spring框架核心模块 SpringBoot 第一个Spring应用DEMO 编写自己的第一个SpringMVC例 ...

  3. MYSQL 之 JDBC(二): 数据库连接(二)通过DriverManager获取数据库连接

    通过DriverManager获取数据库连接 修改一下配置文件 driver=com.mysql.cj.jdbc.Driver jdbcUrl=jdbc:mysql://localhost:3306/ ...

  4. 蜂鸟E203系列——Linux调试(GDB+Openocd)

    欲观原文,请君移步 本文基于文章<蜂鸟E203系列--利用 Hbrid-E-SDK 环境开发程序> GDB 简介 GDB(GNU Project Debugger),是 GNU 工具链中的 ...

  5. bzoj3436小K的农场

    bzoj3436小K的农场 题意: n个数,知道m条关系:a-b≥c.a-b≤c或a==b.问是否存在满足所有关系的情况.n≤10000,m≤10000. 题解: 差分约束.因为只要求是否满足,因此最 ...

  6. 初识Java对象

    初始Java对象 本文的概述顺序 1什么是面向对象编程(面向对象编程与 面向过程编程的区别) 2类和对象的的关系 3类的定义 4对象的创建 5对象使用的一些细节 5.1对象在内存中的产生及分布 5.2 ...

  7. 普通list和树状list互转

    import java.util.ArrayList; import java.util.List; public class TreeNode { private String id; privat ...

  8. Ethical Hacking - Web Penetration Testing(5)

    LOCAL FILE INCLUSION Allows an attacker to read ANY file on the same server. Access files outside ww ...

  9. QQ音乐Android客户端Web页面通用性能优化实践

    QQ音乐 Android 客户端的 Web 页面日均 PV 达到千万量级,然而页面的打开耗时与 Native 页面相距甚远,需要系统性优化.本文将介绍 QQ 音乐 Android 客户端在进行 Web ...

  10. P5198 [USACO19JAN]Icy Perimeter S (洛谷) (水搜索)

    同样是因为洛谷作业不会写…… 写(水)博客啦. 直接放题目吧,感觉放在代码框里好看点 Farmer John要开始他的冰激凌生意了!他制造了一台可以生产冰激凌球的机器,然而不幸的是形状不太规则,所以他 ...