springboot实现自定义定时任务(线程池,配合nacos实现动态修改)


前言

分享与记录自定义task以及踩坑历程,与君共勉。


一、自定义定时任务task线程接口

public interface CustomTask extends Runnable{

    /**
     * init
     * <p>description: 初始化操作,定义任务执行方式,比如while扫描,schedule执行,单次执行等等 </p>
     * @author: Administrator
     * @date: 10:42 2021/12/10
     */
    void init();

    /**
     * updateTask
     * <p>description: 监听cron变更,更新任务执行时间 </p>
     * @author: Administrator
     * @date: 10:42 2021/12/10
     * @param environmentChangeEvent:
     */
    void updateTask(EnvironmentChangeEvent environmentChangeEvent);

}

二、配置全局 监听listener,初始化CustomTask类型线程的init方法

@Component
public class InitializeTask implements ApplicationListener<ContextRefreshedEvent> {

    @Override
    public void onApplicationEvent(ContextRefreshedEvent event) {
        Map<String, CustomTask> customTaskList = event.getApplicationContext().getBeansOfType(CustomTask.class);
        if (null != customTaskList && !customTaskList.isEmpty()) {
            customTaskList.forEach((name, customTask) -> {
                //任务彼此独立,所以要catch异常
                try {
                    customTask.init();
                    LogUtil.fileOutPutInfo("-----监听到:"+name+",已经启动");
                } catch (Exception e) {
                    LogUtil.printCallStack(e);
                }
            });
        }
    }
}

三、自定义定时任务线程池

@Configuration
@EnableAsync
public class ThreadPoolTaskConfig extends AsyncConfigurerSupport {
    /** 线程池核心池的大小 */
    private int corePoolSize = 5;
    /** 线程池的最大线程数 */
    private int maxPoolSize = 9;
    /** 当线程数大于核心时,此为终止前多余的空闲线程等待新任务的最长时间s */
    private int keepAliveTime = 10;
    /**队列数量 */
    private int queueCapacity = 100;
    /**等待时长 */
    private int awaitTerminationSeconds = 60;

    /**
     * 注册线程池
     * @return
     */
    @Bean("asyncThreadPoolTaskExecutor")
    public ThreadPoolTaskExecutor createAsyncThreadPoolTaskExecutor(){
        ThreadPoolTaskExecutor threadPoolTaskExecutor = new ThreadPoolTaskExecutor();
        threadPoolTaskExecutor.setCorePoolSize(corePoolSize);
        threadPoolTaskExecutor.setMaxPoolSize(maxPoolSize);
        threadPoolTaskExecutor.setKeepAliveSeconds(keepAliveTime);
        //调度器shutdown被调用时等待当前被调度的任务完成
        threadPoolTaskExecutor.setWaitForTasksToCompleteOnShutdown(true);
        threadPoolTaskExecutor.setAwaitTerminationSeconds(awaitTerminationSeconds);
        threadPoolTaskExecutor.setQueueCapacity(queueCapacity);
        threadPoolTaskExecutor.setThreadNamePrefix("TaskExecutorProduct-");
        // 线程池对拒绝任务(无线程可用)的处理策略
        threadPoolTaskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        return threadPoolTaskExecutor;
    }

    /**
     * 注册定时任务线程池
     * @return
     */
    @Bean("threadPoolTaskScheduler")
    public ThreadPoolTaskScheduler createThreadPoolTaskScheduler(){
        ThreadPoolTaskScheduler threadPoolTaskScheduler = new ThreadPoolTaskScheduler();
        threadPoolTaskScheduler.setPoolSize(maxPoolSize);
        threadPoolTaskScheduler.setThreadNamePrefix("TaskScheduler-");
        return threadPoolTaskScheduler;
    }
    @Override
    public Executor getAsyncExecutor() {
        return createAsyncThreadPoolTaskExecutor();
    }

    @Override
    public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
        return (ex, method, params) -> System.out.println(String.format("taskConfig-'%s'", method, ex));
    }
}

四、实现自定义task接口,编写自己的定时任务

@Component
@RequiredArgsConstructor
@RefreshScope(proxyMode = ScopedProxyMode.DEFAULT)
public class AppointmentCloseTask implements CustomTask {
    @Resource(name = "threadPoolTaskScheduler")
    private ThreadPoolTaskScheduler threadPoolTaskScheduler;
    @Value("${appointment.close.schedule}")
    private String appointmentCorn;
    private ScheduledFuture<?> appointmentSchedule;
    private final ApplicationContext applicationContext;
    private final TeacherDailyAppointmentMapper teacherDailyAppointmentMapper;
    private final MyMapper myMapper;

    @Override
    public void init() {
        appointmentSchedule = threadPoolTaskScheduler.schedule(this, triggerContext -> new CronTrigger(appointmentCorn).nextExecutionTime(triggerContext));
    }
	
    @EventListener
    @Override
    public void updateTask(EnvironmentChangeEvent event) {
        //监听nacos配置的定时任务参数,如果有变更,触发变更定时任务器
        if (event.getKeys().contains("appointment.close.schedule")){
            //关闭之前的调度任务
            appointmentSchedule.cancel(true);
            appointmentCorn = applicationContext.getEnvironment().getProperty("appointment.close.schedule");
            //开启新的调度任务
            init();
        }
    }

    @Override
    public void run() {
        //编写自己的业务逻辑
       System.out.println("todo some thing");
    }

}

五、补充说明

1.关于nacos动态刷新问题

我当前用的是nacos<2021.1>版本,不支持动态刷新,ps:这一点我当时没想到,之前一直用的携程的apollo做配置中心,支持热更新自动刷新,尤其是定时任务这,还有@ApolloConfigChangeListener注解;所以就使用了springcloud的@RefreshScope,@EventListener作为替换,用来监听配置变更以及触发相应的定时任务线程重置。

2.关于@RefreshScope的踩坑

@RefreshScope(proxyMode = ScopedProxyMode.DEFAULT),需要proxyMode = ScopedProxyMode.DEFAULT,重要的事情说三遍,
否则ioc容器中会创建两个bean,这样就导致上述监听的时候,相同的task会拿到两次并且初始化两次,会有两个相同的定时任务在跑

六、总结

注解的源码还是要去读去分析,如上的问题持续了3个月我才通过sql日志发现问题并进行整改,幸好项目较小ing。

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

人生大事

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值