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

文章目录

  • 前言
  • 一、自定义定时任务task线程接口
  • 二、配置全局 监听listener,初始化CustomTask类型线程的init方法
  • 三、自定义定时任务线程池
  • 四、实现自定义task接口,编写自己的定时任务
  • 五、补充说明
    • 1.关于nacos动态刷新问题
    • 2.关于@RefreshScope的踩坑
  • 六、总结


前言

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


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

public interface CustomTask extends Runnable{

    /**
     * init
     * 

description: 初始化操作,定义任务执行方式,比如while扫描,schedule执行,单次执行等等

* @author: Administrator * @date: 10:42 2021/12/10 */
void init(); /** * updateTask *

description: 监听cron变更,更新任务执行时间

* @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。

你可能感兴趣的:(#,spring,boot技术进阶,spring,boot,java,后端)