当下,java编码过程中,实现定时任务的方式主要以以下两种为主
网络上关于这两种框架的实践和配置相关的教程很多,这里不再赘述。
本文主要就二者的框架原理实现做一个入门引导,为了解深层实现细节做一定的铺垫。
本文源码版本:
class="prettyprint hljs less" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto;">@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 定时任务执行入口在哪?
class="prettyprint hljs cs" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto;">org.springframework.scheduling.config.ContextLifecycleScheduledTaskRegistrar
-
- public void onApplicationEvent(ContextRefreshedEvent event) {
- if (event.getApplicationContext() != this.applicationContext) {
- return;
- }
- // 定时任务执行入口方法绑定到容器生命周期上
- scheduleTasks();
- }
1.2.2 调用链路
class="prettyprint hljs gradle" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto;">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
ScheduledFuture schedule(final ScheduledFutureTask 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注解的生效原理
class="prettyprint hljs gradle" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto;">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;
- }
class="prettyprint hljs fsharp" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto;">// 实例化一个调度器工厂,每个应用只有唯一一个工厂实例
- 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.1 启动入口
class="prettyprint hljs xml" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto;">1\. web.xml配置
- <param-name>quartz:config-fileparam-name>
- <param-value>/some/path/my_quartz.propertiesparam-value>
- <context-param>
- <param-name>quartz:shutdown-on-unloadparam-name>
- <param-value>trueparam-value>
- context-param>
- <context-param>
- <param-name>quartz:start-on-loadparam-name>
- <param-value>trueparam-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 核心方法详解
class="prettyprint hljs java" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto;">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;
- }