Quartz2.2.x官方文档2.2.X—第三章 3.关于Jobs和Job Details

本文深入探讨了Quartz调度器中的Job和JobDetail概念,解释了如何通过JobDetail配置Job属性,利用JobDataMap传递数据,以及如何管理Job的状态和并发。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Quartz 教程


Table of Contents | ‹ Lesson 2 | Lesson 4 ›

课程3: 关于 Jobs 和 Job Details

正如你课程2看到的,Jobs十分容易实现,只需要实现接口中的‘execute’ 方法。你还需要了解更多关于jobs的性质,Job接口的execute(..)方法,关于JobDetails。

当你实现job类通过代码知道怎么实现不同类型的Job,Quartz需要配置你希望job拥有的不同属性。这是通过JobDetail类完成的,在上一节简要提到过。

JobDetail实例的构建使用JobBuilder类。你通常可以使用静态导入所有方法,这样可以使你的代码拥有DSL的感觉。


import static org.quartz.JobBuilder.*;

现在让我们讨论一下关于Jobs的'本质'和在Quartz中的Job实例生命周期的问题。首先让门看一些在我们第一课中看过得代码片段:


  // define the job and tie it to our HelloJob class
  JobDetail job = newJob(HelloJob.class)
      .withIdentity("myJob", "group1") // name "myJob", group "group1"
      .build();

  // Trigger the job to run now, and then every 40 seconds
  Trigger trigger = newTrigger()
      .withIdentity("myTrigger", "group1")
      .startNow()
      .withSchedule(simpleSchedule()
          .withIntervalInSeconds(40)
          .repeatForever())            
      .build();

  // Tell quartz to schedule the job using our trigger
  sched.scheduleJob(job, trigger);

现在考虑一下'HelloJob' Job类定义如下:


  public class HelloJob implements Job {

    public HelloJob() {
    }

    public void execute(JobExecutionContext context)
      throws JobExecutionException
    {
      System.err.println("Hello!  HelloJob is executing.");
    }
  }

我们给这个scheduler一个JobDetail实例,它知道这个被执行job的类型,你只需简单提供job类就可以构造JobDetail。每次调度执行这个job, 都会在调用它的execute(..)方法前,创建一个这个类的新实例。当执行完成后,对job类实例的引用将删除,实例会被垃圾收集器回收。这种行为使得Jobs必须有一个无参的构造函数(当使用默认JobFactory实现时). 另一方面在job类中定义数据字段是没有意义的 - 因为在job执行期间这些值不会被保存.

你可能会问“那我怎样能赋予Job实体属性/配置呢? 和 “我怎样能在job执行期间追踪它的状态?” 这些问题的答案是同一个:关键在于JobDataMap, 它是JobDetail对象的一部分.

JobDataMap

JobDataMap能够用来保存你希望在Job执行时能够传递给Job的任意数量的数据。JobDataMap是Java Map接口的一种是实现, 并且有一些遍历的方法,来存储和检索原始类型的数据。

下面是当定义/构建JobDetail时一些往JobDataMap中放入数据的片段,job添加至调度程序之前:


  // define the job and tie it to our DumbJob class
  JobDetail job = newJob(DumbJob.class)
      .withIdentity("myJob", "group1") // name "myJob", group "group1"
      .usingJobData("jobSays", "Hello World!")
      .usingJobData("myFloatValue", 3.141f)
      .build();

这儿有一个当job执行期间,从JobDataMap中获取数据的例子:


public class DumbJob implements Job {

    public DumbJob() {
    }

    public void execute(JobExecutionContext context)
      throws JobExecutionException
    {
      JobKey key = context.getJobDetail().getKey();

      JobDataMap dataMap = context.getJobDetail().getJobDataMap();

      String jobSays = dataMap.getString("jobSays");
      float myFloatValue = dataMap.getFloat("myFloatValue");

      System.err.println("Instance " + key + " of DumbJob says: " + jobSays + ", and val is: " + myFloatValue);
    }
  }

如果你使用持久的JobStore(在本教程的JobStore部分讨论) 你应该在决定使用JobDataMap的地方小心谨慎,因为它会将对象序列化,而这容易导致类的版本问题。显然标准Java类型是十分安全的,但是对已经序列化的实体修改它们的定义,特别要注意不要破坏它们的兼容性。当然, 你可以只将原始类型放入JDBC-JobStore和JobDataMap,只有字符串被允许存放到map中,这样就排除了任何序列化问题的可能。

如果你给job类添加set方法,这个方法的名称相当于在JobDataMap中的key。(例如上个例子中的数据,setJobSays(String val)方法), 接着当job实例化后,Quartz’s默认的JobFactory实现将会自动调用这些set方法,这样就避免了在执行方法中显示的获取map中的值.

Triggers也可以有与之关联的JobDataMaps。在你有一个Job在调度程序储存中,并且有多个触发器定期/重复使用的情况下是十分有用的,每一个触发器触发时,你都可以给Job提供不同的数据输入。

在Job执行期间通过JobExecutionContext获得JobDataMap是十分方便的。它的是JobDatail的JobDataMap和Trigger中JobDataMap值的合并,后者中任何与前者中相同的值,都会把前者覆盖。

这里有一个例子,关于在Job执行期间从JobExecutionContext中获得合并JobDataMap的值:


public class DumbJob implements Job {

    public DumbJob() {
    }

    public void execute(JobExecutionContext context)
      throws JobExecutionException
    {
      JobKey key = context.getJobDetail().getKey();

      JobDataMap dataMap = context.getMergedJobDataMap();  // Note the difference from the previous example

      String jobSays = dataMap.getString("jobSays");
      float myFloatValue = dataMap.getFloat("myFloatValue");
      ArrayList state = (ArrayList)dataMap.get("myStateData");
      state.add(new Date());

      System.err.println("Instance " + key + " of DumbJob says: " + jobSays + ", and val is: " + myFloatValue);
    }
  }

或者如果你希望依然于JobFactory向你的类注入map值,它可能是这样的:


  public class DumbJob implements Job {


    String jobSays;
    float myFloatValue;
    ArrayList state;

    public DumbJob() {
    }

    public void execute(JobExecutionContext context)
      throws JobExecutionException
    {
      JobKey key = context.getJobDetail().getKey();

      JobDataMap dataMap = context.getMergedJobDataMap();  // Note the difference from the previous example

      state.add(new Date());

      System.err.println("Instance " + key + " of DumbJob says: " + jobSays + ", and val is: " + myFloatValue);
    }

    public void setJobSays(String jobSays) {
      this.jobSays = jobSays;
    }

    public void setMyFloatValue(float myFloatValue) {
      myFloatValue = myFloatValue;
    }

    public void setState(ArrayList state) {
      state = state;
    }

  }

你会注意到这个类的整体代码更长,但是execute()方法的代码更加清晰。虽然这个代码更长,它实际上编写代码更少,如果你的IDE开发工具使用了set方法自动生成,那么就不需要单独再从JobDataMap中获取数据。至于如何选择在于你了。

Job “Instances”

许多用户对于花费时间教你如何构建"job 实例"感到困惑。我们将会在下部分讲解关于job状态和并发的事情。

你可以创建一个单独的job类,然偶胡在调度程序中创建需要JobDetails的实例 - 每个都有自己的属性和JobDataMap - 并将它们全部添加到调度程序中.

例如,你可以创建一个实现Job接口的类"SalesReportJob"。这个job被用来根据输入给它的参数(通过JobDataMap),来指定销售报告根据销售员来生成。他们可以创建多个job的定义(JobDetails), 例如“SalesReportForJoe” 和“SalesReportForMike” ,在JobDataMaps中将"joe"和"mike"分别输入给对应的jobs。

当一个触发触发时,JobDetail(实例定义) 与加载有关,它所引用的job类是通过在调度程序中的JobFactory配置初始化的。默认的JobFactory会简单的调用job class的newInstance()方法,然后尝试在类中调用JobDataMap中与key名称相匹配的set方法。你可能希望创建你自己的JobFactory让你应用程序的Ioc或则DI容器生产/初始化job实例。

在“Quartz speak”, 我们将每一个存储的JobDetail作为"job definition"或者“JobDetail instance”, 我们将每一个调用的job称为"job实例"或者"job定义的实例"。通常如果我们只是使用"job",我们使用的是"job"这个词,一般我们指"job"这个词的意思是名称定义或者JobDetail。当我们提到job接口的实现,我们通常使用"job class"。

Job 状态和并发

现在, 需要注意job的状态数据(又称作JobDataMap)和并发. 有一些注解被添加到你的Job类中,这些注解会影响在这些方面Quartz的行为。

@DisallowConcurrentExecution 是可以添加到Job类的注释,告诉Quartz不要同时执行给定job定义的多个实例(指的是给定的做作业类) .
注意这里的描述,小心的选择使用。在前一节例子中,如果“SalesReportJob”上有这个注解,在给定时间内只有一个"SalesReportForJoe"实例被执行, 但是能够同时执行多个"SalesReportForMike"实例。约束基于实例定义(JobDetail),不是工作类的实例。不管怎样, 它(在Quartz定义中)都明确的指出注释应加载类本身,因为它通常会对类的编码产生影响。

@PersistJobDataAfterExecution 是一个能够在execute()方法完全执行后,告诉Quartz更新JobDetail的JobDataMap的存储副本的注解。这样同一个Job(JobDetail)下一次执行将会收到更新后的值而不是原始存储的值。就像@DisallowConcurrentExecution 注解, 这适用于作业定义实例,而不是一个作业类实例,尽管决定让job类携带属性,因为会对类的编码产生影响。(例如在执行方法中,"statefulness"需要显示指定"understood"。

如果你适用@PersistJobDataAfterExecution注解, 你也应该好好思考需要使用@DisallowConcurrentExecution 注解, 因为为了避免同一个Job的两个实例同时执行时,数据可能会混乱的问题。

其他Jobs属性

下面是能够通过JobDetail对象定义一个job实例的其他属性快速总结:

  • Durability - 如果job非持久化,一旦job与任何活跃触发器失去关联则自动从调度程序中删除。换句话说,非持久jobs有一个 生命周期,trigger存在是必须的。
  • RequestsRecovery - 如果一个任务在"请求中恢复",它在调度程序硬关闭期间执行(例如在运行中崩溃,或者机器被关闭) , 接着当调度程序开始运行它会重新被执行。此时,JobExecutionContext.isRecovering()方法会返回true。

JobExecutionException

最后,我们需要告诉你一些Job.execute(..)方法的一些详细信息。你可以从execute方法抛出唯一异常(包含运行时异常)是JobExecutionException。因此,你应该在使用execute方法的地方用'try-catch'包裹起来。你可以用些时间来看下JobExecutionException的文档,你可以通过它知道如何处理这个异常。

/* * Copyright 2002-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.springframework.scheduling.quartz; import java.io.IOException; import java.util.Map; import java.util.Properties; import java.util.concurrent.Executor; import java.util.concurrent.TimeUnit; import javax.sql.DataSource; import org.quartz.Scheduler; import org.quartz.SchedulerException; import org.quartz.SchedulerFactory; import org.quartz.impl.RemoteScheduler; import org.quartz.impl.SchedulerRepository; import org.quartz.impl.StdSchedulerFactory; import org.quartz.simpl.SimpleThreadPool; import org.quartz.spi.JobFactory; import org.springframework.beans.BeanUtils; import org.springframework.beans.factory.BeanNameAware; import org.springframework.beans.factory.DisposableBean; import org.springframework.beans.factory.FactoryBean; import org.springframework.beans.factory.InitializingBean; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.context.SmartLifecycle; import org.springframework.core.io.Resource; import org.springframework.core.io.ResourceLoader; import org.springframework.core.io.support.PropertiesLoaderUtils; import org.springframework.lang.Nullable; import org.springframework.scheduling.SchedulingException; import org.springframework.util.Assert; import org.springframework.util.CollectionUtils; /** * {@link FactoryBean} that creates and configures a Quartz {@link org.quartz.Scheduler}, * manages its lifecycle as part of the Spring application context, and exposes the * Scheduler as bean reference for dependency injection. * * <p>Allows registration of JobDetails, Calendars and Triggers, automatically * starting the scheduler on initialization and shutting it down on destruction. * In scenarios that just require static registration of jobs at startup, there * is no need to access the Scheduler instance itself in application code. * * <p>For dynamic registration of jobs at runtime, use a bean reference to * this SchedulerFactoryBean to get direct access to the Quartz Scheduler * ({@code org.quartz.Scheduler}). This allows you to create new jobs * and triggers, and also to control and monitor the entire Scheduler. * * <p>Note that Quartz instantiates a new Job for each execution, in * contrast to Timer which uses a TimerTask instance that is shared * between repeated executions. Just JobDetail descriptors are shared. * * <p>When using persistent jobs, it is strongly recommended to perform all * operations on the Scheduler within Spring-managed (or plain JTA) transactions. * Else, database locking will not properly work and might even break. * (See {@link #setDataSource setDataSource} javadoc for details.) * * <p>The preferred way to achieve transactional execution is to demarcate * declarative transactions at the business facade level, which will * automatically apply to Scheduler operations performed within those scopes. * Alternatively, you may add transactional advice for the Scheduler itself. * * <p>Compatible with Quartz 2.1.4 and higher, as of Spring 4.1. * * @author Juergen Hoeller * @since 18.02.2004 * @see #setDataSource * @see org.quartz.Scheduler * @see org.quartz.SchedulerFactory * @see org.quartz.impl.StdSchedulerFactory * @see org.springframework.transaction.interceptor.TransactionProxyFactoryBean */ public class SchedulerFactoryBean extends SchedulerAccessor implements FactoryBean<Scheduler>, BeanNameAware, ApplicationContextAware, InitializingBean, DisposableBean, SmartLifecycle { /** * The thread count property. */ public static final String PROP_THREAD_COUNT = "org.quartz.threadPool.threadCount"; /** * The default thread count. */ public static final int DEFAULT_THREAD_COUNT = 10; private static final ThreadLocal<ResourceLoader> configTimeResourceLoaderHolder = new ThreadLocal<>(); private static final ThreadLocal<Executor> configTimeTaskExecutorHolder = new ThreadLocal<>(); private static final ThreadLocal<DataSource> configTimeDataSourceHolder = new ThreadLocal<>(); private static final ThreadLocal<DataSource> configTimeNonTransactionalDataSourceHolder = new ThreadLocal<>(); /** * Return the {@link ResourceLoader} for the currently configured Quartz Scheduler, * to be used by {@link ResourceLoaderClassLoadHelper}. * <p>This instance will be set before initialization of the corresponding Scheduler, * and reset immediately afterwards. It is thus only available during configuration. * @see #setApplicationContext * @see ResourceLoaderClassLoadHelper */ @Nullable public static ResourceLoader getConfigTimeResourceLoader() { return configTimeResourceLoaderHolder.get(); } /** * Return the {@link Executor} for the currently configured Quartz Scheduler, * to be used by {@link LocalTaskExecutorThreadPool}. * <p>This instance will be set before initialization of the corresponding Scheduler, * and reset immediately afterwards. It is thus only available during configuration. * @since 2.0 * @see #setTaskExecutor * @see LocalTaskExecutorThreadPool */ @Nullable public static Executor getConfigTimeTaskExecutor() { return configTimeTaskExecutorHolder.get(); } /** * Return the {@link DataSource} for the currently configured Quartz Scheduler, * to be used by {@link LocalDataSourceJobStore}. * <p>This instance will be set before initialization of the corresponding Scheduler, * and reset immediately afterwards. It is thus only available during configuration. * @since 1.1 * @see #setDataSource * @see LocalDataSourceJobStore */ @Nullable public static DataSource getConfigTimeDataSource() { return configTimeDataSourceHolder.get(); } /** * Return the non-transactional {@link DataSource} for the currently configured * Quartz Scheduler, to be used by {@link LocalDataSourceJobStore}. * <p>This instance will be set before initialization of the corresponding Scheduler, * and reset immediately afterwards. It is thus only available during configuration. * @since 1.1 * @see #setNonTransactionalDataSource * @see LocalDataSourceJobStore */ @Nullable public static DataSource getConfigTimeNonTransactionalDataSource() { return configTimeNonTransactionalDataSourceHolder.get(); } @Nullable private SchedulerFactory schedulerFactory; private Class<? extends SchedulerFactory> schedulerFactoryClass = StdSchedulerFactory.class; @Nullable private String schedulerName; @Nullable private Resource configLocation; @Nullable private Properties quartzProperties; @Nullable private Executor taskExecutor; @Nullable private DataSource dataSource; @Nullable private DataSource nonTransactionalDataSource; @Nullable private Map<String, ?> schedulerContextMap; @Nullable private String applicationContextSchedulerContextKey; @Nullable private JobFactory jobFactory; private boolean jobFactorySet = false; private boolean autoStartup = true; private int startupDelay = 0; private int phase = DEFAULT_PHASE; private boolean exposeSchedulerInRepository = false; private boolean waitForJobsToCompleteOnShutdown = false; @Nullable private String beanName; @Nullable private ApplicationContext applicationContext; @Nullable private Scheduler scheduler; /** * Set an external Quartz {@link SchedulerFactory} instance to use. * <p>Default is an internal {@link StdSchedulerFactory} instance. If this method is * called, it overrides any class specified through {@link #setSchedulerFactoryClass} * as well as any settings specified through {@link #setConfigLocation}, * {@link #setQuartzProperties}, {@link #setTaskExecutor} or {@link #setDataSource}. * <p><b>NOTE:</b> With an externally provided {@code SchedulerFactory} instance, * local settings such as {@link #setConfigLocation} or {@link #setQuartzProperties} * will be ignored here in {@code SchedulerFactoryBean}, expecting the external * {@code SchedulerFactory} instance to get initialized on its own. * @since 4.3.15 * @see #setSchedulerFactoryClass */ public void setSchedulerFactory(SchedulerFactory schedulerFactory) { this.schedulerFactory = schedulerFactory; } /** * Set the Quartz {@link SchedulerFactory} implementation to use. * <p>Default is the {@link StdSchedulerFactory} class, reading in the standard * {@code quartz.properties} from {@code quartz.jar}. For applying custom Quartz * properties, specify {@link #setConfigLocation "configLocation"} and/or * {@link #setQuartzProperties "quartzProperties"} etc on this local * {@code SchedulerFactoryBean} instance. * @see org.quartz.impl.StdSchedulerFactory * @see #setConfigLocation * @see #setQuartzProperties * @see #setTaskExecutor * @see #setDataSource */ public void setSchedulerFactoryClass(Class<? extends SchedulerFactory> schedulerFactoryClass) { this.schedulerFactoryClass = schedulerFactoryClass; } /** * Set the name of the Scheduler to create via the SchedulerFactory, as an * alternative to the {@code org.quartz.scheduler.instanceName} property. * <p>If not specified, the name will be taken from Quartz properties * ({@code org.quartz.scheduler.instanceName}), or from the declared * {@code SchedulerFactoryBean} bean name as a fallback. * @see #setBeanName * @see StdSchedulerFactory#PROP_SCHED_INSTANCE_NAME * @see org.quartz.SchedulerFactory#getScheduler() * @see org.quartz.SchedulerFactory#getScheduler(String) */ public void setSchedulerName(String schedulerName) { this.schedulerName = schedulerName; } /** * Set the location of the Quartz properties config file, for example * as classpath resource "classpath:quartz.properties". * <p>Note: Can be omitted when all necessary properties are specified * locally via this bean, or when relying on Quartz&#39; default configuration. * @see #setQuartzProperties */ public void setConfigLocation(Resource configLocation) { this.configLocation = configLocation; } /** * Set Quartz properties, like "org.quartz.threadPool.class". * <p>Can be used to override values in a Quartz properties config file, * or to specify all necessary properties locally. * @see #setConfigLocation */ public void setQuartzProperties(Properties quartzProperties) { this.quartzProperties = quartzProperties; } /** * Set a Spring-managed {@link Executor} to use as Quartz backend. * Exposed as thread pool through the Quartz SPI. * <p>Can be used to assign a local JDK ThreadPoolExecutor or a CommonJ * WorkManager as Quartz backend, to avoid Quartz&#39;s manual thread creation. * <p>By default, a Quartz SimpleThreadPool will be used, configured through * the corresponding Quartz properties. * @since 2.0 * @see #setQuartzProperties * @see LocalTaskExecutorThreadPool * @see org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor * @see org.springframework.scheduling.concurrent.DefaultManagedTaskExecutor */ public void setTaskExecutor(Executor taskExecutor) { this.taskExecutor = taskExecutor; } /** * Set the default {@link DataSource} to be used by the Scheduler. * <p>Note: If this is set, the Quartz settings should not define * a job store "dataSource" to avoid meaningless double configuration. * Also, do not define a "org.quartz.jobStore.class" property at all. * (You may explicitly define Spring&#39;s {@link LocalDataSourceJobStore} * but that&#39;s the default when using this method anyway.) * <p>A Spring-specific subclass of Quartz&#39; JobStoreCMT will be used. * It is therefore strongly recommended to perform all operations on * the Scheduler within Spring-managed (or plain JTA) transactions. * Else, database locking will not properly work and might even break * (for example, if trying to obtain a lock on Oracle without a transaction). * <p>Supports both transactional and non-transactional DataSource access. * With a non-XA DataSource and local Spring transactions, a single DataSource * argument is sufficient. In case of an XA DataSource and global JTA transactions, * SchedulerFactoryBean&#39;s "nonTransactionalDataSource" property should be set, * passing in a non-XA DataSource that will not participate in global transactions. * @since 1.1 * @see #setNonTransactionalDataSource * @see #setQuartzProperties * @see #setTransactionManager * @see LocalDataSourceJobStore */ public void setDataSource(DataSource dataSource) { this.dataSource = dataSource; } /** * Set the {@link DataSource} to be used <i>for non-transactional access</i>. * <p>This is only necessary if the default DataSource is an XA DataSource that will * always participate in transactions: A non-XA version of that DataSource should * be specified as "nonTransactionalDataSource" in such a scenario. * <p>This is not relevant with a local DataSource instance and Spring transactions. * Specifying a single default DataSource as "dataSource" is sufficient there. * @since 1.1 * @see #setDataSource * @see LocalDataSourceJobStore */ public void setNonTransactionalDataSource(DataSource nonTransactionalDataSource) { this.nonTransactionalDataSource = nonTransactionalDataSource; } /** * Register objects in the Scheduler context via a given Map. * These objects will be available to any Job that runs in this Scheduler. * <p>Note: When using persistent Jobs whose JobDetail will be kept in the * database, do not put Spring-managed beans or an ApplicationContext * reference into the JobDataMap but rather into the SchedulerContext. * @param schedulerContextAsMap a Map with String keys and any objects as * values (for example Spring-managed beans) * @see JobDetailFactoryBean#setJobDataAsMap */ public void setSchedulerContextAsMap(Map<String, ?> schedulerContextAsMap) { this.schedulerContextMap = schedulerContextAsMap; } /** * Set the key of an {@link ApplicationContext} reference to expose in the * SchedulerContext, for example "applicationContext". Default is none. * Only applicable when running in a Spring ApplicationContext. * <p>Note: When using persistent Jobs whose JobDetail will be kept in the * database, do not put an ApplicationContext reference into the JobDataMap * but rather into the SchedulerContext. * <p>In case of a QuartzJobBean, the reference will be applied to the Job * instance as bean property. An "applicationContext" attribute will * correspond to a "setApplicationContext" method in that scenario. * <p>Note that BeanFactory callback interfaces like ApplicationContextAware * are not automatically applied to Quartz Job instances, because Quartz * itself is responsible for the lifecycle of its Jobs. * @see JobDetailFactoryBean#setApplicationContextJobDataKey * @see org.springframework.context.ApplicationContext */ public void setApplicationContextSchedulerContextKey(String applicationContextSchedulerContextKey) { this.applicationContextSchedulerContextKey = applicationContextSchedulerContextKey; } /** * Set the Quartz {@link JobFactory} to use for this Scheduler. * <p>Default is Spring&#39;s {@link AdaptableJobFactory}, which supports * {@link java.lang.Runnable} objects as well as standard Quartz * {@link org.quartz.Job} instances. Note that this default only applies * to a <i>local</i> Scheduler, not to a RemoteScheduler (where setting * a custom JobFactory is not supported by Quartz). * <p>Specify an instance of Spring&#39;s {@link SpringBeanJobFactory} here * (typically as an inner bean definition) to automatically populate a job&#39;s * bean properties from the specified job data map and scheduler context. * @since 2.0 * @see AdaptableJobFactory * @see SpringBeanJobFactory */ public void setJobFactory(JobFactory jobFactory) { this.jobFactory = jobFactory; this.jobFactorySet = true; } /** * Set whether to automatically start the scheduler after initialization. * <p>Default is "true"; set this to "false" to allow for manual startup. */ public void setAutoStartup(boolean autoStartup) { this.autoStartup = autoStartup; } /** * Return whether this scheduler is configured for auto-startup. If "true", * the scheduler will start after the context is refreshed and after the * start delay, if any. */ @Override public boolean isAutoStartup() { return this.autoStartup; } /** * Specify the phase in which this scheduler should be started and stopped. * The startup order proceeds from lowest to highest, and the shutdown order * is the reverse of that. By default this value is {@code Integer.MAX_VALUE} * meaning that this scheduler starts as late as possible and stops as soon * as possible. * @since 3.0 */ public void setPhase(int phase) { this.phase = phase; } /** * Return the phase in which this scheduler will be started and stopped. */ @Override public int getPhase() { return this.phase; } /** * Set the number of seconds to wait after initialization before * starting the scheduler asynchronously. Default is 0, meaning * immediate synchronous startup on initialization of this bean. * <p>Setting this to 10 or 20 seconds makes sense if no jobs * should be run before the entire application has started up. */ public void setStartupDelay(int startupDelay) { this.startupDelay = startupDelay; } /** * Set whether to expose the Spring-managed {@link Scheduler} instance in the * Quartz {@link SchedulerRepository}. Default is "false", since the Spring-managed * Scheduler is usually exclusively intended for access within the Spring context. * <p>Switch this flag to "true" in order to expose the Scheduler globally. * This is not recommended unless you have an existing Spring application that * relies on this behavior. Note that such global exposure was the accidental * default in earlier Spring versions; this has been fixed as of Spring 2.5.6. */ public void setExposeSchedulerInRepository(boolean exposeSchedulerInRepository) { this.exposeSchedulerInRepository = exposeSchedulerInRepository; } /** * Set whether to wait for running jobs to complete on shutdown. * <p>Default is "false". Switch this to "true" if you prefer * fully completed jobs at the expense of a longer shutdown phase. * @see org.quartz.Scheduler#shutdown(boolean) */ public void setWaitForJobsToCompleteOnShutdown(boolean waitForJobsToCompleteOnShutdown) { this.waitForJobsToCompleteOnShutdown = waitForJobsToCompleteOnShutdown; } @Override public void setBeanName(String name) { this.beanName = name; } @Override public void setApplicationContext(ApplicationContext applicationContext) { this.applicationContext = applicationContext; } //--------------------------------------------------------------------- // Implementation of InitializingBean interface //--------------------------------------------------------------------- @Override public void afterPropertiesSet() throws Exception { if (this.dataSource == null && this.nonTransactionalDataSource != null) { this.dataSource = this.nonTransactionalDataSource; } if (this.applicationContext != null && this.resourceLoader == null) { this.resourceLoader = this.applicationContext; } // Initialize the Scheduler instance... this.scheduler = prepareScheduler(prepareSchedulerFactory()); try { registerListeners(); registerJobsAndTriggers(); } catch (Exception ex) { try { this.scheduler.shutdown(true); } catch (Exception ex2) { logger.debug("Scheduler shutdown exception after registration failure", ex2); } throw ex; } } /** * Create a SchedulerFactory if necessary and apply locally defined Quartz properties to it. * @return the initialized SchedulerFactory */ private SchedulerFactory prepareSchedulerFactory() throws SchedulerException, IOException { SchedulerFactory schedulerFactory = this.schedulerFactory; if (schedulerFactory == null) { // Create local SchedulerFactory instance (typically a StdSchedulerFactory) schedulerFactory = BeanUtils.instantiateClass(this.schedulerFactoryClass); if (schedulerFactory instanceof StdSchedulerFactory stdSchedulerFactory) { initSchedulerFactory(stdSchedulerFactory); } else if (this.configLocation != null || this.quartzProperties != null || this.taskExecutor != null || this.dataSource != null) { throw new IllegalArgumentException( "StdSchedulerFactory required for applying Quartz properties: " + schedulerFactory); } // Otherwise, no local settings to be applied via StdSchedulerFactory.initialize(Properties) } // Otherwise, assume that externally provided factory has been initialized with appropriate settings return schedulerFactory; } /** * Initialize the given SchedulerFactory, applying locally defined Quartz properties to it. * @param schedulerFactory the SchedulerFactory to initialize */ private void initSchedulerFactory(StdSchedulerFactory schedulerFactory) throws SchedulerException, IOException { Properties mergedProps = new Properties(); if (this.resourceLoader != null) { mergedProps.setProperty(StdSchedulerFactory.PROP_SCHED_CLASS_LOAD_HELPER_CLASS, ResourceLoaderClassLoadHelper.class.getName()); } if (this.taskExecutor != null) { mergedProps.setProperty(StdSchedulerFactory.PROP_THREAD_POOL_CLASS, LocalTaskExecutorThreadPool.class.getName()); } else { // Set necessary default properties here, as Quartz will not apply // its default configuration when explicitly given properties. mergedProps.setProperty(StdSchedulerFactory.PROP_THREAD_POOL_CLASS, SimpleThreadPool.class.getName()); mergedProps.setProperty(PROP_THREAD_COUNT, Integer.toString(DEFAULT_THREAD_COUNT)); } if (this.configLocation != null) { if (logger.isDebugEnabled()) { logger.debug("Loading Quartz config from [" + this.configLocation + "]"); } PropertiesLoaderUtils.fillProperties(mergedProps, this.configLocation); } CollectionUtils.mergePropertiesIntoMap(this.quartzProperties, mergedProps); if (this.dataSource != null) { mergedProps.putIfAbsent(StdSchedulerFactory.PROP_JOB_STORE_CLASS, LocalDataSourceJobStore.class.getName()); } // Determine scheduler name across local settings and Quartz properties... if (this.schedulerName != null) { mergedProps.setProperty(StdSchedulerFactory.PROP_SCHED_INSTANCE_NAME, this.schedulerName); } else { String nameProp = mergedProps.getProperty(StdSchedulerFactory.PROP_SCHED_INSTANCE_NAME); if (nameProp != null) { this.schedulerName = nameProp; } else if (this.beanName != null) { mergedProps.setProperty(StdSchedulerFactory.PROP_SCHED_INSTANCE_NAME, this.beanName); this.schedulerName = this.beanName; } } schedulerFactory.initialize(mergedProps); } private Scheduler prepareScheduler(SchedulerFactory schedulerFactory) throws SchedulerException { if (this.resourceLoader != null) { // Make given ResourceLoader available for SchedulerFactory configuration. configTimeResourceLoaderHolder.set(this.resourceLoader); } if (this.taskExecutor != null) { // Make given TaskExecutor available for SchedulerFactory configuration. configTimeTaskExecutorHolder.set(this.taskExecutor); } if (this.dataSource != null) { // Make given DataSource available for SchedulerFactory configuration. configTimeDataSourceHolder.set(this.dataSource); } if (this.nonTransactionalDataSource != null) { // Make given non-transactional DataSource available for SchedulerFactory configuration. configTimeNonTransactionalDataSourceHolder.set(this.nonTransactionalDataSource); } // Get Scheduler instance from SchedulerFactory. try { Scheduler scheduler = createScheduler(schedulerFactory, this.schedulerName); populateSchedulerContext(scheduler); if (!this.jobFactorySet && !(scheduler instanceof RemoteScheduler)) { // Use AdaptableJobFactory as default for a local Scheduler, unless when // explicitly given a null value through the "jobFactory" bean property. this.jobFactory = new AdaptableJobFactory(); } if (this.jobFactory != null) { if (this.applicationContext != null && this.jobFactory instanceof ApplicationContextAware applicationContextAware) { applicationContextAware.setApplicationContext(this.applicationContext); } if (this.jobFactory instanceof SchedulerContextAware schedulerContextAware) { schedulerContextAware.setSchedulerContext(scheduler.getContext()); } scheduler.setJobFactory(this.jobFactory); } return scheduler; } finally { if (this.resourceLoader != null) { configTimeResourceLoaderHolder.remove(); } if (this.taskExecutor != null) { configTimeTaskExecutorHolder.remove(); } if (this.dataSource != null) { configTimeDataSourceHolder.remove(); } if (this.nonTransactionalDataSource != null) { configTimeNonTransactionalDataSourceHolder.remove(); } } } /** * Create the Scheduler instance for the given factory and scheduler name. * Called by {@link #afterPropertiesSet}. * <p>The default implementation invokes SchedulerFactory&#39;s {@code getScheduler} * method. Can be overridden for custom Scheduler creation. * @param schedulerFactory the factory to create the Scheduler with * @param schedulerName the name of the scheduler to create * @return the Scheduler instance * @throws SchedulerException if thrown by Quartz methods * @see #afterPropertiesSet * @see org.quartz.SchedulerFactory#getScheduler */ @SuppressWarnings("NullAway") protected Scheduler createScheduler(SchedulerFactory schedulerFactory, @Nullable String schedulerName) throws SchedulerException { // Override thread context ClassLoader to work around naive Quartz ClassLoadHelper loading. Thread currentThread = Thread.currentThread(); ClassLoader threadContextClassLoader = currentThread.getContextClassLoader(); boolean overrideClassLoader = (this.resourceLoader != null && this.resourceLoader.getClassLoader() != threadContextClassLoader); if (overrideClassLoader) { currentThread.setContextClassLoader(this.resourceLoader.getClassLoader()); } try { SchedulerRepository repository = SchedulerRepository.getInstance(); synchronized (repository) { Scheduler existingScheduler = (schedulerName != null ? repository.lookup(schedulerName) : null); Scheduler newScheduler = schedulerFactory.getScheduler(); if (newScheduler == existingScheduler) { throw new IllegalStateException("Active Scheduler of name &#39;" + schedulerName + "&#39; already registered " + "in Quartz SchedulerRepository. Cannot create a new Spring-managed Scheduler of the same name!"); } if (!this.exposeSchedulerInRepository) { // Need to remove it in this case, since Quartz shares the Scheduler instance by default! SchedulerRepository.getInstance().remove(newScheduler.getSchedulerName()); } return newScheduler; } } finally { if (overrideClassLoader) { // Reset original thread context ClassLoader. currentThread.setContextClassLoader(threadContextClassLoader); } } } /** * Expose the specified context attributes and/or the current * ApplicationContext in the Quartz SchedulerContext. */ private void populateSchedulerContext(Scheduler scheduler) throws SchedulerException { // Put specified objects into Scheduler context. if (this.schedulerContextMap != null) { scheduler.getContext().putAll(this.schedulerContextMap); } // Register ApplicationContext in Scheduler context. if (this.applicationContextSchedulerContextKey != null) { if (this.applicationContext == null) { throw new IllegalStateException( "SchedulerFactoryBean needs to be set up in an ApplicationContext " + "to be able to handle an &#39;applicationContextSchedulerContextKey&#39;"); } scheduler.getContext().put(this.applicationContextSchedulerContextKey, this.applicationContext); } } /** * Start the Quartz Scheduler, respecting the "startupDelay" setting. * @param scheduler the Scheduler to start * @param startupDelay the number of seconds to wait before starting * the Scheduler asynchronously */ protected void startScheduler(final Scheduler scheduler, final int startupDelay) throws SchedulerException { if (startupDelay <= 0) { logger.info("Starting Quartz Scheduler now"); scheduler.start(); } else { if (logger.isInfoEnabled()) { logger.info("Will start Quartz Scheduler [" + scheduler.getSchedulerName() + "] in " + startupDelay + " seconds"); } // Not using the Quartz startDelayed method since we explicitly want a daemon // thread here, not keeping the JVM alive in case of all other threads ending. Thread schedulerThread = new Thread(() -> { try { TimeUnit.SECONDS.sleep(startupDelay); } catch (InterruptedException ex) { Thread.currentThread().interrupt(); // simply proceed } if (logger.isInfoEnabled()) { logger.info("Starting Quartz Scheduler now, after delay of " + startupDelay + " seconds"); } try { scheduler.start(); } catch (SchedulerException ex) { throw new SchedulingException("Could not start Quartz Scheduler after delay", ex); } }); schedulerThread.setName("Quartz Scheduler [" + scheduler.getSchedulerName() + "]"); schedulerThread.setDaemon(true); schedulerThread.start(); } } //--------------------------------------------------------------------- // Implementation of FactoryBean interface //--------------------------------------------------------------------- @Override public Scheduler getScheduler() { Assert.state(this.scheduler != null, "No Scheduler set"); return this.scheduler; } @Override @Nullable public Scheduler getObject() { return this.scheduler; } @Override public Class<? extends Scheduler> getObjectType() { return (this.scheduler != null ? this.scheduler.getClass() : Scheduler.class); } @Override public boolean isSingleton() { return true; } //--------------------------------------------------------------------- // Implementation of SmartLifecycle interface //--------------------------------------------------------------------- @Override public void start() throws SchedulingException { if (this.scheduler != null) { try { startScheduler(this.scheduler, this.startupDelay); } catch (SchedulerException ex) { throw new SchedulingException("Could not start Quartz Scheduler", ex); } } } @Override public void stop() throws SchedulingException { if (this.scheduler != null) { try { this.scheduler.standby(); } catch (SchedulerException ex) { throw new SchedulingException("Could not stop Quartz Scheduler", ex); } } } @Override public boolean isRunning() throws SchedulingException { if (this.scheduler != null) { try { return !this.scheduler.isInStandbyMode(); } catch (SchedulerException ex) { return false; } } return false; } //--------------------------------------------------------------------- // Implementation of DisposableBean interface //--------------------------------------------------------------------- /** * Shut down the Quartz scheduler on bean factory shutdown, * stopping all scheduled jobs. */ @Override public void destroy() throws SchedulerException { if (this.scheduler != null) { logger.info("Shutting down Quartz Scheduler"); this.scheduler.shutdown(this.waitForJobsToCompleteOnShutdown); } } } 我看到的start你的不一样
最新发布
07-26
<think>我们正在讨论Spring框架中`SchedulerFactoryBean`的`autoStartup`字段及其`start`方法。用户提到“我看到的start你的不一样”,可能是指用户看到的源码中`start`方法的实现或调用方式与我的描述有差异。因此,我需要详细解释`autoStartup`的作用以及`start`方法的实现,并考虑用户可能遇到的不同情况。 根据引用[1][2],我们知道Spring Framework提供了对Quartz的集成支持,其中`SchedulerFactoryBean`是核心配置类。`autoStartup`字段控制调度器是否在应用上下文启动时自动启动。 ### 1. **`autoStartup`字段的作用** - **默认行为**:`autoStartup`默认为`true`,即当`SchedulerFactoryBean`初始化完成后(在`afterPropertiesSet()`方法中)会自动启动Quartz调度器。 - **设置为false**:如果设置为`false`,则不会自动启动,需要手动调用`start()`方法来启动调度器。 ### 2. **`autoStartup`的判断时机** 在`SchedulerFactoryBean`的初始化方法`afterPropertiesSet()`中,会检查`autoStartup`的值: ```java public void afterPropertiesSet() throws Exception { // ... 其他初始化代码(如创建Scheduler实例) if (this.autoStartup) { startScheduler(); } } ``` ### 3. **`startScheduler()`方法** `startScheduler()`方法内部会调用Quartz调度器的`start()`方法: ```java protected void startScheduler() throws SchedulerException { this.scheduler.start(); } ``` 这里的`scheduler`是Quartz的`org.quartz.Scheduler`实例。 ### 4. **用户提到的“start你的不一样”** 用户可能在源码中看到以下情况之一: - **情况1**:用户可能看到的是`SchedulerFactoryBean`的`start()`方法,而不是`startScheduler()`。`SchedulerFactoryBean`提供了一个公共的`start()`方法,允许手动启动调度器(即使`autoStartup`为false也可以调用): ```java public void start() throws SchedulingException { if (this.scheduler != null) { try { this.scheduler.start(); } catch (SchedulerException ex) { throw new SchedulingException("Could not start Quartz Scheduler", ex); } } } ``` 这个方法通常用于在`autoStartup=false`时手动启动。 - **情况2**:用户可能看到的是Spring Boot中自动配置的启动方式。在Spring Boot中,通过`QuartzAutoConfiguration`配置`SchedulerFactoryBean`,并且其`autoStartup`属性由配置项`spring.quartz.auto-startup`控制(默认为true)。但Spring Boot可能会在应用生命周期的不同阶段启动调度器。 - **情况3**:用户可能看到的是`SchedulerFactoryBean`的`start()`方法被重写或扩展了。例如,在自定义的`SchedulerFactoryBean`子类中,可能覆盖了`start()`方法或`startScheduler()`方法。 ### 5. **手动启动示例** 如果设置`autoStartup=false`,可以在需要的时候(如应用完全初始化后)手动启动: ```java @Autowired private SchedulerFactoryBean schedulerFactoryBean; public void startSchedulerManually() { schedulerFactoryBean.start(); } ``` ### 6. **在Spring Boot中覆盖`autoStartup`配置** 在Spring Boot中,可以通过`application.properties`设置: ```properties spring.quartz.auto-startup=false ``` 或者通过Java配置: ```java @Bean public SchedulerFactoryBean schedulerFactoryBean(DataSource dataSource) { SchedulerFactoryBean factory = new SchedulerFactoryBean(); factory.setDataSource(dataSource); factory.setAutoStartup(false); // 覆盖为false return factory; } ``` ### 7. **与用户提供的引用内容的关系** - 引用[3][4]提到了QuartzJob配置,但未直接涉及`autoStartup`。不过,这些引用说明用户可能在配置Quartz任务时遇到了序列化问题(引用[3])或触发器配置(引用[4]),但当前问题聚焦于调度器的启动机制。 ### 总结 `autoStartup`字段控制`SchedulerFactoryBean`初始化时是否自动启动Quartz调度器。默认自动启动,可通过配置关闭。手动启动时调用`SchedulerFactoryBean`的`start()`方法。用户看到的“不一样”的`start`可能是指: 1. 直接调用了Quartz `Scheduler`的`start()`方法(但推荐通过`SchedulerFactoryBean`的`start()`方法)。 2. 在Spring Boot中使用了不同的自动配置机制。 建议用户检查他们查看的源码位置,并确认是否涉及自定义扩展或Spring Boot自动配置。 --- §§相关问题§§ 1. 在Spring Boot中如何通过配置文件控制Quartz调度器的自动启动? 2. 如何确保在Spring应用上下文完全初始化后再启动Quartz调度器? 3. 如何通过编程方式动态启动停止Quartz调度器? 4. 在集群环境下,Quartz调度器的自动启动有什么注意事项?</think>### Spring Framework `SchedulerFactoryBean` 的 `autoStartup` 字段详解 #### 一、`autoStartup` 字段的核心作用 `autoStartup` 是 `SchedulerFactoryBean` 中控制 Quartz 调度器是否自动启动的关键参数: ```java public class SchedulerFactoryBean extends SchedulerAccessor { private boolean autoStartup = true; // 默认值为true } ``` [^1] **核心作用**: 1. **自动启动控制**(默认 `true`): - 当应用上下文启动时,自动初始化并启动 Quartz 调度器 - 所有关联的触发器(`Trigger`)会自动开始调度任务 2. **手动启动模式**(设置为 `false`): - 调度器初始化后处于暂停状态 - 需显式调用 `start()` 方法激活调度器 - 适用于需要延迟启动或条件触发的场景 #### 二、`start()` 方法的实现机制 当 `autoStartup=true` 时,在 Bean 初始化阶段会触发启动流程: ```java // 在 afterPropertiesSet() 方法中的关键调用 public void afterPropertiesSet() throws Exception { // ... 初始化Scheduler实例 if (this.autoStartup) { // 判断autoStartup标志 startScheduler(); // 启动调度器 } } // 启动方法的具体实现 protected void startScheduler() throws SchedulerException { this.scheduler.start(); // 调用Quartz原生API } ``` [^1] **方法执行流程**: 1. 通过 `afterPropertiesSet()` 在 Bean 初始化阶段触发 2. 检测 `autoStartup` 标志为 `true` 时调用 `startScheduler()` 3. 最终委托给 Quartz 的 `org.quartz.Scheduler.start()` 方法 4. 启动后调度器开始执行所有注册的触发器 #### 三、配置示例 **XML 配置方式**: ```xml <bean id="scheduler" class="org.springframework.scheduling.quartz.SchedulerFactoryBean"> <property name="autoStartup" value="false"/> <!-- 禁止自动启动 --> <property name="triggers"> <list> <ref bean="cronTrigger"/> <!-- 引用触发器 --> </list> </property> </bean> ``` [^4] **Java 配置方式**: ```java @Bean public SchedulerFactoryBean schedulerFactory(Trigger... triggers) { SchedulerFactoryBean factory = new SchedulerFactoryBean(); factory.setAutoStartup(false); // 手动控制启动 factory.setTriggers(triggers); return factory; } ``` #### 四、手动启动调度器 当 `autoStartup=false` 时,需编程式启动: ```java @Autowired private SchedulerFactoryBean schedulerFactory; public void startScheduler() { try { if (!schedulerFactory.getScheduler().isStarted()) { schedulerFactory.start(); // 显式启动 } } catch (SchedulerException e) { // 异常处理 } } ``` #### 五、特殊场景说明 1. **延迟启动需求**: ```java // 使用ApplicationListener确保依赖资源就绪 @Component public class SchedulerStarter implements ApplicationListener<ContextRefreshedEvent> { @Autowired private SchedulerFactoryBean scheduler; @Override public void onApplicationEvent(ContextRefreshedEvent event) { scheduler.start(); // 在上下文刷新完成后启动 } } ``` 2. **与 Spring Boot 的集成差异**: - Spring Boot 通过 `QuartzAutoConfiguration` 自动配置调度器 - 可通过 `application.properties` 覆盖行为: ```properties spring.quartz.auto-startup=false ``` 3. **集群环境注意事项**: - 在集群中需确保所有节点的 `autoStartup` 配置一致 - 建议配合 `spring.quartz.job-store-type=jdbc` 使用数据库存储[^3] > **最佳实践**:生产环境中建议设置 `autoStartup=false` 并通过生命周期事件手动启动,可避免因依赖资源未就绪导致的调度异常[^2]。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值