文章目录
前言
分享与记录自定义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。