• 在 Spring 中使用 Quartz


    Spring 为创建 Quartz 的 Scheduler、Trigger 和 JobDetail 提供了便利的 FactoryBean 类,以便能够在 Spring 容器中享受注入的好处。此外,Spring 还提供了一些便利工具类,用于直接将 Spring 中的 Bean 包装成合法的任务。Spring 进一步降低了使用 Quartz 的难度,能以更具风格的方式使用 Quartz。概括来说,Spring 提供了两方面的支持。

    (1)为 Quartz 的重要组件类提供更具 Bean 风格的扩展类。

    (2)提供创建 Scheduler 的 BeanFactory 类,方便在 Spring 环境下创建对应的组件对象,并结合 Spring 容器生命周期执行启动和停止的动作。

    1.创建JobDetail

    用户可以直接使用 Quartz 的 JobDetail 在 Spring 中配置一个 JobDetail Bean,但是 JobDetail 使用带参的构造函数,对于习惯通过属性配置的 Spring 用户来说存在使用上的不便。为此,Spring 通过扩展 JobDetail 提供了一个更具 Bean 风格的 JobDetailFactoryBean。

    此外,Spring 还提供了一个 MethodInvokingJobDetailFactoryBean,通过这个 FactoryBean可以将 Spring 容器中 Bean 的方法包装成 Quartz 任务,这样开发者就不必为 Job 创建对应的类。

    1)JobDetailFactoryBean

    JobDetailFactoryBean 扩展于 Quartz 的 JobDetail。使用该 Bean 声明 JobDetail 时,Bean 的名字即任务的名字,如果没有指定所属组,就使用默认组。除了 JobDetail 中的属性外,还定义了以下属性。

    (1)jobClass:类型为 Class,实现 Job 接口的任务类。

    (2)beanName:默认为 Bean 的 id 名,通过该属性显式指定 Bean 名称,它对应任务的名称。

    (3)jobDataAsMap:类型为 Map,为任务所对应的 JobDataMap 提供值。之所以需要提供这个属性,是因为用户无法在 Spring 配置文件中为 JobDataMap 类型的属性提供信息,所以 Spring 通过 jobDataAsMap 设置 JobDataMap 的值。

    (4)applicationContextJobDataKey:用户可以将 SpringApplicationContext 的引用保存到 JobDataMap 中,以便在 Job 的代码中访问 ApplicationContext。为了达到这个目的,用户需要指定一个键,用于在 jobDataAsMap 中保存 ApplicationContext。如果不设置此键,JobDetailBean 就不会将 ApplicationContext 放入 JobDataMap 中。

    (5)jobListenerNames:类型为 String[],指定注册在 Scheduler 中的 JobListeners 名称,以便让这些监听器对本任务的事件进行监听。

    下面的配置片段使用 JobDetailBean 在 Spring 中配置一个 JobDetail。

    <bean name="jobDetail" class="org.springframework.scheduling.quartz.JobDetailBean"
      p:jobClass="com.smart.quartz.MyJob"
      p:applicationContextJobDataKey="applicationContext">
      <property name="jobDataAsMap">
        <map>
          <entry key="size" value="10" />
        </map>
      </property>
    </bean>

    JobDetaiIFactoryBean 封装了 MyJob 任务类,并为 Job 对应的 JobDataMap 设置了一个键为 size 的数据。此外,通过指定 applicationContextJobDataKey,让 Job 的 JobDataMap 持有 SpringApplicationContext 的引用。

    这样,MyJob 在运行时就可以通过 JobDataMap 访问到 size 和 ApplicationContext。来看一下 MyJob 的代码,如下面代码所示。

    public class MyJob implements StatefulJob {
        public void execute(JobExecutionContext jctx) throws JobExecutionException {
            Map dataMap = jctx.getJobDetail().getJobDataMap();//①获取JobDetail关联的JobDataMap
            //Map dataMap = jctx.getTrigger().getJobDataMap();
            String size =(String)dataMap.get("size");//
            ApplicationContext ctx = (ApplicationContext)dataMap.get("applicationContext");//
            System.out.println("size:"+size);
            dataMap.put("size",size+"0");//④对JobDataMap所做的更改是否会被持久化取决于任务的类型
            
            String count =(String)dataMap.get("count");
            System.out.println("count:"+count);
        }
    }

    在②处获取 size 值,在③处可以根据键 applicanonContext 获取 ApplicationContext,有了 ApplicationContext 的引用,Job 就可以毫无障碍地访问 Spring 容器中的任何 Bean。MyJob 可以在 execute() 方法中对 JobDataMap 进行更改,如④处所示。如果 MyJob 实现了 Job 接口,则这种更改对于下一次执行是不可见的;如果 MyJob 实现了 StatefulJob 接口,则这种更改对于下一次执行是可见的。

    2)MethodInvokingJobDetailFactoryBean

    通常情况下,任务都定义在一个业务类方法中。这时,为了满足 QuartzJob 接口的规定,还需要定义一个引用业务类方法的实现类。为了避免创建这个只包含一行调用代码的 Job 实现类,Spring 提供了 MethodInvokingJobDetailFactoryBean,借由该 FactoryBean,可以将一个 Bean 的某个方法封装成满足 Quartz 要求的 Job。来看一个具体的例子:

    <bean id="jobDetail_1"
      class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean"
      p:targetObject-ref="myService"  //①引用一个Bean
      p:targetMethod
    ="doJob" //②指定目标Bean的方法
      p:concurrent
    ="false" /> //③指定最终封装的任务是否有状态 <bean id="myService" class="com.smart.service.MyService" />

    jobDetail1_1 将 MyService#doJob() 封装成一个任务,同时通过 concurrent 属性指定任务的类型。默认情况下封装为无状态的任务。如果希望封装为有状态的任务,仅需将 concurrent 属性设置为 false 就可以了。Spring 通过名为 concurrent 的属性指定任务的类型,能够更直接地描述任务执行的方式(有状态的任务不能并发执行,无状态的任务可并发执行),对于不熟悉 Quartz 内部机制的用户来说,比起 stateful,concurrent 显然更简明达意些。

    Myservice 服务类拥有一个 doJob() 方法,它的代码如下所示:

    public class MyService {
       public void doJob(){//①被封装成任务的目标方法
           System.out.println("in MyService.dojob().");
       }
    }

    doJob() 方法既可以是 static 的,也可以是非 static 的,但不能拥有方法入参。通过 MethodInvokingJobDetailFactoryBean 产生的 JobDetail 不能被序列化,所以不能被持久化到数据库中。如果希望使用持久化任务,则用户只能创建正规的 Quartz 的 Job 实现类。

    2.创建Trigger

    Quartz 中另一个重要的组件就是 Trigger,Spring 按照相似的思路分别为 SimpleTrigger 和 CronTrigger 提供了更具 Bean 风格的 SimpleTriggerFactoryBean 和 CronTriggerFactoryBean 扩展类,通过这两个扩展类可以更容易地在 Spring 中以 Bean 的方式配置 Trigger。

    1)SimpleTriggerFactoryBean

    在默认情况下,通过 SimpleTriggerFactoryBean 配置的 Trigger 名称即为 Bean 的名称,属于默认组。 SimpleTriggerFactoryBean 在 SimpleTrigger 的基础上新增了以下属性。

    (1)jobDetail:对应的 JobDetail。

    (2)beanName:默认为 Bean 的 id 名,通过该属性显式指定Bean名称,它对应 Trigger 的名称。

    (3)jobDataAsMap:以 Map 类型为 Trigger 关联的 JobDataMap 提供值。

    (4)startDelay:延迟多少时间开始触发,单位为毫秒,默认值为0。

    (5)triggerListenerNames:类型为 String[],指定注册在 Scheduler 中的 TriggerListener 名称,以便让这些监听器对本触发器的事件进行监听。

    下面的实例使用 SimpleTriggerFactoryBean 定义了一个 Trigger,该 Trigger 和 jobDetail 相关联,延迟1秒后启动,时间间隔为2秒,重复执行100次。此外,还为 Trigger 设置了 JobDataMap 数据。

    <bean id="simpleTrigger" class="org.springframework.scheduling.quartz.SimpleTriggerBean"
      p:jobDetail-ref="jobDetail" 
      p:startDelay
    ="1000"
      p:repeatInterval
    ="2000"   p:repeatCount="100">   <property name="jobDataAsMap"> //①     <map>       <entry key="count" value="10" />     </map>   </property> </bean>

    需要特别注意的是,在①处配置的 Map 数据将填充到 Trigger 的 JobDataMap 中,执行任务时必须通过以下方式获取配置的值:

    public class MyJob implements StatefulJob {
        public void execute(JobExecutionContext jctx) throws JobExecutionException {
            Map dataMap = jctx.getTrigger().getJobDataMap();//①获取Trigger的JobDataMap
            
            String count =(String)dataMap.get("count");
            dataMap.put("count","30");//②对JobDataMap的更改不会被持久化,不影响下次的执行
        }
    }

    2)CronTriggerFactoryBean

    CronTriggerFactoryBean 扩展于 CronTrigger,触发器的名称即为 Bean 的名称,保存在默认组中。在  CronTrigger 的基础上,新增的属性和 SimpleTriggerFactoryBean 大致相同,配置的方法也和  SimpleTriggerFactoryBean 相似。下面给出一个简单的例子:

    <bean id="checkImagesTrigger" 
      class="org.springframework.scheduling.quartz.CronTriggerBean"
      p:jobDetail-ref="jobDetail"
      p:cronExpression="0/5 * * * * ?"/>

    3.创建Scheduler

    Quartz 的 SchedulerFactory 是标准的工厂类,不太适合在 Spring 环境下使用。此外,为了保证 Scheduler 能够感知 Spring 容器的生命周期,在 Spring 容器启动后,Scheduler 自动开始工作,而在 Spring 容器关闭前,自动关闭 Scheduler。为此,Spring 提供了 SchedulerFactoryBean,这个 FactoryBean 大致拥有以下功能。

    (1)以更具 Bean 风格的方式为 Scheduler 提供配置信息。

    (2)让 Scheduler 和 Spring 容器的生命周期建立关联,相生相息。

    (3)通过属性配置的方式代替 Quartz 自身的配置文件。

    来看一个 SchedulerFactoryBean 配置的例子,如下面代码所示。

    <bean id="scheduler"
      class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
      <property name="triggers"> //①注册多个Trigger
        <list>
          <ref bean="simpleTrigger" />
        </list>
      </property>
      <property name="schedulerContextAsMap">  //②以Map类型设置SchedulerContext数据
        <map>
          <entry key="timeout" value="30" />
        </map>
      </property>
      <property name="configLocation" 
            value="classpath:com/smart/quartz/quartz.properties" />//③显示指定Quartz的配置文件地址
    </bean>

    SchedulerFactoryBean 的 triggers 属性为 Trigger[] 类型,可以通过该属性注册多个 Trigger,在①处注册了一个 Trigger。Scheduler 拥有一个类似于 ServletContext 的 SchedulerContext。SchedulerFactoryBean 允许用户以  Map 的形式设置 SchedulerContext 的参数值,如②处所示。在默认情况下,Quartz 在类路径下查询  quartz.properties 配置文件,用户也可以通过 configLocation 属性显式指定配置文件的位置,如③处所示。

    除了实例中所用的属性外,SchedulerFactoryBean 还拥有一些常见的属性。

    (1)calendars:类型为 Map,通过该属性向 Scheduler 注册 Calendar。

    (2)jobDetails:类型为 JobDetail[],通过该属性向 Scheduler 注册 JobDetail。

    (3)autoStartup:SchedulerFactoryBean 在初始化后是否马上启动 Scheduler,默认为 true。如果设置为  false,则需要手工启动 Scheduler。

    (4)startupDelay:在 SchedulerFactoryBean 初始化完成后,延迟多少秒启动 Scheduler,默认值为0,表示马上启动。除非拥有需要立即执行的任务,一般情况下,可以通过属性让 Scheduler 延迟一小段时间后启动,以便让 Spring 能够更快初始化容器中剩余的 Bean。

    SchedulerFactoryBean 的一个重要功能是允许用户将 Quartz 配置文件中的信息转移到 Spring 配置文件中,由此带来的好处是配置信息的集中化管理,同时我们不必熟悉多种框架有差异的配置文件结构。回忆一个 Spring 集成的 Hibernate 框架,就知道这是 Spring 在集成第三方框架时采用的惯用招数。通过以下属性代替框架的自身配置文件。

    (1)datasource:当需要使用数据库来持久化任务调度数据时,用户可以在 Quartz 中配置数据源,也可以直接在 Spring 中通过 datasource 指定一个 Spring 管理的数据源。如果指定了该属性,那么,即使在 quartz.properties 中已经定义了数据源,也会被 dataSource 覆盖。

    (2)transactionManager:可以通过该属性设置一个 Spring 事务管理器。在设置 datasource 时,Spring 强烈推荐用户使用一个事务管理器,否则数据表锁定可能无法正常工作。

    (3)nonTransactionalDataSource:在全局事务的情况下,如果用户不希望 Scheduler 执行的相关数据操作参与到全局事务中,则可以通过该属性指定数据源。在 Spring 本地事务的情况下,使用 datasource 属性就足够了。

    (4)quartzProperties:类型为 Properties,允许用户在 Spring 中定义 Quartz 的属性,其值将覆盖  quartz.properties配置文件中的设置。这些属性必须是Quartz能够识别的合法属性,在配置时,需要查看 Quartz 的相关文档。

    下面是一个配置 quartz.properties 属性的例子:

    <bean id="scheduler"
      class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
      ...
      <property name="quartzProperties">  //①Quartz属性项1
        <props>
          <prop key="org.quartz.threadPool.class">
            org.quartz.simpl.SimpleThreadPool
          </prop>
          <prop key="org.quartz.threadPool.threadCount">10</prop>//②Quartz属性项2
        </props>
      </property>
    </bean>

    在实际应用中,我们并不总是在程序部署的时候就确定需要哪些任务,往往需要在运行期根据业务数据动态产生触发器和任务。用户完全可以在运行期通过代码调用 SchedulerFactoryBean 获取 Scheduler 实例,然后动态注册触发器和任务。

  • 相关阅读:
    关于使用quartz动态增删改定时任务
    关于chrome被篡改主页修复方法
    关于git被误删除的分支还原问题
    mysql数据库备份bat脚本
    同步数据库bat脚本
    读取spring boot项目中resource目录下的文件
    使用Java进行udp-demo编程时碰到的consumer和producter无法连接并报出“java.net.SocketException: Can't assign requested address”问题
    关于在项目中遇到MySQL数据库死锁的问题
    Gitlab仓库搭建及在Linux/windows中的免密使用
    GIT
  • 原文地址:https://www.cnblogs.com/jwen1994/p/11355356.html
Copyright © 2020-2023  润新知