在网上寻找了很多关于spring schedule的介绍博客,发现很多地方还是不够连贯,现在首先通过一个自己基于spring实现的定时任务,来带领大伙探讨一下spring schedule的那些事。
spring 版本3.1.1(相当古老的版本了)
package com.example.task;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.scheduling.TaskScheduler;
import org.springframework.scheduling.annotation.SchedulingConfigurer;
import org.springframework.scheduling.config.ScheduledTaskRegistrar;
import org.springframework.scheduling.support.ScheduledMethodRunnable;
import org.springframework.stereotype.Service;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ScheduledExecutorService;
/**
* Created by admin on 2018/6/28.
* 注入我们自己数据库配置的定时任务
*
*/
@Service
public class BaseTask implements ApplicationContextAware, ApplicationListener {
private static ApplicationContext applicationContext;
private static Logger log = LoggerFactory.getLogger(BaseTask.class);
/*@Autowired
private TaskDao taskDao;*/
/**
* @Description: 从数据库获取对应配置的定时任务
* @Param: []
* @return: java.util.Map
* @Author: chenshuai
* @Date: 2018/6/28
*/
private Map getCronTask(){
Map cronTasks = new HashMap();
try {
Object bean = applicationContext.getBean("你需要跑的定时任务的beanName");
Class cl = bean.getClass();
Method method = null;
method = cl.getMethod("你需要跑的定时任务的方法"),new Class[ 0 ]);
Runnable runnable = new ScheduledMethodRunnable(bean, method);
cronTasks.put(runnable, "你的cron表达式");
} catch (Exception e) {
e.printStackTrace();
log.error("==============BaseTask 初始化数据库任务列表失败=================");
log.error("==============JobId: 任务错误=================");
}
/*List taskList = taskDao.getAllOpenTask();
if(taskList ==null || taskList .size() ==0 ){
return cronTasks;
}可自己写从数据库获取数据的方法这里就不贴了
log.info("==============BaseTask 初始化数据库任务列表 Begin=================");
for (TaskDto taskDto: taskList) {
log.info("==============jobId:"+taskDto.getJobId()+",className:"+taskDto.getClassName()+",method:"+taskDto.getMethod()+"=================");
try {
Object bean = applicationContext.getBean(taskDto.getClassName());
Class cl = bean.getClass();
Method method = null;
method = cl.getMethod(taskDto.getMethod(),new Class[ 0 ]);
Runnable runnable = new ScheduledMethodRunnable(bean, method);
cronTasks.put(runnable, taskDto.getCron());
} catch (Exception e) {
e.printStackTrace();
log.error("==============BaseTask 初始化数据库任务列表失败=================");
log.error("==============JobId:"+taskDto.getJobId()+"任务错误=================");
}
}
log.info("==============BaseTask 初始化数据库任务列表 End=================");*/
return cronTasks;
}
@Override
public void onApplicationEvent(ContextRefreshedEvent event){
if(event.getApplicationContext() == this.applicationContext) {
{
//设置定时任务
Map cronTasks = getCronTask();
ScheduledTaskRegistrar registrar = null;
//初始化定时任务
if(!cronTasks.isEmpty()) {
registrar = new ScheduledTaskRegistrar();
registrar.setCronTasks(cronTasks);
//定时任务加入到线程池中执行
registrar.afterPropertiesSet();
}
}
}
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) {
this.applicationContext = applicationContext;
}
}
看到这里你可能还是有点懵,spring是如何处理定时任务,以及spring的定时任务如何定时运行的。
这里我得先和你说一说java自带的Executor框架了,下面先引一段来自《Java并发编程的艺术》中的图片以及解释了
下面是解释
Executor是一个接口,它是Executor框架的基础,它将任务的提交与任务的执行分离开来。
ThreadPoolExecutor是线程池的核心实现类,用来执行被提交的任务。
ScheduledThreadPoolExecutor是一个实现类,可以在给定的延迟后运行命令,或者定期执行命令。ScheduledThreadPoolExecutor比Timer更灵活,功能更强大。
Future接口和实现Future接口的FutureTask类,代表异步计算的结果。
Runnable接口和Callable接口的实现类,都可以被ThreadPoolExecutor或ScheduledThreadPoolExecutor执行。
上面我们自己是想的定时任务的代码中,核心的加入定时任务的代码如下
//初始化定时任务
if(!cronTasks.isEmpty()) {
registrar = new ScheduledTaskRegistrar();//创建线程执行的对象
registrar.setCronTasks(cronTasks);//设置线程
//定时任务加入到线程池中执行
registrar.afterPropertiesSet();//将定时任务放入到线程池中
}
spring schedule是基于java提供的ScheduledExecutorService中的schedule(Runnable command, long delay, TimeUnit unit)来实现的。
下面来贴几段源码
首先是上面我们自己写的基于spring schedule的定时任务,其中有一个核心的注册类ScheduledTaskRegistrar,里面提供了实现了一个InitializingBean接口的afterPropertiesSet方法,在这个方法中我们可以很清晰的看到它初始化以及设置定时任务的过程,
public void afterPropertiesSet() {
/*这里是当我们没指定线程执行对象(这个对象封装了一个线程池,使用这个线程池去跑我们的定时任务),会默认一个设置线程池*/
if(this.taskScheduler == null) {
this.localExecutor = Executors.newSingleThreadScheduledExecutor();
this.taskScheduler = new ConcurrentTaskScheduler(this.localExecutor);
}
Entry entry;
Iterator var2;
if(this.triggerTasks != null) {
var2 = this.triggerTasks.entrySet().iterator();
while(var2.hasNext()) {
entry = (Entry)var2.next();
this.scheduledFutures.add(this.taskScheduler.schedule((Runnable)entry.getKey(), (Trigger)entry.getValue()));
}
}
if(this.cronTasks != null) {
var2 = this.cronTasks.entrySet().iterator();
while(var2.hasNext()) {
entry = (Entry)var2.next();
/*这里是我们真正执行的方法,this.taskScheduler.schedule调用ConcurrentTaskScheduler.schedule方法去执行我们的定时任务*/
this.scheduledFutures.add(this.taskScheduler.schedule((Runnable)entry.getKey(), new CronTrigger((String)entry.getValue())));
}
}
if(this.fixedRateTasks != null) {
var2 = this.fixedRateTasks.entrySet().iterator();
while(var2.hasNext()) {
entry = (Entry)var2.next();
this.scheduledFutures.add(this.taskScheduler.scheduleAtFixedRate((Runnable)entry.getKey(), ((Long)entry.getValue()).longValue()));
}
}
if(this.fixedDelayTasks != null) {
var2 = this.fixedDelayTasks.entrySet().iterator();
while(var2.hasNext()) {
entry = (Entry)var2.next();
this.scheduledFutures.add(this.taskScheduler.scheduleWithFixedDelay((Runnable)entry.getKey(), ((Long)entry.getValue()).longValue()));
}
}
}
下面我们再来看看ConcurrentTaskScheduler 这个类,这个主要是用来执行我们的定时任务,并封装了一些自己的实现。
这里只贴部分核心源码
这个是构造方法:
//指定特定的线程池,用来执行定时任务的线程
public ConcurrentTaskScheduler(ScheduledExecutorService scheduledExecutor) {
super(scheduledExecutor);
this.setScheduledExecutor(scheduledExecutor);
}
下面是我们刚刚使用cron表达式的方法
public ScheduledFuture schedule(Runnable task, Trigger trigger) {
try {
ErrorHandler errorHandler = this.errorHandler != null?this.errorHandler:TaskUtils.getDefaultErrorHandler(true);
//这里是使用创建一个ReschedulingRunnable对象并使用schedule执行我们的任务
return (new ReschedulingRunnable(task, trigger, this.scheduledExecutor, errorHandler)).schedule();
} catch (RejectedExecutionException var4) {
throw new TaskRejectedException("Executor [" + this.scheduledExecutor + "] did not accept task: " + task, var4);
}
}
终于到最后面一步了,使用java提供的ScheduledExecutorService中的schedule方法来执行我们的定时任务
//构造函数,初始化定时任务
public ReschedulingRunnable(Runnable delegate, Trigger trigger, ScheduledExecutorService executor, ErrorHandler errorHandler) {
super(delegate, errorHandler);
this.trigger = trigger;
this.executor = executor;
}
//真正定时执行的方法
public ScheduledFuture schedule() {
Object var1 = this.triggerContextMonitor;//任务监听器
synchronized(this.triggerContextMonitor) {
//获取定时任务执行时间,通过监听器实时调用任务
this.scheduledExecutionTime = this.trigger.nextExecutionTime(this.triggerContext);
if(this.scheduledExecutionTime == null) {
return null;
} else {
long initialDelay = this.scheduledExecutionTime.getTime() - System.currentTimeMillis();
//schedule方法周期执行定时任务,传入this自身对象,通过excutor执行run方法实现递归调用
this.currentFuture = this.executor.schedule(this, initialDelay, TimeUnit.MILLISECONDS);
return this;
}
}
}
public void run() {
Date actualExecutionTime = new Date();
super.run();
Date completionTime = new Date();
Object var3 = this.triggerContextMonitor;
synchronized(this.triggerContextMonitor) {
this.triggerContext.update(this.scheduledExecutionTime, actualExecutionTime, completionTime);
}
//循环调用任务
if(!this.currentFuture.isCancelled()) {
this.schedule();
}
}
到这里我们终于把整个执行流程看完了。由于水平有限,有什么不对的地方还请各位在评论区评论了。spring是3.1.1的啊,高版本原理也差不太多,各位可以去翻一翻对应版本的spring注解方式是怎么实现这个定时任务的。
个人原创转载请备注链接,谢谢。。。。