本文适用语言:java

序章:定时任务实现方式

当下,java编码过程中,实现定时任务的方式主要以以下两种为主

  • spring框架的@Scheduled
  • quzrtz框架

网络上关于这两种框架的实践和配置相关的教程很多,这里不再赘述。

本文主要就二者的框架原理实现做一个入门引导,为了解深层实现细节做一定的铺垫。

本文源码版本

  • spring-context-3.2.18.RELEASE.jar
  • quartz-1.8.6.jar

一、Scheduled

1.1 使用方法

@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() {
// 业务逻辑
...
}
}

1.2 源码分析

1.2.1 定时任务执行入口在哪?

org.springframework.scheduling.config.ContextLifecycleScheduledTaskRegistrar

public void onApplicationEvent(ContextRefreshedEvent event) {
if (event.getApplicationContext() != this.applicationContext) {
return;
}
// 定时任务执行入口方法绑定到容器生命周期上
scheduleTasks();
}

1.2.2 调用链路

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;
}
}

1.2.3 @Scheduled注解的生效原理

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;
}

二、QUARTZ

2.1 使用方法

// 实例化一个调度器工厂,每个应用只有唯一一个工厂实例
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);

2.2 源码分析

2.2.1 启动入口


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...");
...
}

2.2.2 核心方法详解

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;
}

JAVA定时任务原理入门的更多相关文章

  1. 大白话说Java反射:入门、使用、原理

    文章首发于[博客园-陈树义],点击跳转到原文<大白话说Java反射:入门.进阶.原理> 反射之中包含了一个「反」字,所以想要解释反射就必须先从「正」开始解释. 一般情况下,我们使用某个类时 ...

  2. 大白话说Java泛型:入门、使用、原理

    文章首发于[博客园-陈树义],点击跳转到原文<大白话说Java泛型:入门.使用.原理> 远在 JDK 1.4 版本的时候,那时候是没有泛型的概念的.当时 Java 程序员们写集合类的代码都 ...

  3. Java学习---Quartz定时任务快速入门

    Quartz是OpenSymphony开源组织在Job scheduling领域又一个开源项目,它可以与J2EE与J2SE应用程序相结合也可以单独使用.Quartz可以用来创建简单或为运行十个,百个, ...

  4. 大白话说Java反射:入门、使用、原理 (转)

    文章首发于[博客园-陈树义],点击跳转到原文<大白话说Java反射:入门.进阶.原理> 目录 一个简单的例子 反射常用API 获取反射中的Class对象 通过反射创建类对象 通过反射获取类 ...

  5. atititt.java定时任务框架选型Spring Quartz 注解总结

    atititt.java定时任务框架选型Spring Quartz 总结 1. .Spring Quartz  (ati recomm) 1 2. Spring Quartz具体配置 2 2.1. 增 ...

  6. Java入门-浅析Java学习从入门到精通【转】

    一. JDK (Java Development Kit)  JDK是整个Java的核心,包括了Java运行环境(Java Runtime Envirnment),一堆Java工具和Java基础的类库 ...

  7. Java编译原理

    http://wenku.baidu.com/view/f9b1734b87c24028915fc3a3.html Java编译原理 1. 关于动态加载机制 学习Java比C++更容易理解OOP的思想 ...

  8. Java对象序列化入门

      Java对象序列化入门 关于Java序列化的文章早已是汗牛充栋了,本文是对我个人过往学习,理解及应用Java序列化的一个总结.此文内容涉及Java序列化的基本原理,以及多种方法对序列化形式进行定制 ...

  9. java J2EE学习入门

    首先学习JAVA基础编程,大学教材就是最简单的了!象写写Helloworld啊 输出水仙花数啊 玩些简单的,慢慢在研究研究流啊,都可以了.然后学习简单的JSP,这个时候多上网上DOWN一些原码.多看看 ...

随机推荐

  1. Python-100-Days-master

    跟着python100学习一下 100以内的素数 # 输出100以内的所有素数 # 想法:从1到100遍历,假设得到了i=17,那么此时从1到9遍历,如果找到了一个数用17能除尽则跳出循环 # 如果找 ...

  2. 树莓派开发笔记(十四):入手研华ADVANTECH工控树莓派UNO-220套件(三):使用研发自带系统测试rtc、gpio、232和485套件接口

    前言   上一篇说明了必须要使用研华自带的8G卡的系统,通过沟通拿到了相关的系统,购买的时候会带8GB的卡,请自行备份一份镜像.本篇对uno-220套件的相关研华配套的额外接口做测试,篇幅较长,重点讲 ...

  3. Sass预处理器

    CSS预处理器 less,sass和stylus sass:较早,缩进风格 scss:完全兼容css3,使用大括号 编写css的编程语言,引入变量,函数,重复代码等功能,如果编译成css文件 Sass ...

  4. SQL中的数字、字母和汉字

    知识点001 当变量的数据类型为VARCHAR时,变量赋值后,变量中的字符所占字节数,数字和字母是1个bytes,汉字是2个bytes; 当变量的数据类型为NVARCHAR时,变量赋值后,变量中的字符 ...

  5. printf 输出前导0

    printf ("%3d\n", 5); printf ("%03d\n", 5); 输出为

  6. ExtJS 布局-Card 布局(Card layout)

    更新记录: 2022年6月1日 开始. 2022年6月6日 发布. 1.说明 卡片布局类似牌堆,每次只有一个子组件可见,子组件几乎填满了整个容器.卡片布局常用于向导(Wizard)和选项卡(Tabs) ...

  7. 点亮Arduino内置的LED灯

    更新记录 2022年4月16日:本文迁移自Panda666原博客,原发布时间:2021年9月3日. 15块软妹币的板子镇楼. 上一篇配置好了开发环境,然后就开始搞第一个小灯的实验了. 原理相当的简单, ...

  8. BUUCTF-你竟然赶我走

    你竟然赶我走 首先看到这个图片没啥感觉,直接用16进制打开了.拖到最下面果然有flag flag{stego_is_s0_bor1ing}

  9. 强化学习-Windows安装gym、atari和box2d环境

    安装gym pip3 install gym pip3 install gym[accept-rom-license] 安装atari环境[可选] 下载安装VS build tools 如果出现 OS ...

  10. NHibernte 4.0.3版本中,使用Queryover().Where().OrderBy().Skip().Take()方法分页获取数据失败

    问题代码如下: var result=repository.QueryOver<modal>() .Where(p=>p.Code==Code) .OrderBy(p=>p.I ...