题外话

这是我第五篇原创文章,计划写定时任务内容,想想只要打开自己的有道云笔记,复制粘贴,整理排版一下就能轻松搞定了,这样做有意义吗?自己写文章的价值点是什么呢?考虑了很长时间,主要希望做到一下几点:

1.思路分析

我一直觉得作为技术人员,技术思维比技术本身更重要,工作中遇到难题无法解决,没思路才是最可怕的。解决问题的思维能力特别考验技术人内功、经验和技术积累。

现在如何需要获取某技术知识,很多知识点可以通过网络获取,而且获取成本越来越低,例如目前很火的极客时间,里面很多大牛的课程,价格已经非常低,想想3年前我想找付费知识学习,都是几百几千的费用,只能在淘宝淘盗版。很多问题是想不到,如果有人告诉思路,就算里面的技术点不明白,交给搜索引擎就是了。
因此,

思路分析

提到使用定时任务扫描数据库中状态值为失败的业务定时再次调用接口。此时项目中未有现成的定时任务案例,该如何做呢?我的思路如下:

  1. 网络百度谷歌搜索springboot定时任务,直接采用注解方式(Spring Task)去实现。因为简单快速,在要求不高而追求速度的情况下采用,以实现功能为主。
  2. 后续随着定时任务增多,将其改成了异步注解的方式。
  3. 后面业务需求:将定时任务全部关闭。这个时候上述方式的已无法满足需求了,总不可能一行一行的注释代码吧。当时由于百度不到相关教程,能够实现灵活关闭定时任务的教程都是一些定时任务相关框架的使用,比如Quartz,xxl-job。这些方式当然能够实现需求(ps:复杂度提升,还要将原有的定时任务代码改造迁移,需要花费一定时间)自己仔细琢磨着,我的需求是能够全部关闭定时任务,那么可以做一个开关放到配置文件中,这样只需要更改配置文件就行了,而由于spring task里面采用注解去实现,顺着注解@EnableScheduling翻看源码,找到了解决方法。
  4. 后期随着项目定时任务增多,业务需求也希望能够更好的管理配置定时任务,这个时候就需要选择相关框架了,推荐xxl-job,简单方便上手快,文档详细,功能也很全面。
springboot定时任务对接

在主类上使用@EnableScheduling注解开启对定时任务的支持
在定时任务的类或者方法上添加@Async实现异步
cron时间动态配置到配置文件中

@Component
@Async
public class SchedulingTest {
    @Value("${paramInfo}")
    private String paramInfo;

    private static Logger log = LoggerFactory.getLogger(SchedulingTest.class);

    @Scheduled(cron = "${cron.time}")
    public void queryPayStatus() {
        log.info("执行定时任务queryPayStatus---start");
        log.info("执行定时任务queryPayStatus---end");
    }

}
配置定时任务开关

1.新建SchedulerCondition类,配置定时任务开启条件

scheduling.enabled对应到配置文件中的值
scheduling.enabled=true 开启
scheduling.enabled=false 关闭

public class SchedulerCondition implements Condition {
    @Override
    public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {
        boolean enable = Boolean.valueOf(conditionContext.getEnvironment().getProperty("scheduling.enabled"));
        return enable;
    }
}

自定义启动类

@Configuration
public class Scheduler {
    @Conditional(SchedulerCondition.class)
    @Bean(name = TaskManagementConfigUtils.SCHEDULED_ANNOTATION_PROCESSOR_BEAN_NAME)
    @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
    public ScheduledAnnotationBeanPostProcessor scheduledAnnotationProcessor() {
        return new ScheduledAnnotationBeanPostProcessor();
    }

}

这样就完成了配置文件中scheduling.enabled的值来控制定时任务的启动与关闭。

自定义异步配置类
@Configuration
@EnableAsync
public class AsyncConfig {
    @Value("${core.pool.size}")
    private int corePoolSize;
    @Value("${max.pool.size}")
    private int maxPoolSize;
    @Value("${queue.capacity}")
    private int queueCapacity;
     @Value("${keep.alive}")
    private int keepAlive;
        // 核心线程数(setCorePoolSize)10:线程池创建时候初始化的线程数
        // 最大线程数(setMaxPoolSize)20:线程池最大的线程数,只有在缓冲队列满了之后才会申请超过核心线程数的线程
        // 缓冲队列(setQueueCapacity)200:用来缓冲执行任务的队列
        // 允许线程的空闲时间(setKeepAliveSeconds)60秒:当超过了核心线程出之外的线程在空闲时间到达之后会被销毁
        // 线程池名的前缀(setThreadNamePrefix):设置好了之后可以方便我们定位处理任务所在的线程池
        // 线程池对拒绝任务的处理策略(setRejectedExecutionHandler):这里采用了CallerRunsPolicy策略,当线程池没有处理能力的时候,该策略会直接在 execute
        // 方法的调用线程中运行被拒绝的任务(setWaitForTasksToCompleteOnShutdown);如果执行程序已关闭,则会丢弃该任务
        // setWaitForTasksToCompleteOnShutdown(true)该方法就是这里的关键,用来设置线程池关闭的时候等待所有任务都完成再继续销毁其他的Bean,这样这些异步任务的销毁就会先于Redis线程池的销毁。
        // 同时,这里还设置了setAwaitTerminationSeconds(60),该方法用来设置线程池中任务的等待时间,如果超过这个时候还没有销毁就强制销毁,以确保应用最后能够被关闭,而不是阻塞住。

    @Bean
    public Executor taskExecutor() {
       ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(corePoolSize);
        executor.setMaxPoolSize(maxPoolSize);
        executor.setQueueCapacity(queueCapacity);
        executor.setKeepAliveSeconds(keepAlive);
        executor.setThreadNamePrefix("collect-");//自定义线程名称
        // 线程池对拒绝任务(无线程可用)的处理策略,目前只支持AbortPolicy、CallerRunsPolicy;默认为后者
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        //调度器shutdown被调用时等待当前被调度的任务完成
        executor.setWaitForTasksToCompleteOnShutdown(true);
        //等待时长
        executor.setAwaitTerminationSeconds(60);
        executor.initialize();
        return executor;

    }
}
CRON表达式

每隔5秒执行一次:*/5 * * * * ?
每隔1分钟执行一次:0 */1 * * * ?
每天23点执行一次:0 0 23 * * ?
每天凌晨1点执行一次:0 0 1 * * ?
每月1号凌晨1点执行一次:0 0 1 1 * ?
每月最后一天23点执行一次:0 0 23 L * ?
每周星期天凌晨1点实行一次:0 0 1 ? * L
在26分、29分、33分执行一次:0 26,29,33 * * * ?
每天的0点、13点、18点、21点都执行一次:0 0 0,13,18,21 * * ?
“0 15 10 * * ? 2005” 2005年的每天上午10:15 触发