Spring Boot 自定义定时任务

原文连接:https://zhuanlan.zhihu.com/p/79644891

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

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

/**
 * @author yudong
 * @date 2019/8/24
 */
@EnableCaching // 启用缓存功能
@EnableScheduling // 开启定时任务功能
@ComponentScan(basePackages = "org.javamaster.b2c")
@EnableTransactionManagement
@SpringBootApplication
public class ScheduledApplication {
    private 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 {
    private Logger logger = LoggerFactory.getLogger(getClass());
    private int i;

    @Scheduled(cron = "*/15 * * * * ?")
    public void execute() {
        logger.info("thread id:{},FixedPrintTask execute times:{}", Thread.currentThread().getId(), ++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.findByCronKey(springScheduledCron.getCronKey()).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.findByCronKey(springScheduledCron.getCronKey()).getCronExpression();
                        return new CronTrigger(cronExpression).nextExecutionTime(triggerContext);
                    }
            );

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

/**
 * @author yudong
 * @date 2019/5/10
 */
@Component
public class DynamicPrintTask implements ScheduledOfTask {
    private Logger logger = LoggerFactory.getLogger(getClass());
    private int i;
    @Override
    public void execute() {
        logger.info("thread id:{},DynamicPrintTask execute times:{}", Thread.currentThread().getId(), ++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 Result<Void> editTaskCron(String cronKey, String newCron) {
        if (!CronUtils.isValidExpression(newCron)) {
            throw new IllegalArgumentException("失败,非法表达式:" + newCron);
        }
        cronRepository.updateCronExpressionByCronKey(newCron, cronKey);
        return new Result<>(AppConsts.SUCCESS, "更新成功");
    }
    /**
     * 执行定时任务
     */
    @ResponseBody
    @RequestMapping("/runTaskCron")
    public Result<Void> runTaskCron(String cronKey) throws Exception {
        ((ScheduledOfTask) context.getBean(Class.forName(cronKey))).execute();
        return new Result<>(AppConsts.SUCCESS, "执行成功");
    }
    /**
     * 启用或禁用定时任务
     */
    @ResponseBody
    @RequestMapping("/changeStatusTaskCron")
    public Result<Void> changeStatusTaskCron(Integer status, String cronKey) {
        cronRepository.updateStatusByCronKey(status, cronKey);
        return new Result<>(AppConsts.SUCCESS, "操作成功");
    }
}

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

网页效果是这样的:

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

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

  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
- chapter1:[基本项目构建(可作为工程脚手架),引入web模块,完成一个简单的RESTful API](http://blog.didispace.com/spring-boot-learning-1/) - [使用Intellij中的Spring Initializr来快速构建Spring Boot/Cloud工程](http://blog.didispace.com/spring-initializr-in-intellij/) ### 工程配置 - chapter2-1-1:[配置文件详解:自定义属性、随机数、多环境配置等](http://blog.didispace.com/springbootproperties/) ### Web开发 - chapter3-1-1:[构建一个较为复杂的RESTful API以及单元测试](http://blog.didispace.com/springbootrestfulapi/) - chapter3-1-2:[使用Thymeleaf模板引擎渲染web视图](http://blog.didispace.com/springbootweb/) - chapter3-1-3:[使用Freemarker模板引擎渲染web视图](http://blog.didispace.com/springbootweb/) - chapter3-1-4:[使用Velocity模板引擎渲染web视图](http://blog.didispace.com/springbootweb/) - chapter3-1-5:[使用Swagger2构建RESTful API](http://blog.didispace.com/springbootswagger2/) - chapter3-1-6:[统一异常处理](http://blog.didispace.com/springbootexception/) ### 数据访问 - chapter3-2-1:[使用JdbcTemplate](http://blog.didispace.com/springbootdata1/) - chapter3-2-2:[使用Spring-data-jpa简化数据访问层(推荐)](http://blog.didispace.com/springbootdata2/) - chapter3-2-3:[多数据源配置(一):JdbcTemplate](http://blog.didispace.com/springbootmultidatasource/) - chapter3-2-4:[多数据源配置(二):Spring-data-jpa](http://blog.didispace.com/springbootmultidatasource/) - chapter3-2-5:[使用NoSQL数据库(一):Redis](http://blog.didispace.com/springbootredis/) - chapter3-2-6:[使用NoSQL数据库(二):MongoDB](http://blog.didispace.com/springbootmongodb/) - chapter3-2-7:[整合MyBatis](http://blog.didispace.com/springbootmybatis/) - chapter3-2-8:[MyBatis注解配置详解](http://blog.didispace.com/mybatisinfo/) ### 事务管理 - chapter3-3-1:[使用事务管理](http://blog.didispace.com/springboottransactional/) - chapter3-3-2:[分布式事务(未完成)] ### 其他内容 - chapter4-1-1:[使用@Scheduled创建定时任务](http://blog.didispace.com/springbootscheduled/) - chapter4-1-2:[使用@Async实现异步调用](http://blog.didispace.com/springbootasync/) #### 日志管理 - chapter4-2-1:[默认日志的配置](http://blog.didispace.com/springbootlog/) - chapter4-2-2:[使用log4j记录日志](http://blog.didispace.com/springbootlog4j/) - chapter4-2-3:[对log4j进行多环境不同日志级别的控制](http://blog
1.1 前言 1.2 资料官网 1.3 spring boot起步之Hello World 1.4 Spring Boot返回json数据 1.5 Spring Boot热部署 1.6 Spring Boot使用别的json解析框架 1.7 全局异常捕捉 1.8 Spring Boot datasource - mysql 1.9 JPA - Hibernate 1.10 使用JPA保存数据 1.11 使用JdbcTemplate 1.12 Spring Boot修改端口号 1.13 Spring Boot配置ContextPath 1.14 Spring Boot改变JDK编译版本 1.15 处理静态资源(默认资源映射) 1.16 处理静态资源(自定义资源映射) 1.17 Spring Boot定时任务的使用 1.18 Spring Boot使用Druid和监控配置 1.19 Spring Boot使用Druid(编程注入) 1.20 Spring Boot普通类调用bean 1.21 使用模板(thymeleaf-freemarker) 1.22 Spring Boot 添加JSP支持 1.23 Spring Boot Servlet 1.24 Spring Boot过滤器、监听器 1.25 Spring Boot 拦截器HandlerInterceptor 1.26 Spring Boot启动加载数据CommandLineRunner 1.27 Spring Boot环境变量读取和属性对象的绑定 1.28 Spring Boot使用自定义的properties 1.29 改变自动扫描的包 1.30 Spring Boot Junit单元测试 1.31 SpringBoot启动时的Banner设置 1.32 Spring boot 文件上传(多文件上传) 1.33 导入时如何定制spring-boot依赖项的版本 1.34 Spring Boot导入XML配置 1.35 Spring Boot使用@SpringBootApplication注解 1.36 Spring Boot 监控和管理生产环境 1.37 Spring Boot的启动器Starter详解 1.38 Spring Boot集成Redis实现缓存机制 1.39 Spring Boot Cache理论篇 1.40 Spring Boot集成EHCache实现缓存机制 1.41 Spring Boot分布式Session状态保存Redis 1.42 Spring Boot Shiro权限管理 1.43 Spring Boot Shiro权限管理 1.44 Spring Boot Shiro权限管理 1.45 Spring Boot Shiro权限管理

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值