SpringBoot 自动化定时任务管理
最近公司中写定时任务相关的东西,但是呢本人觉的不通用,就写了一个通过数据库配置可以自动关闭/打开/修改调度时间的自动化定时任务;
二话不说代码如下:定时线程池
package com.itechhero.app.module.config.scheduler;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.TaskScheduler;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
@Configuration
@Slf4j
public class SchedulerConfig {
@Bean
public TaskScheduler threadPoolTaskScheduler() {
ThreadPoolTaskScheduler taskScheduler = new ThreadPoolTaskScheduler();
/*定时任务执行线程池核心线程数*/
taskScheduler.setPoolSize(20);
taskScheduler.setRemoveOnCancelPolicy(true);
taskScheduler.setThreadNamePrefix("TechHero-Scheduler-");
return taskScheduler;
}
}
定时ApplicationContext 工具类
package com.itechhero.app.module.config;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
/**
* 获取ApplicationContextG工具类
* 作者: 吴 波
* 时间: 2020-03-17 19:57
* 笔名: 那年秋天的茄子^^
*/
@Component
public class AppContextUtils implements ApplicationContextAware {
public static ApplicationContext context;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
context=applicationContext;
}
public static ApplicationContext getContext() {
return context;
}
}
定时管理器
package com.itechhero.app.module.config.scheduler;
import com.itechhero.app.module.common.utils.req.ResBean;
import com.itechhero.app.module.config.AppContextUtils;
import com.itechhero.app.module.edu.timer.model.TimerBean;
import com.itechhero.app.module.edu.timer.service.TimerService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.TaskScheduler;
import org.springframework.scheduling.support.CronTrigger;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.atomic.AtomicBoolean;
/**
* 自动化定时任务管理器
* 作者: 吴 波
* 时间: 2020-03-26 17:21
* 笔名: 那年秋天的茄子^^
*/
@Slf4j
@Component
public class SchedulerManager {
@Autowired
private TaskScheduler threadPoolTaskScheduler;
@Autowired
private TimerService timerService;
/*定义定时列表集合*/
private ConcurrentHashMap<String, TimerEntity> timers = new ConcurrentHashMap<>();
@PostConstruct
private void run(){
/*启动运行,先保存定时*/
createTimers();
ConcurrentHashMap.KeySetView<String, TimerEntity> keySet = timers.keySet();
for (String key : keySet) {
TimerEntity timerEntity = timers.get(key);
if(timerEntity.isStart()){
startTimer(timerEntity);
}
}
}
/**
* 启动扫描所有定时任务
* 作者: 吴 波
* 时间: 2020-03-26 18:43
* 笔名: 那年秋天的茄子^^
*/
private void createTimers() {
List<TimerBean> timerBeans = timerService.getAll();
for (TimerBean timerBean : timerBeans) {
TimerEntity timerEntity=new TimerEntity();
timerEntity.setTimerBean(timerBean);
timers.put(timerBean.getId(), timerEntity);
}
}
/**
* 启动定时
* 作者: 吴 波
* 时间: 2020-03-26 19:16
* 笔名: 那年秋天的茄子^^
*/
public ResBean startTimer(TimerEntity timerEntity) {
/*获取容器中的Timer*/
TimerEntity timer = this.timers.get(timerEntity.getId());
/*如果容器中有对应的Timer并且具有执行的任务则释放任务*/
if(timer!=null && timer.getFuture()!=null){
timer.getFuture().cancel(true);
}
/*查看当前容器中没有,并且当前任务不需要执行,之间返回*/
if(timer!=null && !timerEntity.isStart()){
this.timers.remove(timerEntity.getId());
return ResBean.success();
}
final String className=timerEntity.getClassName();
final String methodName=timerEntity.getMethodName();
final String timerName=timerEntity.getTimerName();
final String cron=timerEntity.getCron();
ScheduledFuture<?> future ;
AtomicBoolean flag = new AtomicBoolean(true);
future = threadPoolTaskScheduler.schedule(() -> {
try {
Object timerObjBean = AppContextUtils.getContext().getBean(className);
Method runMethod = timerObjBean.getClass().getMethod(methodName);
if (Modifier.isPublic(runMethod.getModifiers())) {
long startTime = System.currentTimeMillis();
log.info("\n[定时任务-{}]:[正在运行...]",timerName);
try {
runMethod.invoke(timerObjBean);
} catch (Exception e) {
log.error("\n[定时任务-{}]:[运行出现异常..]",timerName,e);
}
long endTime = System.currentTimeMillis();
log.info("\n[定时任务-{}]-[运行结束...]\n[消耗时间={}毫秒]",timerName,endTime-startTime);
} else {
flag.set(false);
}
} catch (Exception e) {
log.error("创建定时任务失败", e);
flag.set(false);
}
}, triggerContext -> {
if (StringUtils.isBlank(cron)) {
return null;
}
/*定时任务触发,可修改定时任务的执行周期*/
CronTrigger trigger = new CronTrigger(cron);
return trigger.nextExecutionTime(triggerContext);
});
/*如果创建失败,则清除当前执行线程,并且将当前定时线程排除*/
if(!flag.get()){
if(future!=null){
future.cancel(true);
}
return ResBean.failed("定时创建出现问题!");
}
timerEntity.setFuture(future);
this.timers.put(timerEntity.getId(),timerEntity);
log.info("\n[自动化定时任务:\n{}-{}-启动成功...]",timerName,cron);
return ResBean.success();
}
}
定时实体 TimerEntity
package com.itechhero.app.module.config.scheduler;
import com.itechhero.app.module.edu.timer.model.TimerBean;
import lombok.Data;
import java.io.Serializable;
import java.util.concurrent.ScheduledFuture;
@Data
public class TimerEntity implements Serializable {
private TimerBean timerBean;
private ScheduledFuture<?> future;
public ScheduledFuture<?> getFuture() {
return future;
}
public void setFuture(ScheduledFuture<?> future) {
this.future = future;
}
public boolean isStart(){
return this.timerBean.isStart();
}
/**
* 获取唯一标识
*
* @return ID - 唯一标识
*/
public String getId() {
return this.timerBean.getId();
}
/**
* 获取定时名称
*
* @return TIMER_NAME - 定时名称
*/
public String getTimerName() {
return this.timerBean.getTimerName();
}
/**
* 获取描述
*
* @return DESCRIBE - 描述
*/
public String getDescribe() {
return this.timerBean.getDescribe();
}
/**
* 获取服务类名
*
* @return CLASS_NAME - 服务类名
*/
public String getClassName() {
return this.timerBean.getClassName();
}
/**
* 获取函数名称
*
* @return METHOD_NAME - 函数名称
*/
public String getMethodName() {
return this.timerBean.getMethodName();
}
/**
* 获取定时表达式
*
* @return CRON - 定时表达式
*/
public String getCron() {
return this.timerBean.getCron();
}
}
数据库实体 TimerBean
package com.itechhero.app.module.edu.timer.model;
import lombok.ToString;
import javax.persistence.*;
import java.io.Serializable;
@Table(name = "YJ_TIMER")
@ToString
public class TimerBean implements Serializable {
/**
* 唯一标识
*/
@Id
@Column(name = "ID")
@GeneratedValue(strategy = GenerationType.IDENTITY)
private String id;
/**
* 定时名称
*/
@Column(name = "TIMER_NAME")
private String timerName;
/**
* 描述
*/
@Column(name = "DESCRIBE")
private String describe;
/**
* 服务类名
*/
@Column(name = "CLASS_NAME")
private String className;
/**
* 函数名称
*/
@Column(name = "METHOD_NAME")
private String methodName;
/**
* 定时表达式
*/
@Column(name = "CRON")
private String cron;
/**
* 是否启用
*/
@Column(name = "IF_START")
private Integer ifStart;
/**
* 获取唯一标识
*
* @return ID - 唯一标识
*/
public String getId() {
return id;
}
/**
* 设置唯一标识
*
* @param id 唯一标识
*/
public void setId(String id) {
this.id = id == null ? null : id.trim();
}
/**
* 获取定时名称
*
* @return TIMER_NAME - 定时名称
*/
public String getTimerName() {
return timerName;
}
/**
* 设置定时名称
*
* @param timerName 定时名称
*/
public void setTimerName(String timerName) {
this.timerName = timerName == null ? null : timerName.trim();
}
/**
* 获取描述
*
* @return DESCRIBE - 描述
*/
public String getDescribe() {
return describe;
}
/**
* 设置描述
*
* @param describe 描述
*/
public void setDescribe(String describe) {
this.describe = describe == null ? null : describe.trim();
}
/**
* 获取服务类名
*
* @return CLASS_NAME - 服务类名
*/
public String getClassName() {
return className;
}
/**
* 设置服务类名
*
* @param className 服务类名
*/
public void setClassName(String className) {
this.className = className == null ? null : className.trim();
}
/**
* 获取函数名称
*
* @return METHOD_NAME - 函数名称
*/
public String getMethodName() {
return methodName;
}
/**
* 设置函数名称
*
* @param methodName 函数名称
*/
public void setMethodName(String methodName) {
this.methodName = methodName == null ? null : methodName.trim();
}
/**
* 获取定时表达式
*
* @return CRON - 定时表达式
*/
public String getCron() {
return cron;
}
/**
* 设置定时表达式
*
* @param cron 定时表达式
*/
public void setCron(String cron) {
this.cron = cron == null ? null : cron.trim();
}
/**
* 获取是否启用
*
* @return IF_START - 是否启用
*/
public Integer getIfStart() {
return ifStart;
}
/*工具方法*/
public Boolean isStart(){
return this.ifStart==1;
}
/**
* 设置是否启用
*
* @param ifStart 是否启用
*/
public void setIfStart(Integer ifStart) {
this.ifStart = ifStart;
}
}
定时Mapper
package com.itechhero.app.module.edu.timer.mapper;
import com.itechhero.app.module.common.utils.req.CMap;
import com.itechhero.app.module.config.mybatis.base.BaseMapper;
import com.itechhero.app.module.edu.timer.model.TimerBean;
import org.apache.ibatis.annotations.Param;
import java.util.List;
public interface TimerBeanMapper extends BaseMapper<TimerBean> {
/**
* 获取分页定时任务
* 作者: 吴 波
* 时间: 2020-03-26 22:08
* 笔名: 那年秋天的茄子^^
*/
List<CMap> getTimers(@Param("param") CMap param);
}
定时Mapper xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.itechhero.app.module.edu.timer.mapper.TimerBeanMapper">
<resultMap id="BaseResultMap" type="com.itechhero.app.module.edu.timer.model.TimerBean">
<id column="ID" jdbcType="VARCHAR" property="id" />
<result column="TIMER_NAME" jdbcType="VARCHAR" property="timerName" />
<result column="DESCRIBE" jdbcType="VARCHAR" property="describe" />
<result column="CLASS_NAME" jdbcType="VARCHAR" property="className" />
<result column="METHOD_NAME" jdbcType="VARCHAR" property="methodName" />
<result column="CRON" jdbcType="VARCHAR" property="cron" />
<result column="IF_START" jdbcType="DECIMAL" property="ifStart" />
</resultMap>
<select id="getTimers" resultType="com.itechhero.app.module.common.utils.req.CMap">
select * from YJ_TIMER
where 1 = 1
<if test="param.className!=''">
AND CLASS_NAME LIKE '%'||#{param.className}||'%'
</if>
<if test="param.methodName!=''">
AND METHOD_NAME LIKE '%'||#{param.methodName}||'%'
</if>
<if test="param.timerName!=''">
AND TIMER_NAME LIKE '%'||#{param.timerName}||'%'
</if>
</select>
</mapper>
定时Service
package com.itechhero.app.module.edu.timer.service;
import com.fasterxml.jackson.core.type.TypeReference;
import com.github.pagehelper.PageHelper;
import com.itechhero.app.module.common.utils.exceptions.ReqException;
import com.itechhero.app.module.common.utils.req.BodyParam;
import com.itechhero.app.module.common.utils.req.CMap;
import com.itechhero.app.module.common.utils.req.PageData;
import com.itechhero.app.module.common.utils.req.ResBean;
import com.itechhero.app.module.config.scheduler.SchedulerManager;
import com.itechhero.app.module.config.scheduler.TimerEntity;
import com.itechhero.app.module.edu.timer.mapper.TimerBeanMapper;
import com.itechhero.app.module.edu.timer.model.TimerBean;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
@Slf4j
@Service
@Transactional
public class TimerService {
@Autowired
private TimerBeanMapper timerBeanMapper;
@Autowired
private SchedulerManager schedulerManager;
/*获取全部定时列表*/
public List<TimerBean> getAll() {
return this.timerBeanMapper.selectAll();
}
/*关闭定时*/
public void closeTimer(String timerId) {
TimerBean timerBean = this.timerBeanMapper.selectByPrimaryKey(timerId);
timerBean.setIfStart(0);
TimerEntity timerEntity = new TimerEntity();
timerEntity.setTimerBean(timerBean);
this.schedulerManager.startTimer(timerEntity);
this.timerBeanMapper.updateByPrimaryKeySelective(timerBean);
}
/*开启定时*/
public void startTimer(String timerId) {
TimerBean timerBean = new TimerBean();
timerBean.setId(timerId);
timerBean.setIfStart(1);
this.timerBeanMapper.updateByPrimaryKeySelective(timerBean);
}
/*测试定时任务*/
@Transactional(readOnly = true)
public void run() {
//List<TimerBean> all = this.getAll();
log.info("定时任务已经开启:\n");
}
/**
* 分页获取定时列表
* 作者: 吴 波
* 时间: 2020-03-26 22:05
* 笔名: 那年秋天的茄子^^
*/
public PageData getTimers(CMap param) {
PageHelper.startPage(param.getPageNum(), param.getPageSize());
List<CMap> list = this.timerBeanMapper.getTimers(param);
return PageData.builder("获取到分页定时", list);
}
/**
* 修改定时任务
* 作者: 吴 波
* 时间: 2020-03-26 22:15
* 笔名: 那年秋天的茄子^^
*/
@Transactional
public ResBean update(BodyParam bodyParam) {
TimerBean updateTimerBean = new TimerBean();
updateTimerBean.setId(bodyParam.getValue("id"));
updateTimerBean.setTimerName(bodyParam.getValue("timerName"));
updateTimerBean.setDescribe(bodyParam.getValue("describe"));
updateTimerBean.setClassName(bodyParam.getValue("className"));
updateTimerBean.setMethodName(bodyParam.getValue("methodName"));
updateTimerBean.setCron(bodyParam.getValue("cron"));
updateTimerBean.setIfStart(bodyParam.getInteger("ifStart"));
this.timerBeanMapper.updateByPrimaryKeySelective(updateTimerBean);
TimerBean reStartBean = this.timerBeanMapper.selectByPrimaryKey(updateTimerBean.getId());
TimerEntity timerEntity = new TimerEntity();
timerEntity.setTimerBean(reStartBean);
ResBean resBean = schedulerManager.startTimer(timerEntity);
if (resBean.isSuccess()) {
return resBean;
}
throw ReqException.builder("定时修改失败");
}
/**
* 添加定时任务
* 作者: 吴 波
* 时间: 2020-03-26 22:20
* 笔名: 那年秋天的茄子^^
*/
public ResBean save(BodyParam bodyParam) {
TimerBean timerBean = bodyParam.getBeanByTypeReference(new TypeReference<TimerBean>() {
});
this.timerBeanMapper.insertSelective(timerBean);
TimerEntity timerEntity = new TimerEntity();
timerEntity.setTimerBean(timerBean);
ResBean resBean = schedulerManager.startTimer(timerEntity);
if (resBean.isSuccess()) {
return resBean;
}
throw ReqException.builder("定时修改失败");
}
/**
* 删除定时
* 作者: 吴 波
* 时间: 2020-03-27 1:35
* 笔名: 那年秋天的茄子^^
*/
public ResBean del(String id) {
TimerBean timerBean = this.timerBeanMapper.selectByPrimaryKey(id);
timerBean.setIfStart(0);
TimerEntity timerEntity = new TimerEntity();
timerEntity.setTimerBean(timerBean);
this.schedulerManager.startTimer(timerEntity);
this.timerBeanMapper.deleteByPrimaryKey(id);
return ResBean.success();
}
}
数据库SQL
drop table YJ_TIMER cascade constraints;
/*==============================================================*/
/* Table: YJ_TIMER */
/*==============================================================*/
create table YJ_TIMER
(
ID VARCHAR(32) not null,
TIMER_NAME VARCHAR(80),
DESCRIBE VARCHAR(300),
CLASS_NAME VARCHAR(50),
METHOD_NAME VARCHAR(60),
CRON VARCHAR(100),
IF_START INT,
constraint PK_YJ_TIMER primary key (ID)
);
comment on table YJ_TIMER is
'定时管理表';
comment on column YJ_TIMER.ID is
'唯一标识';
comment on column YJ_TIMER.TIMER_NAME is
'定时名称';
comment on column YJ_TIMER.DESCRIBE is
'描述';
comment on column YJ_TIMER.CLASS_NAME is
'服务类名';
comment on column YJ_TIMER.METHOD_NAME is
'函数名称';
comment on column YJ_TIMER.CRON is
'定时表达式';
comment on column YJ_TIMER.IF_START is
'是否启用';