本文適用語言:java
當下,java編碼過程中,實現定時任務的方式主要以以下兩種為主
網路上關於這兩種框架的實踐和設定相關的教學很多,這裡不再贅述。
本文主要就二者的框架原理實現做一個入門引導,為了解深層實現細節做一定的鋪墊。
本文原始碼版本:
@EnableScheduling // @EnableScheduling 在設定類上使用,開啟計劃任務的支援
@Component(value="myClass")// 由spring管理
public class MyClass {
@Scheduled(cron= "0 0 0 * * ?")//0 0 12 * * ? 每天12點觸發0 0 0/1 * * ? 0 0 0 * * ?
public void myTask() {
// 業務邏輯
...
}
}
org.springframework.scheduling.config.ContextLifecycleScheduledTaskRegistrar
public void onApplicationEvent(ContextRefreshedEvent event) {
if (event.getApplicationContext() != this.applicationContext) {
return;
}
// 定時任務執行入口方法系結到容器生命週期上
scheduleTasks();
}
1. 所有已註冊task
org.springframework.scheduling.config.ScheduledTaskRegistrar
protected void scheduleTasks() {
...
if (this.triggerTasks != null) {
for (TriggerTask task : this.triggerTasks) {
// 執行初始化完成的task和Trigger
this.scheduledFutures.add(this.taskScheduler.schedule(
task.getRunnable(), task.getTrigger()));
}
}
...
}
2. 單個task
org.springframework.scheduling.TaskScheduler
ScheduledFuture schedule(Runnable task, Trigger trigger);
3. 執行緒池執行task
org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler
public ScheduledFuture schedule(Runnable task, Trigger trigger) {
ScheduledExecutorService executor = getScheduledExecutor();
try {
ErrorHandler errorHandler =
(this.errorHandler != null ? this.errorHandler : TaskUtils.getDefaultErrorHandler(true));
// 呼叫具體的實現方法.schedule()
return new ReschedulingRunnable(task, trigger, executor, errorHandler).schedule();
}
catch (RejectedExecutionException ex) {
throw new TaskRejectedException("Executor [" + executor + "] did not accept task: " + task, ex);
}
}
4. 這塊是具體的執行緒實現細節,已經與schedul無關
private <V> ScheduledFuture<V> schedule(final ScheduledFutureTask<V> task) {
if (task == null) {
throw new NullPointerException("task");
} else {
if (this.inEventLoop()) {
this.delayedTaskQueue.add(task);
} else {
// 此處就是真正的執行緒執行方法
this.execute(new Runnable() {
public void run() {
SingleThreadEventExecutor.this.delayedTaskQueue.add(task);
}
});
}
return task;
}
}
org.springframework.scheduling.annotation.ScheduledAnnotationBeanPostProcessor
// BeanPostProcessor生命週期方法,spring載入的時候會執行
public Object postProcessAfterInitialization(final Object bean, String beanName) {
Class<?> targetClass = AopUtils.getTargetClass(bean);
if (!this.nonAnnotatedClasses.containsKey(targetClass)) {
final Set<Method> annotatedMethods = new LinkedHashSet<Method>(1);
ReflectionUtils.doWithMethods(targetClass, new MethodCallback() {
public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException {
Scheduled scheduled = AnnotationUtils.getAnnotation(method, Scheduled.class);
if (scheduled != null) {
// @Scheduled的真正解析方法,具體解析細節和引數參看原始碼
// 解析後新增到ScheduledTaskRegistrar裡
// 全部任務解析完成,執行ScheduledTaskRegistrar,具體實現參看[1.2.2 呼叫鏈路]章節
processScheduled(scheduled, method, bean);
annotatedMethods.add(method);
}
}
});
if (annotatedMethods.isEmpty()) {
this.nonAnnotatedClasses.put(targetClass, Boolean.TRUE);
}
}
return bean;
}
// 範例化一個排程器工廠,每個應用只有唯一一個工廠範例
SchedulerFactory schedFact = new org.quartz.impl.StdSchedulerFactory();
// 範例化一個排程器
Scheduler sched = schedFact.getScheduler();
// 啟動,只有啟動了排程器Quartz才會去執行任務
sched.start();
// 範例化一個任務
JobDetail job = newJob(HelloJob.class)
.withIdentity("myJob", "group1")
.build();
// 範例化一個任務觸發器,立刻觸發,每40s執行一次
Trigger trigger = newTrigger()
.withIdentity("myTrigger", "group1")
.startNow()
.withSchedule(simpleSchedule()
.withIntervalInSeconds(40)
.repeatForever())
.build();
// 排程任務
sched.scheduleJob(job, trigger);
1. web.xml設定
<context-param>
<param-name>quartz:config-file</param-name>
<param-value>/some/path/my_quartz.properties</param-value>
</context-param>
<context-param>
<param-name>quartz:shutdown-on-unload</param-name>
<param-value>true</param-value>
</context-param>
<context-param>
<param-name>quartz:start-on-load</param-name>
<param-value>true</param-value>
</context-param>
<listener>
<listener-class>
org.quartz.ee.servlet.QuartzInitializerListener
</listener-class>
</listener>
2. org.quartz.ee.servlet.QuartzInitializerListener
// 執行ServletContextListener.contextInitialized的容器生命週期方法
public void contextInitialized(ServletContextEvent sce) {
...
// 根據自定義的組態檔載入SchedulerFactory
if (configFile != null) {
factory = new StdSchedulerFactory(configFile);
} else {
factory = new StdSchedulerFactory();
}
// 載入scheduler
scheduler = factory.getScheduler();
// 啟動scheduler
scheduler.start();
log.info("Scheduler has been started...");
...
}
1. StdSchedulerFactory.getScheduler()
public Scheduler getScheduler() throws SchedulerException {
if (cfg == null) {
// 根據不同的設定方式載入對應設定
initialize();
}
...
// 載入範例(載入Scheduler整個上下文環境)
sched = instantiate();
return sched;
}
2. StdSchedulerFactory.getScheduler().instantiate()
具體實現程式碼很多,以下做虛擬碼描述
private Scheduler instantiate() throws SchedulerException {
// 校驗初始化
if (cfg == null) {
initialize();
}
// 獲取 Scheduler
// 載入 ThreadPool
// 載入 JobStore
// 載入 DataSources
// 載入 SchedulerPlugins
// 載入 JobListeners
// 載入 TriggerListeners
// 載入 ThreadExecutor
// 構造QuartzScheduler
qs = new QuartzScheduler(rsrcs, schedCtxt, idleWaitTime, dbFailureRetry);
Scheduler scheduler = instantiate(rsrcs, qs);
qs.initialize();
// 返回範例化好的scheduler
return scheduler;
}