本篇文章在《Spring Scheduled + Redis 实现分布式定时器(一)》上演变!
**用户只需要控制ky_task_details和ky_trigger_details两张表即可。 心跳定时器会帮我们自动触发相应的动作;
当然,如果不想使用心跳检测的方式,也可以用提供接口的方式。每次修改ky_task_details信息时,主动去调用一下接口。**
创建mysql表语句:
CREATE TABLE `ky_task_details` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`taskName` varchar(255) COLLATE utf8_bin NOT NULL,
`beanName` varchar(255) COLLATE utf8_bin NOT NULL,
`state` int(1) NOT NULL,
`triggerId` int(11) unsigned zerofill NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=29 DEFAULT CHARSET=utf8 COLLATE=utf8_bin;
CREATE TABLE `ky_trigger_details` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(255) COLLATE utf8_bin NOT NULL,
`cron` varchar(255) COLLATE utf8_bin NOT NULL,
`state` varchar(255) COLLATE utf8_bin NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8 COLLATE=utf8_bin;
接下来分解代码:(借鉴代码的时候可能要根据项目实际情况做些改动)
KyScheduled:新增name和synclock属性;
@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface KyScheduled {
/**
* A cron-like expression, extending the usual UN*X definition to include
* triggers on the second as well as minute, hour, day of month, month
* and day of week. e.g. {@code "0 * * * * MON-FRI"} means once per minute on
* weekdays (at the top of the minute - the 0th second).
* @return an expression that can be parsed to a cron schedule
*/
String cron() default "";
/**
* 自定义的定时器任务名称,为空时使用对应的方法名作为任务名;
* @return ""
*/
String name() default "";
/**
* 是否使用同步锁, 默认:使用
* @return
*/
boolean synclock() default true;
}
KyScheduledFuture:
public class KyScheduledFuture {
private String cron;
private ScheduledFuture> future;
public KyScheduledFuture(String cron, ScheduledFuture> future) {
this.cron = cron;
this.future = future;
}
public String getCron() {
return cron;
}
public void setCron(String cron) {
this.cron = cron;
}
public java.util.concurrent.ScheduledFuture> getFuture() {
return future;
}
public void setFuture(java.util.concurrent.ScheduledFuture> future) {
this.future = future;
}
}
KyTaskDetails:与数据库ky_task_details表关联的实体类;
public class KyTaskDetails {
private long id; //id
private String taskName; //用户自定义的任务名
private String beanName; //redis的lockName, 也是唯一值
private KyTrigger trigger; //触发器
private KyScheduledExecution.Job job; //任务
private int state = 2; //当前任务是否正在执行。1:执行,2:未执行
private long triggerId;//触发器id
private KyTriggerDetails triggerDetails;
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getTaskName() {
return taskName;
}
public void setTaskName(String taskName) {
this.taskName = taskName;
}
public String getBeanName() {
return beanName;
}
public void setBeanName(String beanName) {
this.beanName = beanName;
}
public int getState() {
return state;
}
public void setState(int state) {
this.state = state;
}
public long getTriggerId() {
return triggerId;
}
public void setTriggerId(long triggerId) {
this.triggerId = triggerId;
}
public KyTrigger getTrigger() {
return trigger;
}
public void setTrigger(KyTrigger trigger) {
this.trigger = trigger;
}
public KyScheduledExecution.Job getJob() {
return job;
}
public void setJob(KyScheduledExecution.Job job) {
this.job = job;
}
public KyTriggerDetails getTriggerDetails() {
return triggerDetails;
}
public void setTriggerDetails(KyTriggerDetails triggerDetails) {
this.triggerDetails = triggerDetails;
}
}
KyTriggerDetails:与数据库ky_trigger_details表关联的实体类;
public class KyTriggerDetails {
private long id;
private String name;
private String cron;
private int state;
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getCron() {
return cron;
}
public void setCron(String cron) {
this.cron = cron;
}
public int getState() {
return state;
}
public void setState(int state) {
this.state = state;
}
}
KyTrigger:自定义触发器,记录自定义注解的属性信息和提供一个获取下一次执行任务时间的方法;
/**
* 自定义触发器对象
*/
public class KyTrigger implements Trigger, Serializable {
private String cron;
private boolean syncLock;
public KyTrigger(KyScheduled kyScheduled){
if(kyScheduled.cron() != null && !"".equals(kyScheduled.cron())) {
this.cron = kyScheduled.cron();
}
this.syncLock = kyScheduled.synclock();
}
public boolean getSyncLock(){
return this.syncLock;
}
public String getCron() {
return cron;
}
public void setCron(String cron) {
if(cron != null && !"".equals(cron)) {
this.cron = cron;
}
}
@Override
public Date nextExecutionTime(TriggerContext triggerContext) {
CronTrigger cronTrigger = new CronTrigger(this.cron);
return cronTrigger.nextExecutionTime(triggerContext);
}
}
KyScheduledController:持久化任务的控制器, 主要是记录当前正在运行的任务,以及任务的启动、停止、变更;
@Component
@EnableScheduling
public class KyScheduledController {
@Autowired
private KyScheduledExecution scheduledExecution;
@Autowired
private ThreadPoolTaskScheduler threadPoolTaskScheduler;
private Map futureMap = new HashMap<>();
@Bean
public ThreadPoolTaskScheduler KyScheduledController() {
return new ThreadPoolTaskScheduler();
}
/**
* 创建新定时器
* @param kyTaskDetails KyTaskDetails
* @return String
*/
public String startTask(KyTaskDetails kyTaskDetails) {
String beanName = kyTaskDetails.getBeanName();
if (futureMap.get(beanName) == null) {
KyTaskDetails kyTaskInfo = scheduledExecution.getTaskDetailMap().get(beanName);
if(kyTaskInfo != null) {
String cron = kyTaskInfo.getTrigger().getCron();
ScheduledFuture> future = threadPoolTaskScheduler.schedule(kyTaskInfo.getJob(), new CronTrigger(cron));
futureMap.put(kyTaskInfo.getBeanName(), new KyScheduledFuture(cron, future));
}
return "startTask";
}
return "";
}
/**
* 修改定时器
* @param kyTaskDetails KyTaskDetails
* @return String
*/
public String changeTask(KyTaskDetails kyTaskDetails) {
String beanName = kyTaskDetails.getBeanName();
String cron = kyTaskDetails.getTrigger().getCron();
stopTask(beanName);
if (futureMap.get(beanName) == null) {
KyTaskDetails kyTaskInfo = scheduledExecution.getTaskDetailMap().get(beanName);
kyTaskInfo.getTrigger().setCron(cron);
ScheduledFuture> future = threadPoolTaskScheduler.schedule(kyTaskInfo.getJob(), new CronTrigger(kyTaskInfo.getTrigger().getCron()));
futureMap.put(kyTaskInfo.getBeanName(), new KyScheduledFuture(cron, future));
return "changeTask";
}
return "";
}
/**
* 关闭定时器
* @param name String
* @return String
*/
public String stopTask(@PathVariable("name") String name) {
KyScheduledFuture kyFuture = futureMap.get(name);
if (kyFuture != null) {
kyFuture.getFuture().cancel(true);
futureMap.remove(name);
return "stopTask";
}
return "";
}
public Map getFutureMap() {
return futureMap;
}
}
HeartBeatJob:心跳检测任务不计入持久化范围,主要是监听数据库的变化和执行定时器操作;
@Component
public class HeartBeatJob {
@Autowired
private KyScheduledController scheduledController;
@Autowired
private KyScheduledExecution scheduledExecution;
@Autowired
private JdbcTemplate jdbcTemplate;
@KyScheduled(cron = "0/5 * * * * ?", name = "heartbeatCheck", synclock = false)
public void heartbeatCheck() {
Map futureMap = scheduledController.getFutureMap();
List dbTaskDetailsList = new ArrayList<>();
List
KyScheduledExecution:主要更变在configureTasks方法中,加入了创建持久化对象;
@Component
public class KyScheduledExecution implements BeanPostProcessor, SchedulingConfigurer, ApplicationContextAware {
private Log log = LogFactory.getLog(getClass());
@Value("${spring.redis.host}")
private String redisHost;
@Value("${spring.redis.port}")
private int redisPort;
@Autowired
private JdbcTemplate jdbcTemplate;
private Jedis jedis;
private Map taskDetailMap = new HashMap();
//记录任务集合
private List kyTaskList = new ArrayList<>();
private ApplicationContext applicationContext;
public Map getTaskDetailMap() {
return taskDetailMap;
}
/**
* 创建redis客户端
*/
private void createRedisClient() {
if (jedis == null) {
jedis = new Jedis(redisHost, redisPort);
}
}
@Override
public void setApplicationContext(ApplicationContext context) throws BeansException {
createRedisClient();
this.applicationContext = context;
}
private Object getBean(Class classname) {
try {
return this.applicationContext.getBean(classname);
} catch (Exception e) {
log.error(e);
return "";
}
}
public class KyTask {
private KyScheduled kyScheduled;
private Method kyMethod;
public KyScheduled getKyScheduled() {
return kyScheduled;
}
public void setKyScheduled(KyScheduled kyScheduled) {
this.kyScheduled = kyScheduled;
}
public Method getKyMethod() {
return kyMethod;
}
public void setKyMethod(Method kyMethod) {
this.kyMethod = kyMethod;
}
}
/**
* 任务对象
*/
public class Job implements Serializable, Runnable {
private Method method;
private String lockName;
private Object invokeMethod;
private KyTrigger trigger;
public String getLockName() {
return lockName;
}
Job(Method m, KyTrigger t) {
this.trigger = t;
this.invokeMethod = getBean(m.getDeclaringClass());//获取bean实例
this.lockName = m.getDeclaringClass().getName() + "." + m.getName();//构造LockName
this.method = m;
}
@Override
public void run() {
synchronized (this) {
if (this.trigger.getSyncLock()) {
//获取下次执行时间(秒)
long nextTime = (this.trigger.nextExecutionTime(new SimpleTriggerContext()).getTime() - new Date().getTime()) / 1000;
//抢占分布式锁
String result = setnxLock(this.lockName, (int) nextTime);
if (result != null && !"".equals(result)) {
try {
//执行自定义注解的方法
System.out.println("nexttime: " + nextTime);
this.method.invoke(this.invokeMethod);
} catch (IllegalAccessException | InvocationTargetException e) {
e.printStackTrace();
log.error(e);
}
}
} else {
try {
//执行自定义注解的方法
this.method.invoke(this.invokeMethod);
} catch (IllegalAccessException | InvocationTargetException e) {
e.printStackTrace();
log.error(e);
}
}
}
}
}
/**
* 配置定时器
*
* @param taskRegistrar ScheduledTaskRegistrar
*/
@Override
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
if (taskRegistrar != null) {
for (KyTask kt : kyTaskList) {
Method method = kt.getKyMethod();
//创建触发器
KyTrigger trigger = new KyTrigger(kt.getKyScheduled());
//创建任务
Job job = new Job(method, trigger);
//自动将心跳任务加入调度器中
if ("heartbeatCheck".equals(kt.getKyScheduled().name())) {
taskRegistrar.addTriggerTask(job, trigger);
} else {
String beanName = method.getDeclaringClass().getName() + "." + method.getName();
//创建任务对象
KyTaskDetails taskDetails = new KyTaskDetails();
taskDetails.setTaskName(!"".equals(kt.getKyScheduled().name()) ? kt.getKyScheduled().name() : method.getName());
taskDetails.setBeanName(beanName);
taskDetails.setTrigger(trigger);
taskDetails.setTriggerId(0L);
taskDetails.setJob(job);
//创建持久化对象
Map objectMap;
try {
objectMap = jdbcTemplate.queryForMap("select * from ky_task_details where beanName = '" + beanName + "'");
} catch (Exception ex) {
objectMap = null;
}
if(objectMap == null) {
//插入数据
String insertSql = "insert into ky_task_details (taskName, beanName, state, triggerId) values (?,?,?,?)";
KeyHolder keyHolder = new GeneratedKeyHolder();
jdbcTemplate.update(new PreparedStatementCreator() {
public PreparedStatement createPreparedStatement(
Connection connection) throws SQLException {
PreparedStatement ps = connection.prepareStatement(insertSql,
new String[] { "id" });
ps.setString(1, taskDetails.getTaskName());
ps.setString(2, taskDetails.getBeanName());
ps.setInt(3, taskDetails.getState());
ps.setLong(4, taskDetails.getTriggerId());
return ps;
}
}, keyHolder);
taskDetails.setId(keyHolder.getKey().intValue());
}
taskDetailMap.put(taskDetails.getBeanName(), taskDetails);
}
}
}
}
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
/**
* 获取所有自定义注解,并记录注解和方法的信息
*
* @param bean bean
* @param beanName beanName
* @return Object
* @throws BeansException BeansException
*/
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
Method[] methods = ReflectionUtils.getAllDeclaredMethods(bean.getClass());
if (methods != null) {
for (Method method : methods) {
KyScheduled annotation = AnnotationUtils.findAnnotation(method, KyScheduled.class);
if (annotation != null) {
KyTask at = new KyTask();
at.setKyScheduled(annotation);
at.setKyMethod(method);
kyTaskList.add(at);
}
}
}
return bean;
}
/**
* 获取分布式锁
*
* @param lockName 锁名称
* @param second 加锁时间(秒)
* @return 如果获取到锁,则返回lockId值,否则为null
*/
private String setnxLock(String lockName, int second) {
synchronized (this) {
//生成随机的Value值
String lockId = UUID.randomUUID().toString();
//抢占锁
Long lock = this.jedis.setnx(lockName, lockId);
if (lock == 1) {
//拿到Lock,设置超时时间
this.jedis.expire(lockName, second - 1);
return lockId;
}
return null;
}
}
}