java中的实时定时任务管理系统
前言
本文将介绍在不集成quartz框架的情况下,用java代码实现定时任务的开启、关闭、修改等功能。
新增定时任务的线程池配置类
@Configuration
public class SchedulingConfig {
@Bean
public TaskScheduler taskScheduler(){
/*
* ThreadPoolTaskScheduler:线程池任务调度类,能够开启线程池进行任务调度。
*
*/
ThreadPoolTaskScheduler poolTaskScheduler = new ThreadPoolTaskScheduler();
// 定时任务执行线程池核心线程数
poolTaskScheduler.setPoolSize(5);
// 开启 remove-on-cancel
poolTaskScheduler.setRemoveOnCancelPolicy(true);
poolTaskScheduler.setThreadNamePrefix("ThreadPoolTaskScheduler--");
return poolTaskScheduler;
}
}
配置ScheduledFuture的包装类。ScheduledFuture是ScheduledExecutorService定时任务线程池的执行结果。
public final class ScheduledTask {
/**
* ScheduledFuture 是 ScheduledExecutorService 定时任务线程池的执行结果。
*/
volatile ScheduledFuture<?> future;
/**
* 取消 定时任务
*/
public void cancel(){
ScheduledFuture<?> future = this.future;
if (future != null) {
future.cancel(true);
}
}
}
实现Runnable接口实现类,被定时任务线程池调用,用来执行指定bean里面的方法
这里的notiId与notiType是我的业务参数,可删除,定时任务执行run()的时候只需要枚举类taskEnum里的参数就够了。
public class TaskRunnable implements Runnable {
private static final Logger LOGGER = LoggerFactory.getLogger(TaskRunnable.class);
private String notiId;
private Integer notiType;
private TaskEnum taskEnum;
public TaskRunnable(String notiId, Integer notiType, TaskEnum taskEnum) {
this.notiId = notiId;
this.notiType = notiType;
this.taskEnum = taskEnum;
}
@Override
public void run() {
LOGGER.info("定时任务开始执行:notiId:{},notiType:{},taskEnum:{}", notiId, notiType, taskEnum);
// 开始执行时间
long startTime = System.currentTimeMillis();
try {
// 获取到bean
Object target = SpringContextUtil.getBean(taskEnum.getBeanName());
// 获取到bean里面的方法
Method method = null;
method = target.getClass().getDeclaredMethod(taskEnum.getMethodName());
ReflectionUtils.makeAccessible(method);
method.invoke(target);
} catch (Exception e) {
LOGGER.error("定时任务执行异常 - notiId:{},notiType:{},taskEnum:{},exception:{}", notiId, notiType, taskEnum,e);
}
long times = System.currentTimeMillis() - startTime;
LOGGER.info("定时任务执行结束 - notiId:{},notiType:{},taskEnum:{},time:{}", notiId, notiType, taskEnum, times);
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof TaskRunnable)) {
return false;}
TaskRunnable that = (TaskRunnable) o;
return Objects.equals(notiType, that.notiType) &&
Objects.equals(notiId, that.notiId) &&
taskEnum == that.taskEnum;
}
@Override
public int hashCode() {
return Objects.hash(notiType, notiId, taskEnum);
}
public static boolean isBlank(String str) {
int strLen;
if (str == null || (strLen = str.length()) == 0) {
return true;
}
for (int i = 0; i < strLen; i++) {
if (!Character.isWhitespace(str.charAt(i))) {
return false;
}
}
return true;
}
}
定时任务注册类,用来增加、删除定时任务
@Component
public class CronTaskRegistrar implements DisposableBean {
/**
* 存放定时任务
*/
private final Map<Runnable, ScheduledTask> scheduledTaskMap = new ConcurrentHashMap<>();
@Autowired
private TaskScheduler taskScheduler;
/**
* 添加定时任务类
*
* @param task
* @param cronExpression cron表达式
*/
public void addCronTask(Runnable task, String cronExpression) {
addCronTask(new CronTask(task, cronExpression));
}
/**
* 添加定时任务类
*
* @param cronTask
*/
public void addCronTask(CronTask cronTask) {
if (cronTask != null) {
Runnable task = cronTask.getRunnable();
if (this.scheduledTaskMap.containsKey(task)) {
this.removeCronTask(task);
}
this.scheduledTaskMap.put(task, scheduledCronTask(cronTask));
}
}
/**
* 移除 定时任务,并取消
*
* @param task
*/
public void removeCronTask(Runnable task) {
ScheduledTask scheduledTask = this.scheduledTaskMap.remove(task);
if (scheduledTask != null) {
scheduledTask.cancel();
}
}
/**
* 调用线程池
* ThreadPoolTaskScheduler.schedule()方法会创建一个定时计划ScheduledFuture,
* 在这个方法需要添加两个参数,Runnable(线程接口类) 和CronTrigger(定时任务触发器)
*
* @param cronTask 定时任务
* @return
*/
public ScheduledTask scheduledCronTask(CronTask cronTask) {
ScheduledTask scheduledTask = new ScheduledTask();
scheduledTask.future = this.taskScheduler.schedule(cronTask.getRunnable(), cronTask.getTrigger());
return scheduledTask;
}
/**
* 重写销毁方法
*
* @throws Exception
*/
@Override
public void destroy() throws Exception {
for (ScheduledTask task : this.scheduledTaskMap.values()) {
task.cancel();
}
this.scheduledTaskMap.clear();
}
}
定时任务示例类
@Component("SendMailTask")
public class SendMailTask{
private static final Logger LOGGER = LoggerFactory.getLogger(SendMailTask.class);
public void sendMail(){
String transId = CommonFunction.getTransId();
LOGGER.info("[{}] 定时发送邮件开始",transId);
// 要实现的业务逻辑
LOGGER.info("[{}] 定时发送邮件结束",transId);
}
}
定时任务实体类SysJobVo
@Data
public class SysJobVo {
/**
* 定时任务id
*/
private Long jobId;
/**
* 通知类型
*/
private Integer notiType;
/**
* 通知配置表业务主键
*/
private String notiId;
/**
* 定时任务表达式
*/
private String cronExpression;
/**
* 定时任务状态
*/
private Integer jobStatus;
/**
* 定时任务key 值
*/
private String schKey;
}
定时任务实体类NotiSchRuleVo
@Data
public class NotiSchRuleVo {
/**
* id 自增主键
*/
private Long id;
/**
* notiId 业务主键
*/
private String notiId;
/**
* notiType 通知类型(0-系统消息;1-邮件通知;2-短信通知)
*/
private Integer notiType;
/**
* 定时任务在数据库中存的key
*/
private String schKey;
/**
* 定时事件描述
*/
private String schDesc;
/**
* status 任务状态,0-不启用(忽略,不做任何操作),1-立即执行一次,2-启动定时任务
*/
private Integer status;
/**
* notiSch 通知事件提醒规则
*/
private String notiSch;
/**
* createBy 创建人
*/
private Long createBy;
/**
* createTime 创建时间
*/
private Date createTime;
/**
* modifyBy 修改人
*/
private Long modifyBy;
/**
* modifyTime 修改时间
*/
private Date modifyTime;
/**
* deleted 删除标记
*/
private Integer deleted;
}
枚举类,用schKey映射不同的定时任务要执行的方法
这里就是用SEND_MAIL去映射SendMailTask 类里面的sendMail方法。
public enum TaskEnum {
SEND_MAIL_TASK("SEND_MAIL","SendMailTask","sendMail"),
;
/**
* @desc 表中的SCH_KEY
*/
private String schKey;
/**
* @desc 要调用的类名
*/
private String beanName;
/**
* @desc 要调用的方法名
*/
private String methodName;
public String getSchKey() {
return schKey;
}
public void setSchKey(String schKey) {
this.schKey = schKey;
}
public String getBeanName() {
return beanName;
}
public void setBeanName(String beanName) {
this.beanName = beanName;
}
public String getMethodName() {
return methodName;
}
public void setMethodName(String methodName) {
this.methodName = methodName;
}
TaskEnum() {
}
TaskEnum(String schKey) {
this.schKey = schKey;
}
TaskEnum(String schKey, String beanName, String methodName) {
this.schKey = schKey;
this.beanName = beanName;
this.methodName = methodName;
}
}
定时任务实现类
@Service
public class SysTaskServiceImpl implements SysTaskService {
@Autowired
private CronTaskRegistrar cronTaskRegistrar;
private static List<SysJobVo> jobList = new ArrayList<>();
@Autowired
private NoticeSchRuleMapper noticeSchRuleMapper;
private final static Logger log = LoggerFactory.getLogger(SysTaskServiceImpl.class);
/**
* 查询进行中的定时任务类
*
* @param status
*/
@Override
public List<NotiSchRuleVo> selectTask(Integer status) {
log.info("SysTaskServiceImpl selectTask, status={}",status);
List<NotiSchRuleVo> noticeSchRuleList = noticeSchRuleMapper.selectAllSche(status);
for(NotiSchRuleVo notiSchRuleVo : noticeSchRuleList){
// 添加到定时任务列表
SysJobVo jobVo = new SysJobVo();
jobVo.setJobId(notiSchRuleVo.getId());
jobVo.setCronExpression(notiSchRuleVo.getNotiSch());
jobVo.setJobStatus(notiSchRuleVo.getStatus());
jobVo.setNotiType(notiSchRuleVo.getNotiType());
jobVo.setNotiId(notiSchRuleVo.getNotiId());
jobVo.setSchKey(notiSchRuleVo.getSchKey());
jobList.add(jobVo);
}
return noticeSchRuleList;
}
/**
* 添加定时任务
*
* @param notiSchRuleVo
*/
@Override
public String addTask(NotiSchRuleVo notiSchRuleVo) {
log.info("SysTaskServiceImpl addTask, status={}",notiSchRuleVo);
// 处理数据 插入数据库
noticeSchRuleMapper.insertOne(notiSchRuleVo);
if(StringUtils.isNotEmpty(notiSchRuleVo.getNotiSch())){
// 添加到定时任务列表
SysJobVo jobVo = new SysJobVo();
jobVo.setJobId(notiSchRuleVo.getId());
jobVo.setCronExpression(notiSchRuleVo.getNotiSch());
jobVo.setJobStatus(notiSchRuleVo.getStatus());
jobVo.setNotiType(notiSchRuleVo.getNotiType());
jobVo.setNotiId(notiSchRuleVo.getNotiId());
jobVo.setSchKey(notiSchRuleVo.getSchKey());
jobVo.setSchKey(notiSchRuleVo.getSchKey());
jobList.add(jobVo);
this.changeTaskStatus(Boolean.TRUE, jobVo);
}
return "addSuccess";
}
/**
* 修改定时任务
*
* @param notiSchRuleVo
*/
@Override
public String updateTask( NotiSchRuleVo notiSchRuleVo) {
// 获取数据库中已存在的数据
Long jobId = notiSchRuleVo.getId();
NoticeSchRule rule = new NoticeSchRule();
rule.setId(jobId);
rule.setStatus(Constants.NOTI_SCH_STATUS_TWO);
rule.setDeleted(DictConstant.DELETED_FALSE_INTEGER);
NoticeSchRule noticeSchRule = noticeSchRuleMapper.selectOne(rule);
Integer status = notiSchRuleVo.getStatus();
// 创建任务 VO
SysJobVo jobVo = new SysJobVo();
jobVo.setJobId(notiSchRuleVo.getId());
jobVo.setCronExpression(notiSchRuleVo.getNotiSch());
jobVo.setJobStatus(status);
jobVo.setNotiType(notiSchRuleVo.getNotiType());
jobVo.setNotiId(notiSchRuleVo.getNotiId());
jobVo.setSchKey(notiSchRuleVo.getSchKey());
SysJobVo existJob = null;
// 获取已经在执行的定时任务列表中已存在的数据
for(SysJobVo sysJobVo : jobList){
if(jobVo.getJobId().equals(sysJobVo.getJobId())){
existJob = sysJobVo;
}
}
if(null == existJob ){
// 内存里没有此定时任务
if(null == noticeSchRule){
// 数据库也没有此定时任务
if(2 == status){
// 启用状态,则开启新定时任务
// 开启新的定时任务
this.changeTaskStatus(Boolean.TRUE, jobVo);
jobList.add(jobVo);
}
}else {
// 内存没有数据库有,则先从数据库修复此定时任务
this.changeTaskStatus(Boolean.TRUE, jobVo);
jobList.add(jobVo);
existJob = jobVo;
// 判断status
if(2 == status){
// 启用状态,则先关闭现有的定时任务
this.changeTaskStatus(Boolean.FALSE, existJob);
jobList.remove(existJob);
// 启动新的定时任务
this.changeTaskStatus(Boolean.TRUE, jobVo);
jobList.add(jobVo);
}else {
// 不启用状态,则把原来的关闭
this.changeTaskStatus(Boolean.FALSE, existJob);
jobList.remove(existJob);
}
}
}else {
// 内存里有此定时任务,则先关闭此定时任务,再判断是否启用
this.changeTaskStatus(Boolean.FALSE, existJob);
jobList.remove(existJob);
if( 2 == status){
// 开启新的定时任务
this.changeTaskStatus(Boolean.TRUE, jobVo);
jobList.add(jobVo);
}
}
// 更新数据库
BeanCopyUtil.copyPropertiesIgnoreNull(notiSchRuleVo,rule);
noticeSchRuleMapper.updateByPrimaryKeySelective(rule);
return "updateSuccess";
}
/**
* 删除定时任务
*
* @param jobId
*/
@Override
public String deleteTask(Long jobId, Long operId) {
NoticeSchRule noticeSchRule = noticeSchRuleMapper.selectByPrimaryKey(jobId);
// 获取已经在执行的定时任务列表中已存在的数据
SysJobVo existJob = new SysJobVo();
existJob.setJobId(noticeSchRule.getId());
existJob.setNotiId(noticeSchRule.getNotiId());
existJob.setNotiType(noticeSchRule.getNotiType());
existJob.setCronExpression(noticeSchRule.getNotiSch());
existJob.setJobStatus(noticeSchRule.getStatus());
existJob.setSchKey(noticeSchRule.getSchKey());
// 判断定时任务是否开启
if (Objects.equals(noticeSchRule.getStatus(), 2)) {
// 关闭定时任务
this.changeTaskStatus(Boolean.FALSE, existJob);
}
// 处理数据 插入数据库
for (int i = 0; i < jobList.size(); i++) {
Long jobId2 = jobList.get(i).getJobId();
if (Objects.equals(jobId2,jobId)) {
jobList.remove(i);
break;
}
}
// 更新数据库
noticeSchRule.setModifyBy(operId);
noticeSchRule.setModifyTime(new Date());
noticeSchRule.setDeleted(Constants.DELETED_TRUE);
noticeSchRuleMapper.updateByPrimaryKey(noticeSchRule);
return "deleteSuccess";
}
/**
* 修改定时任务类状态
*
* @param add
* @param jobVo
*/
private void changeTaskStatus(boolean add, SysJobVo jobVo) {
if (add) {
TaskRunnable task = new TaskRunnable( jobVo.getNotiId(),jobVo.getNotiType(),TaskEnum.valueOf(jobVo.getSchKey()+"_TASK"));
cronTaskRegistrar.addCronTask(task, jobVo.getCronExpression());
} else {
TaskRunnable task = new TaskRunnable( jobVo.getNotiId(),jobVo.getNotiType(),TaskEnum.valueOf(jobVo.getSchKey()+"_TASK"));
cronTaskRegistrar.removeCronTask(task);
}
}
}
初始化数据库中的定时任务
这里查询status为2的定时任务,则是查询数据库中状态为开启的定时任务。
@Service
public class SysJobRunner implements CommandLineRunner {
private final static Logger LOGGER = LoggerFactory.getLogger(SysJobRunner.class);
@Autowired
private CronTaskRegistrar cronTaskRegistrar;
@Autowired
private SysTaskServiceImpl sysTaskService;
@Override
public void run(String... args) throws Exception {
String transId = CommonFunction.getTransId();
List<NotiSchRuleVo> noticeSchRuleList = sysTaskService.selectTask(2);
LOGGER.error("[{}] SysJobRunner run,初始化定时任务 noticeSchRuleList = [{}]", transId, noticeSchRuleList);
for(NotiSchRuleVo notiSchRuleVo : noticeSchRuleList){
if(StringUtils.isNotEmpty(notiSchRuleVo.getNotiSch()) && StringUtils.isNotEmpty(notiSchRuleVo.getSchKey())){
TaskRunnable task = new TaskRunnable(notiSchRuleVo.getNotiId(),notiSchRuleVo.getNotiType(),TaskEnum.valueOf(notiSchRuleVo.getSchKey()+"_TASK"));
cronTaskRegistrar.addCronTask(task, notiSchRuleVo.getNotiSch());
}
}
LOGGER.error("[{}] SysJobRunner run,定时任务初始化完毕", transId);
}
}