SpringBoot定時任務(schedule、quartz)


Scheduled

  只適合處理簡單的計划任務,不能處理分布式計划任務。優勢:是spring框架提供的計划任務,開發簡單,執行效率比較高。且在計划任務數量太多的時候,可能出現阻塞,崩潰,延遲啟動等問題。
  Scheduled定時任務是spring3.0版本之后自帶的一個定時任務。其所屬Spring的資源包為:spring-context-support。所以需要使用Scheduled定時任務機制時,需要在工程中依賴對應資源,具體如下:

<!-- scheduled所屬資源為spring-context-support -->
<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-context-support</artifactId>
</dependency>

  如果在spring應用中需要啟用Scheduled定時任務,則需要在啟動類上增加注解@EnableScheduling,代表啟用Scheduled定時任務機制。具體如下:

@SpringBootApplication
@EnableScheduling public class AppStarter {

  public static void main(String[] args) {
    SpringApplication.run(AppStarter.class, args);
  }
}

  Scheduled定時任務的核心在於注解@Scheduled,這個注解的核心屬性是cron,代表定時任務的觸發計划表達式。這個表達式的格式為:

@Scheduled(cron="seconds minutes hours day month week")

  或

@Scheduled(cron="seconds minutes hours day month week year")

  推薦使用第一種表達式形式,因為在很多其他技術中都有不同的定時任務機制,其中用於設置觸發計划的表達式都是第一種cron表達式。第二種表達式不能說是Spring Scheduled特有的,也是只有少數技術支持的。

  cron表達式中,每個位置的約束如下:

星號(*):可用在所有字段中,表示對應時間域的每一個時刻,例如,*在分鍾字段時,表示“每分鍾”;
問號(?):該字符只在日期和星期字段中使用,它通常指定為“無意義的值”,相當於占位符;
減號(-):表達一個范圍,如在小時字段中使用“10-12”,則表示從10到12點,即10,11,12;
逗號(,):表達一個列表值,如在星期字段中使用“MON,WED,FRI”,則表示星期一,星期三和星期五;
斜杠(/):x/y表達一個等步長序列,x為起始值,y為增量步長值。如在秒數字段中使用0/15,則表示為0,15,30和45秒,而5/15在分鍾字段中表示5,20,35,50,你也可以使用*/y,它等同於0/y;

  L:該字符只在日期和星期字段中使用,代表“Last”的意思,但它在兩個字段中意思不同。L在日期字段中,表示這個月份的最后一天,如一月的31號,非閏年二月的28號;如果L用在星期中,則表示星期六,等同於7。但是,如果L出現在星期字段里,而且在前面有一個數值X,則表示“這個月的最后X天”,例如,6L表示該月的最后星期五;

  W:該字符只能出現在日期字段里,是對前導日期的修飾,表示離該日期最近的工作日。例如15W表示離該月15號最近的工作日,如果該月15號是星期六,則匹配14號星期五;如果15日是星期日,則匹配16號星期一;如果15號是星期二,那結果就是15號星期二。但必須注意關聯的匹配日期不能夠跨月,如你指定1W,如果1號是星期六,結果匹配的是3號星期一,而非上個月最后的那天。W字符串只能指定單一日期,而不能指定日期范圍;

  LW組合:在日期字段可以組合使用LW,它的意思是當月的最后一個工作日;

  井號(#):該字符只能在星期字段中使用,表示當月某個工作日。如6#3表示當月的第三個星期五(6表示星期五,#3表示當前的第三個),而4#5表示當月的第五個星期三,假設當月沒有第五個星期三,忽略不觸發;

  C:該字符只在日期和星期字段中使用,代表“Calendar”的意思。它的意思是計划所關聯的日期,如果日期沒有被關聯,則相當於日歷中所有日期。例如5C在日期字段中就相當於日歷5日以后的第一天。1C在星期字段中相當於星期日后的第一天。

  Cron表達式對特殊字符的大小寫不敏感,對代表星期的縮寫英文大小寫也不敏感。

  計划任務Scheduled是通過一個線程池實現的。是一個多線程的調度。SpringBoot會初始化一個線程池,線程池默認大小為1,專門用於執行計划任務。每個計划任務啟動的時候,都從線程池中獲取一個線程執行,如果發生異常,只是執行當前任務的線程發生異常,而不是計划任務調度線程發生異常。如果當前定時任務還未執行完成,當相同的定時任務又進入到執行周期時,不會觸發新的定時任務。如:

@Scheduled(cron="* * * * * ?")
public void test1(){
    Random r = new Random();
    /*int i = r.nextInt(100);
    if(i % 3 == 0){
        throw new RuntimeException("error");
    }*/
    System.out.println(Thread.currentThread().getName() + " cron=* * * * * ? --- " + new Date());
    try{
        Thread.sleep(2000);
    }catch(Exception e){
        e.printStackTrace();
    }
}

  如結果所示(每次的線程名稱一致,由於前一個定時任務未執行完成,因此造成后一個任務的推遲,而不是1秒執行一次,而是3秒):

pool-1-thread-1 cron=* * * * * ? --- Thu Sep 19 02:23:20 CST 2019 pool-1-thread-1 cron=* * * * * ? --- Thu Sep 19 02:23:23 CST 2019 pool-1-thread-1 cron=* * * * * ? --- Thu Sep 19 02:23:26 CST 2019 pool-1-thread-1 cron=* * * * * ? --- Thu Sep 19 02:23:29 CST 2019

quartz

  Quartz是OpenSymphony開源組織在Job scheduling領域又一個開源項目,它可以與J2EE與J2SE應用程序相結合也可以單獨使用。Quartz可以用來創建簡單或為運行十個,百個,甚至是好幾萬個Jobs這樣復雜的程序。

  Quartz是一個完全由java編寫的開源作業調度框架。不要讓作業調度這個術語嚇着你。盡管Quartz框架整合了許多額外功能, 但就其簡易形式看,你會發現它易用得簡直讓人受不了!
在開發Quartz相關應用時,只要定義了Job(任務),Trigger(觸發器)和Scheduler(調度器),即可實現一個定時調度能力。其中Scheduler是Quartz中的核心,Scheduler負責管理Quartz應用運行時環境,Scheduler不是靠自己完成所有的工作,是根據Trigger的觸發標准,調用Job中的任務執行邏輯,來完成完整的定時任務調度。
  Job - 定時任務內容是什么。
  Trigger - 在什么時間上執行job。
  Scheduler - 維護定時任務環境,並讓觸發器生效。
  在SpringBoot中應用Quartz,需要依賴下述資源:

<dependencies>
        <!-- scheduled所屬資源為spring-context-support,在Spring中對Quartz的支持,是集成在spring-context-support包中。
            org.springframework.scheduling.quartz
         -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context-support</artifactId>
        </dependency>
        <!-- Quartz坐標 -->
        <dependency>
            <groupId>org.quartz-scheduler</groupId>
            <artifactId>quartz</artifactId>
            <version>2.2.1</version>
            <!-- Quartz默認需要slf4j支持。springboot中,提供了更高版本的slf4j -->
            <exclusions>
                <exclusion>
                    <artifactId>slf4j-api</artifactId>
                    <groupId>org.slf4j</groupId>
                </exclusion>
            </exclusions>
        </dependency>
        <!-- Spring tx 坐標,quartz可以提供分布式定時任務環境。多個分布點上的Quartz任務,是通過數據庫實現任務信息傳遞的。
            通過數據庫中的數據,保證一個時間點上,只有一個分布環境執行定時任務。
         -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-tx</artifactId>
        </dependency>
</dependencies>

  啟動器添加注解@EnableScheduling:

/**
 * @EnableScheduling 必要
 * 開啟定時任務機制。
 */
@SpringBootApplication
@EnableScheduling public class AppStarter {

    public static void main(String[] args) {
        SpringApplication.run(AppStarter.class, args);
    }
}

  定義JOB任務以及JOB任務調用的模擬業務對象:

 1 public class SpringBootQuartzJobDemo implements Job {
 2 
 3     // 用於模擬任務中的業務對象。 也可能是數據訪問對象,或其他類型的對象。
 4     @Autowired
 5     private CommonsUtil4Quartz commonsUtil4Quartz;
 6     
 7     @Override
 8     public void execute(JobExecutionContext context) throws JobExecutionException {
 9         System.out.println("SpringBootQuartzJobDemo : " + new Date());
10         this.commonsUtil4Quartz.testMethod(); 11     }
12 
13 }
@Component
public class CommonsUtil4Quartz {

    public void testMethod(){
        System.out.println("CommonsUtil4Quartz testMethod run...");
    }
}

  創建Trigger以及JobDetail對象,並用Schedule配置定時任務:

/**
 * 初始化類
 * Quartz環境初始化。
 * 
 */
@Configuration
public class QuartzConfiguration {

    /**
     * 創建Job對象。在Spring環境中,創建一個類型的對象的時候,很多情況下,都是通過FactoryBean來間接創建的。
     * 如果有多個Job對象,定義多次方法。
     * 
     * 在JobDetailFactoryBean類型中,用於創建JobDetail對象的方法,其底層使用的邏輯是:Class.newInstance()
     * 也就是說,JobDetail對象不是通過Spring容器管理的。
     * 因為Spring容器不管理JobDetail對象,那么Job中需要自動裝配的屬性,就無法實現自動狀態。如上JOB的第10行會報空指針異常。
     * 
     * 解決方案是: 將JobDetail加入到Spring容器中,讓Spring容器管理JobDetail對象。
     * 需要重寫Factory相關代碼。實現Spring容器管理JobDetail。
     * @return
     */
    @Bean
    public JobDetailFactoryBean initJobDetailFactoryBean(){
        JobDetailFactoryBean factoryBean = 
                new JobDetailFactoryBean();
        // 提供job類型。
        factoryBean.setJobClass(SpringBootQuartzJobDemo.class);
        
        return factoryBean;
    }
    
    /**
     * 管理Trigger對象
     * CronTrigger - 就是Trigger的一個實現類型。 其中用於定義周期時間的是CronSchedulerBuilder
     * 實際上,CronTrigger是用於管理一個Cron表達式的類型。
     * @param jobDetailFactoryBean - 上一個方法初始化的JobDetailFactoryBean
     * @return
     */
    @Bean(name="cronTriggerFactoryBean1")
    public CronTriggerFactoryBean initCronTriggerFactoryBean(
            ){
        CronTriggerFactoryBean factoryBean = 
                new CronTriggerFactoryBean();
        
        JobDetailFactoryBean jobDetailFactoryBean = this.initJobDetailFactoryBean();
        
        factoryBean.setJobDetail(jobDetailFactoryBean.getObject());
        
        factoryBean.setCronExpression("0/3 * * * * ?");
        
        return factoryBean;
    }
    
    /**
     * 初始化Scheduler
     * @param cronTriggerFactoryBean - 上一個方法初始化的CronTriggerFactoryBean
     * @return
     */
    @Bean
    public SchedulerFactoryBean initSchedulerFactoryBean(
            CustomJobFactory customJobFactory,
            CronTriggerFactoryBean[] cronTriggerFactoryBean){
        SchedulerFactoryBean factoryBean = 
                new SchedulerFactoryBean();
        CronTrigger[] triggers = new CronTrigger[cronTriggerFactoryBean.length];
        for(int i = 0; i < cronTriggerFactoryBean.length; i++){
            triggers[i] = cronTriggerFactoryBean[i].getObject();
        }
        // 注冊觸發器,一個Scheduler可以注冊若干觸發器。
        factoryBean.setTriggers(triggers);
        // 為Scheduler設置JobDetail的工廠。可以覆蓋掉SpringBoot提供的默認工廠,保證JobDetail中的自動裝配有效。
        factoryBean.setJobFactory(customJobFactory);
        
        return factoryBean;
    }
    
}

  重寫JobFactory:

/**
 * 重寫的工廠對象。
 */
@Component
public class CustomJobFactory extends AdaptableJobFactory {

    /**
     * AutowireCapableBeanFactory : 簡單理解為Spring容器,是Spring容器Context的一個Bean對象管理工程。
     * 可以實現自動裝配邏輯,和對象創建邏輯。
     * 是SpringIoC容器的一個重要組成部件。
     */
    @Autowired
    private AutowireCapableBeanFactory autowireCapableBeanFactory;
    
    @Override
    protected Object createJobInstance(TriggerFiredBundle bundle) throws Exception {
        // 通過父類型中的方法,創建JobDetail對象。
        Object obj = super.createJobInstance(bundle);
        // 將JobDetail對象加入到Spring容器中,讓Spring容器管理,並實現自動裝配邏輯。
        this.autowireCapableBeanFactory.autowireBean(obj);
        
        return obj;
    }

}

分布式quartz配置

  1、資源依賴配置:由於分布式的原因,Quartz中提供分布式處理的jar包以及數據庫及連接相關的依賴。

<dependencies>
        <!-- scheduled所屬資源為spring-context-support -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context-support</artifactId>
        </dependency>
        <!-- Quartz坐標 -->
        <dependency>
            <groupId>org.quartz-scheduler</groupId>
            <artifactId>quartz</artifactId>
            <version>2.2.1</version>
            <exclusions>
                <exclusion>
                    <artifactId>slf4j-api</artifactId>
                    <groupId>org.slf4j</groupId>
                </exclusion>
            </exclusions>
        </dependency>
        <!-- 是Quartz中提供分布式處理的jar包。 -->
        <dependency>
            <groupId>org.quartz-scheduler</groupId>
            <artifactId>quartz-jobs</artifactId>
            <version>2.2.1</version>
        </dependency>
        <!-- Sprng tx 坐標 -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-tx</artifactId>
        </dependency>
        <!-- web啟動器 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!--jdbc -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <!--mysql -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <!-- druid數據庫連接池 -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.0.9</version>
        </dependency>
</dependencies>

  2、提供數據庫相關配置:

spring.datasource.url=jdbc:mysql://localhost:3306/quartz?autoReconnect=true&useUnicode=true&characterEncoding=utf-8
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.initialSize=5
spring.datasource.minIdle=5
spring.datasource.maxActive=20
spring.datasource.maxWait=600000
spring.datasource.timeBetweenEvictionRunsMillis=600000
spring.datasource.minEvictableIdleTimeMillis=300000
spring.datasource.validationQuery=SELECT 1
spring.datasource.testWhileIdle=true
spring.datasource.testOnBorrow=false
spring.datasource.testOnReturn=false

server.port=8080

  3、提供Quartz配置信息,為Quartz提供數據庫配置信息,如數據庫,表格的前綴之類的。

# 是否使用properties作為數據存儲
org.quartz.jobStore.useProperties=false
# 數據庫中的表格命名前綴
org.quartz.jobStore.tablePrefix = QRTZ_
# 是否是一個集群,是不是分布式的任務
org.quartz.jobStore.isClustered = true
# 集群檢查周期,單位毫秒。可以自定義縮短時間。 當某一個節點宕機的時候,其他節點等待多久后開始執行任務。
org.quartz.jobStore.clusterCheckinInterval = 5000
# 單位毫秒, 集群中的節點退出后,再次檢查進入的時間間隔。
org.quartz.jobStore.misfireThreshold = 60000
# 事務隔離級別
org.quartz.jobStore.txIsolationLevelReadCommitted = true
# 存儲的事務管理類型
org.quartz.jobStore.class = org.quartz.impl.jdbcjobstore.JobStoreTX
# 使用的Delegate類型
org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.StdJDBCDelegate
# 集群的命名,一個集群要有相同的命名。
org.quartz.scheduler.instanceName = ClusterQuartz
# 節點的命名,可以自定義。 AUTO代表自動生成。
org.quartz.scheduler.instanceId= AUTO
# rmi遠程協議是否發布
org.quartz.scheduler.rmi.export = false
# rmi遠程協議代理是否創建
org.quartz.scheduler.rmi.proxy = false
# 是否使用用戶控制的事務環境觸發執行job。
org.quartz.scheduler.wrapJobExecutionInUserTransaction = false

  4、初始化數據庫

  建表語句可以自己在官方網站中查找(Quartz-lib中),使用tables-mysql.sql建表。

  5、定義JOB類

  啟動器和普通quartz無差異,但是JOB自身定義有些許差異:

/**
 * 使用Spring提供的Quartz相關Job類型實現Job的定義。
 * 父類型QuartzJobBean中,提供了分布式環境中任務的配置定義。 * 保證分布式環境中的任務是有效的。 */
@PersistJobDataAfterExecution // 當job執行結束,持久化job信息到數據庫
@DisallowConcurrentExecution // 保證job的唯一性(單例)
public class SpringBootQuartzJobDemo extends QuartzJobBean {

    @Override
    protected void executeInternal(JobExecutionContext context) throws JobExecutionException {
        System.out.println("SpringBootQuartzJobDemo : " + new Date());
    }
}

   6、QuartzConfiguration類型定義

@Configuration
public class QuartzConfiguration {

    @Autowired private DataSource dataSource; /**
     * 創建調度器, 可以省略的。
     * @return
     * @throws Exception
     */
    @Bean
    public Scheduler scheduler() throws Exception {
        Scheduler scheduler = schedulerFactoryBean().getScheduler();
        scheduler.start();
        return scheduler;
    }

    /**
     * 創建調度器工廠bean對象。
     * @return
     * @throws IOException
     */
    @Bean
    public SchedulerFactoryBean schedulerFactoryBean() throws IOException {
        SchedulerFactoryBean factory = new SchedulerFactoryBean();

        factory.setSchedulerName("Cluster_Scheduler");
        factory.setDataSource(dataSource);
        factory.setApplicationContextSchedulerContextKey("applicationContext");
        // 設置調度器中的線程池。
        factory.setTaskExecutor(schedulerThreadPool());
        // 設置觸發器
        factory.setTriggers(trigger().getObject());
        // 設置quartz的配置信息
        factory.setQuartzProperties(quartzProperties());
        return factory;
    }

    /**
     * 讀取quartz.properties配置文件的方法。
     * @return
     * @throws IOException
     */
    @Bean
    public Properties quartzProperties() throws IOException {
        PropertiesFactoryBean propertiesFactoryBean = new PropertiesFactoryBean();
        propertiesFactoryBean.setLocation(new ClassPathResource("/quartz.properties"));

        //quartz.properties中的屬性被讀取並注入后再初始化對象
        propertiesFactoryBean.afterPropertiesSet();
        return propertiesFactoryBean.getObject();
    }

    /**
     * 創建Job對象的方法。
     * @return
     */
    @Bean
    public JobDetailFactoryBean job() {
        JobDetailFactoryBean jobDetailFactoryBean = new JobDetailFactoryBean();         jobDetailFactoryBean.setJobClass(SpringBootQuartzJobDemo.class);
        // 是否持久化job內容
        jobDetailFactoryBean.setDurability(true);
        // 設置是否多次請求嘗試任務。
        jobDetailFactoryBean.setRequestsRecovery(true);

        return jobDetailFactoryBean;
    }

    /**
     * 創建trigger factory bean對象。
     * @return
     */
    @Bean
    public CronTriggerFactoryBean trigger() {
        CronTriggerFactoryBean cronTriggerFactoryBean = new CronTriggerFactoryBean();

        cronTriggerFactoryBean.setJobDetail(job().getObject());
        cronTriggerFactoryBean.setCronExpression("0/2 * * * * ?");

        return cronTriggerFactoryBean;
    }

    /**
     * 創建一個調度器的線程池。
     * @return
     */
    @Bean
    public Executor schedulerThreadPool() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();

        executor.setCorePoolSize(15);
        executor.setMaxPoolSize(25);
        executor.setQueueCapacity(100);

        return executor;
    }
}

  若JOB任務有定義調用業務等內容,也需要重寫JobFactory,如上述常規quartz,此處不再贅述。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM