在工作中遇到这样一个需求,“在每个工作日的【11:15、14:15、16:15、18:00】定时提醒”,该需求中存在两个难点:
- 每日时间是固定的,并且不是周期循环,只能配置在固定的时间点进行执行
- 在工作日执行,这就存在需要排除周六、天,排除节假日,可能还存在周六、日补班的情况等。
此处对工作日的处理只通过程序排除周六、日。对节假日暂不进行处理,但思路是如果对节假日进行处理只能进行人工配置的方式,例如将每年的节假日和周末补班的情况预先配置到数据库表或缓存(Redis)中,定时任务每次执行时先进行判断。
代码结构自上而下
主启动类
此处主要需要注意的是需要添加 @EnableScheduling 注解进行开启,否则定时任务不生效。
package com.lx.dca.okr.batch;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableScheduling;
/**
* 启动类
* @author wangcp
* @date 2021/05/07 17:04
**/
@SpringBootApplication
@EnableScheduling
public class BatchApplication {
public static void main(String[] args) {
SpringApplication.run(BatchApplication.class , args);
}
}
定时任务执行类
此处需要注意类上添加 @Component 注解进行注入,否则定时任务不执行
package com.lx.dca.okr.batch.task;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.scheduling.annotation.Schedules;
import org.springframework.stereotype.Component;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
* 定时提醒类
* @author wangcp
* @date 2021/05/08 18:04
**/
@Component
public class TimingRemindTask {
// @Schedules({@Scheduled(cron = "0 15 11,14,16 * * ? "),@Scheduled(cron = "0 0 18 * * ? ")})
@Schedules({@Scheduled(cron = "0 0/3 9 * * ? "),@Scheduled(cron = "0 40,45,50,55 9 * * ? ")})
public void remindTask(){
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
System.out.println("执行定时任务了,执行时间为:" + sdf.format(new Date()));
}
}
被注释掉的 @Schedules 是根据开头需求【11:15、14:15、16:15、18:00】设定的,但为了方便测试,定义了从9:00开始每3分钟执行一次和从9:00开始9:40、9:45、9:50、9:55执行,执行结果如下图所示:

image-20210510100234397.png
现在定时任务的问题已经解决了,但还需要排除周六和周日。解决的思路为执行定时任务时,判断当前是否为周六或周日,不是则正常执行,反之则跳过此次任务。
添加判断代码
package com.lx.dca.okr.batch.task;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.scheduling.annotation.Schedules;
import org.springframework.stereotype.Component;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
/**
* 定时提醒类
* @author wangcp
* @date 2021/05/08 18:04
**/
@Component
public class TimingRemindTask {
// @Schedules({@Scheduled(cron = "0 15 11,14,16 * * ? "),@Scheduled(cron = "0 0 18 * * ? ")})
@Schedules({@Scheduled(cron = "0 0/1 * * * ? "),@Scheduled(cron = "0 40,45,50,55 9 * * ? ")})
public void remindTask(){
Date date = new Date();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
// 不为周末时进行执行
if(this.isWeekends(date)){
System.out.println("执行定时任务了,执行时间为:" + sdf.format(date));
}
}
/**
* 根据传入时间进行判断,若为周六日则返回false,否则返回true
* @author wangcp
* @date 2021/05/10 10:24
* @param date
* @return boolean
*/
private boolean isWeekends(Date date){
Calendar instance = Calendar.getInstance();
instance.setTime(date);
if(instance.get(Calendar.DAY_OF_WEEK) == Calendar.SATURDAY || instance.get(Calendar.DAY_OF_WEEK) == Calendar.SUNDAY){
return false;
}
return true;
}
}
添加线程池
线程池配置类
有关线程池不做详细介绍,前面有专门介绍线程池的文章。
package com.lx.dca.okr.batch.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.task.TaskExecutor;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import java.util.concurrent.ThreadPoolExecutor;
/**
* 线程池配置类
* @author wangcp
* @date 2021/05/08 17:56
**/
@Data
@EnableAsync
@Configuration
@ConfigurationProperties(prefix = "poolconfig")
public class ThreadPoolConfig {
/**
* 线程池维护线程的最少数量,即使没有任务需要执行,也会一直存活
*/
private int corePoolSize;
/**
* 线程池维护线程的最大数量
*/
private int maxPoolSize;
/**
* 缓存队列(阻塞队列)当核心线程数达到最大时,新任务会放在队列中排队等待执行
*/
private int queueCapacity;
/**
* 允许的空闲时间,当线程空闲时间达到keepAliveTime时,线程会退出,直到线程数量=corePoolSize
*/
private int keepAlive;
@Bean
public TaskExecutor taskExecutor(){
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
//设置核心线程数
executor.setCorePoolSize(corePoolSize);
// 设置最大线程数
executor.setMaxPoolSize(maxPoolSize);
// 设置队列容量
executor.setQueueCapacity(queueCapacity);
// 设置允许的空闲时间(秒)
executor.setKeepAliveSeconds(keepAlive);
// 设置默认线程名称
executor.setThreadNamePrefix("thread-");
// 设置拒绝策略rejection-policy:当pool已经达到max size的时候,如何处理新任务
// CALL_RUNS:不在新线程中执行任务,二十由调用者所在的线程来执行
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
// 等待所有任务结束后在关闭线程池
executor.setWaitForTasksToCompleteOnShutdown(true);
return executor;
}
}
任务接口与实现类
TaskService
package com.lx.dca.okr.batch.service;
public interface TaskService {
void executeRemindMessage() throws InterruptedException;
}
TaskServiceImpl
package com.lx.dca.okr.batch.service.impl;
import com.lx.dca.okr.batch.service.TaskService;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
* 定时任务执行类
* @author wangcp
* @date 2021/05/10 10:42
**/
@Service
public class TaskServiceImpl implements TaskService {
@Async("taskExecutor")
@Override
public void executeRemindMessage() throws InterruptedException {
Date date = new Date();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
System.out.println("当前运行线的程名称:" + Thread.currentThread().getName() + " ,执行定时任务了,执行时间为:" + sdf.format(date));
Thread.sleep(1500);
}
}
执行结果如下:

image-20210510110433844.png