一、定时任务的实现
1、配置文件方式
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
<bean id="userTimer" class="com.kevin.timer.XmlTimer"></bean>
<bean id="userTask"
class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">
<property name="targetObject">
<ref bean="userTimer" />
</property>
<property name="targetMethod">
<value>execute</value>
</property>
</bean>
<!-- 每隔5秒钟执行一次,我要在运行时动态修改为每小时执行一次 -->
<bean id="userTrigger" class="org.springframework.scheduling.quartz.CronTriggerFactoryBean">
<property name="jobDetail">
<ref bean="userTask" />
</property>
<property name="cronExpression">
<value>0/5 * * * * ?</value>
</property>
</bean>
<bean id="startQuertz" lazy-init="false" autowire="no" class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
<property name="triggers">
<list>
<ref bean="userTrigger" />
</list>
</property>
</bean>
</beans>
2、注解方式
我最近经常基于spring boot写定时任务,并且是使用注解的方式进行实现,分成的方便将自己的类注入spring容器(@conponent等),
并在要实现父方法上添加注解 @Scheduled(cron="0/10 * * * * ?") ,告知任务调度器质性方法中的业务逻辑的质性时间。
二、任务调用的工作原理
三、动态任务调用的实现
首先定义任务实体应该具有的属性:
package com.kevin.schedule.entity;
/**
* 调度任务定义
* @author Tom
*
*/
public class Task {
private String id; //任务ID,默认系统时间戳
private String parentId = ""; //父级任务ID
private String name = ""; //任务名称
private String desc = ""; //任务描述
private int planExe = 0; //计划执行次数,默认为0,表示满足条件循环执行
private String group = ""; //任务组名称
private String groupDesc = ""; //任务组描述
private String cron = ""; //任务表达式
private String cronDesc = ""; //表达式描述
private String trigger = ""; //触发器
private String triggerDesc = "";//触发器描述
private int execute = 0; //任务被执行过多少次
private Long lastExeTime = 0L; //最后一次开始执行时间
private Long lastFinishTime = 0L;//最后一次执行完成时间
private int state = 1; //任务状态0禁用、1启动、2删除
private int deply = 0; //延时启动,默认为0,表示不延时启动
public Task(String taskId){
this.id = taskId;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getParentId() {
return parentId;
}
public void setParentId(String parentId) {
this.parentId = parentId;
}
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 String getCronDesc() {
return cronDesc;
}
public void setCronDesc(String cronDesc) {
this.cronDesc = cronDesc;
}
// public List<ScheduleJob> getChildren() {
// return children;
// }
// public void setChildren(List<ScheduleJob> children) {
// this.children = children;
// }
public int getExecute() {
return execute;
}
public void setExecute(int execute) {
this.execute = execute;
}
public String getDesc() {
return desc;
}
public void setDesc(String desc) {
this.desc = desc;
}
public String getGroup() {
return group;
}
public void setGroup(String group) {
this.group = group;
}
public String getGroupDesc() {
return groupDesc;
}
public void setGroupDesc(String groupDesc) {
this.groupDesc = groupDesc;
}
public int getState() {
return state;
}
public void setState(int state) {
this.state = state;
}
public Long getLastExeTime() {
return lastExeTime;
}
public void setLastExeTime(Long lastExeTime) {
this.lastExeTime = lastExeTime;
}
public String getTrigger() {
return trigger;
}
public void setTrigger(String trigger) {
this.trigger = trigger;
}
public String getTriggerDesc() {
return triggerDesc;
}
public void setTriggerDesc(String triggerDesc) {
this.triggerDesc = triggerDesc;
}
public int getDeply() {
return deply;
}
public void setDeply(int deply) {
this.deply = deply;
}
public int getPlanExe() {
return planExe;
}
public void setPlanExe(int planExe) {
this.planExe = planExe;
}
// public String getTriggerGroup() {
// return triggerGroup;
// }
// public void setTriggerGroup(String triggerGroup) {
// this.triggerGroup = triggerGroup;
// }
// public String getTriggerGroupDesc() {
// return triggerGroupDesc;
// }
// public void setTriggerGroupDesc(String triggerGroupDesc) {
// this.triggerGroupDesc = triggerGroupDesc;
// }
public Long getLastFinishTime() {
return lastFinishTime;
}
public void setLastFinishTime(Long lastFinishTime) {
this.lastFinishTime = lastFinishTime;
}
}
package com.kevin.schedule.proxy;
import java.lang.reflect.Method;
import org.quartz.Job;
import org.quartz.JobDataMap;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import com.gupaoedu.schedule.entity.Task;
/**
*
* @author Tom
*
*/
public class TriggerProxy implements Job{
public static final String DATA_TARGET_KEY = "target"; //目标对象,实例
public static final String DATA_TRIGGER_KEY = "trigger"; //方法名
public static final String DATA_TRIGGER_PARAMS_KEY = "trigger_params";//方法的参数值
public static final String DATA_TASK_KEY = "task"; //自己封装的任务对象
private ThreadLocal<Entry> local = new ThreadLocal<Entry>();
//是由调度器自动调用的
public void execute(JobExecutionContext context) throws JobExecutionException {
// TriggerProxy.class.getResource("")
try {
local.set(new Entry());
//获取参数信息
JobDataMap data = context.getTrigger().getJobDataMap();
Object target = data.get(DATA_TARGET_KEY);
Method method = (Method)data.get(DATA_TRIGGER_KEY);
Object[] params = (Object[])data.get(DATA_TRIGGER_PARAMS_KEY);
//修改任务执行次数
Task task = (Task)data.get(DATA_TASK_KEY);
//任务没执行一次,需要累加1
task.setExecute(task.getExecute() + 1);
local.get().start = System.currentTimeMillis();
//调用触发器,用反射调用我们自己定义的方法
method.invoke(target,params);
local.get().end = System.currentTimeMillis();
//记录任务的最后一次执行时间
task.setLastExeTime(local.get().start);
//记录任务完成的时间
task.setLastFinishTime(local.get().end);
} catch (Exception e) {
e.printStackTrace();
}
}
class Entry{
public long start = 0L;
public long end = 0L;
}
}
@Component
public class AnnotationTimer {
Logger LOG = Logger.getLogger(this.getClass());
@Scheduled(cron="0/10 * * * * ?")
@Async
public void a(){
LOG.info("annotation配置的任务执行");
}
}
定义动态任务调度需要的方法,在web项目中,通过界面的方式调用方法以动态控制和查看任务调度的执行情况:
package com.kevin.schedule.service;
import java.util.List;
import com.gupaoedu.schedule.entity.Task;
public interface IScheduleService {
/**
* 获取任务列表
* @return
*/
public List<Task> getAllTask();
/**
* 根据任务ID获取一个任务
* @return
*/
public Task getTask(String taskId);
/**
* 新建一个任务
* @param taskName 任务名称
* @param taskClassName 任务Class名称
* @param triggerName 触发器名称
* @param cron 执行表达式
* @throws Exception
*/
public Task createTask(String taskName,String taskClassName,String triggerName,String cron) throws Exception;
/**
* 修改一个任务的触发时间(使用默认的任务组名,触发器名,触发器组名)
*
*/
public Task modifyTaskCron(String taskId, String cron);
/**
* 移除一个任务(使用默认的任务组名,触发器名,触发器组名)
*
*/
public Task removeTask(String taskId);
/**
* 重启任务
* @param taskId
* @return
*/
public Task restartTask(String taskId);
/**
* 暂停定时任务
* @param taskId
* @return
*/
public Task pauseTask(String taskId);
/**
* 关闭定时任务
* @param taskId
* @return
*/
public Task shutdownTask(String taskId);
/**
* 启动所有定时任务
*
*/
public void startAllTask();
/**
* 关闭所有定时任务
*/
public void shutdownAllTask();
}
实现方法:
package com.kevin.schedule.service.impl;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import org.quartz.CronTrigger;
import org.quartz.JobDetail;
import org.quartz.Scheduler;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.scheduling.quartz.SchedulerFactoryBean;
import org.springframework.stereotype.Service;
import com.gupaoedu.schedule.entity.Task;
import com.gupaoedu.schedule.proxy.TriggerProxy;
import com.gupaoedu.schedule.service.IScheduleService;
/**
* 任务管理,负责输出和管理日志
* @author Tom
*/
@Service
public class ScheduleService implements IScheduleService,ApplicationContextAware{
//自己不能再重新搞一个工厂出来,必须和Spring引用的调度工厂是同一个
//才能实现无缝集成,而且可以动态修改配置文件已经配置好的任务
@Autowired private SchedulerFactoryBean schedulerFactory;
//保存所有任务表达式的描述
private Map<String,String> cronDesc = new LinkedHashMap<String,String>();
//所有的任务都放到任务池中
private static Map<String,Task> taskPool = new LinkedHashMap<String,Task>();
private static ApplicationContext app;
public ScheduleService(){}
/**
* 创建一个调度任务
* @param m
* @return
* @throws Exception
*/
private Task createTask(Method m) throws Exception{
//任务ID就是时间戳,再加上一个随机数
Task task = new Task("" + System.currentTimeMillis());
//通过方法,可以获取到方法所在的class
Class clazz = m.getDeclaringClass();
//设置任务组
task.setGroup(clazz.getName());
//设置触发器信息
//一个触发器对应一个方法,在此处,所谓的触发器还只是一个概念
//概念是一个字符描述,它记录了触发的目标信息
task.setTrigger(clazz.getName() + "." + m.getName());
Annotation sc = m.getAnnotation(Scheduled.class);
Method cronM = sc.getClass().getMethod("cron",null);
String cron = cronM.invoke(sc, null).toString();
task.setTriggerDesc(cronDesc.get(cron));
task.setCron(cron);
task.setCronDesc(cronDesc.get(cron));
//此时,就已经完成一个task的封装
return task;
}
@Override
public void setApplicationContext(ApplicationContext app) throws BeansException {
this.app = app;
//加载所有任务到任务队列
for(String name : app.getBeanDefinitionNames()){
try{
Class<?> c = app.getBean(name).getClass();
for(Method m : c.getMethods()){
//只要是添加了Scheduled注解的都给他添加到调度工厂中
if(m.isAnnotationPresent(Scheduled.class)){
Task task = createTask(m);
//将任务加入队列准备启动
createTask(task);
}
}
}catch(Exception e){
continue;
}
}
}
public Task getTask(String taskId) {
return taskPool.get(taskId);
}
@Override
public List<Task> getAllTask() {
if(taskPool.size() == 0){
return new ArrayList<Task>();
}
List<Task> r = new ArrayList<Task>();
r.addAll(taskPool.values());
return r;
}
private Task createTask(Task task) throws Exception{
if(null == task.getGroup() || task.getGroup().trim().length() == 0){ return null; }
//还是拿到字节码
Class<?> clazz = Class.forName(task.getGroup());
//先从容器中获取
Object target = null;
try{
//从Spring容器中提取已经创建好的对象引用
target = app.getBean(clazz);
}catch(Exception e){
}
//如果Spring容器没有帮我们创建,那么就自己创建实例
if(target == null){
target = clazz.newInstance();
}
//把触发器需要调用的方法找出来,还是用反射
Method m = clazz.getMethod(task.getTrigger().replaceAll(task.getGroup() + ".", ""));
//把任务ID取出来,时间戳
String taskId = task.getId();
//================ 事前准备 ====================
//拿到Quartz中的调度器
Scheduler sched = schedulerFactory.getScheduler();
//创建一个Detail
JobDetail taskDetail = new JobDetail(taskId, task.getGroup(), TriggerProxy.class);// 任务名,任务组,任务执行类
// 触发器
CronTrigger trigger = new CronTrigger(taskId, task.getTrigger());// 触发器名,触发器组
//在这里设置CronExpression表达式
trigger.setCronExpression(task.getCron());// 触发器时间设定
//JobDataMap 用来存储附加信息
//利用这么一个API,把自定义的信息添加到Map中
trigger.getJobDataMap().put(TriggerProxy.DATA_TARGET_KEY, target);
trigger.getJobDataMap().put(TriggerProxy.DATA_TRIGGER_KEY, m);
// m.getParameterTypes()
trigger.getJobDataMap().put(TriggerProxy.DATA_TRIGGER_PARAMS_KEY, new Object[]{});
trigger.getJobDataMap().put(TriggerProxy.DATA_TASK_KEY, task);
sched.scheduleJob(taskDetail, trigger);
// 如果这个任务没有被主动关闭,我们就给他启动
if (!sched.isShutdown()) {
sched.start();
}
//放入我们的任务池
if(!taskPool.containsKey(taskId)){
taskPool.put(taskId, task);
}
return task;
}
/**
* 添加一个定时任务,使用默认的任务组名,触发器名,触发器组名
*/
public Task createTask(String taskName,String taskClassName,String triggerName,String cron) throws Exception{
return createTask(taskName,null,taskClassName,null,triggerName,cron);
}
/**
* 添加一个定时任务
*/
private Task createTask(String taskName, String taskGroupName, String taskClassName,String triggerGroupName, String triggerName,String cron) throws Exception{
//根据类名,利用反射机制获取到类的字节码
//约定优于配置
Class<?> clazz = Class.forName(taskClassName); //就是类名全程,包名.类名
Method m = clazz.getMethod(triggerName); //显然就是方法名
Task task = createTask(m);
task.setName(taskName);
if(null != taskGroupName){
task.setGroup(taskGroupName);
}
task.setCron(cron);
return createTask(task);
}
/**
* 修改一个任务的触发时间(使用默认的任务组名,触发器名,触发器组名)
*
*/
public Task modifyTaskCron(String taskId, String cron) {
Task task = taskPool.get(taskId);
try {
Scheduler sched = schedulerFactory.getScheduler();
CronTrigger trigger = (CronTrigger) sched.getTrigger(taskId,task.getTrigger());
String oldTime = trigger.getCronExpression();
if (!oldTime.equalsIgnoreCase(cron)) {
JobDetail taskDetail = sched.getJobDetail(taskId,task.getGroup());
Class objJobClass = taskDetail.getJobClass();
removeTask(taskId);
//重新生成ID
task.setId("" + System.currentTimeMillis());
task.setCron(cron);
createTask(task);
}
} catch (Exception e) {
throw new RuntimeException(e);
}
return task;
}
/**
* 移除一个任务(使用默认的任务组名,触发器名,触发器组名)
*
*/
public Task removeTask(String taskId) {
Task task = taskPool.get(taskId);
try {
Scheduler sched = schedulerFactory.getScheduler();
sched.pauseTrigger(taskId, task.getTrigger());// 停止触发器
sched.unscheduleJob(taskId, task.getGroup());// 移除触发器
sched.deleteJob(taskId, task.getGroup());// 删除任务
taskPool.remove(taskId);
} catch (Exception e) {
throw new RuntimeException(e);
}
return task;
}
/**
* 暂停任务
* @param taskId
*/
public Task pauseTask(String taskId){
Task task = taskPool.get(taskId);
try {
Scheduler sched = schedulerFactory.getScheduler();
sched.pauseTrigger(task.getId(), task.getTrigger());// 停止触发器
} catch (Exception e) {
throw new RuntimeException(e);
}
return task;
}
/**
*
*
*/
public Task restartTask(String taskId) {
Task task = taskPool.get(taskId);
try {
Scheduler sched = schedulerFactory.getScheduler();
// 重启触发器
sched.resumeTrigger(task.getId(),task.getTrigger());
} catch (Exception e) {
throw new RuntimeException(e);
}
return task;
}
/**
* 关闭任务
* @param taskId
*/
public Task shutdownTask(String taskId){
Task task = taskPool.get(taskId);
try {
Scheduler sched = schedulerFactory.getScheduler();
sched.pauseTrigger(taskId, task.getTrigger());// 停止触发器
sched.unscheduleJob(taskId, task.getGroup());// 移除触发器
sched.deleteJob(taskId, task.getGroup());// 删除任务
} catch (Exception e) {
throw new RuntimeException(e);
}
return task;
}
/**
* 启动所有定时任务
*
*/
public void startAllTask() {
try {
Scheduler sched = schedulerFactory.getScheduler();
sched.start();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* 关闭所有定时任务
*/
public void shutdownAllTask() {
try {
Scheduler sched = schedulerFactory.getScheduler();
if (!sched.isShutdown()) {
sched.shutdown();
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}