溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

如何在Spring Boot中支持Crontab任務(wù)改造

發(fā)布時間:2021-05-26 10:38:49 來源:億速云 閱讀:187 作者:Leah 欄目:編程語言

本篇文章為大家展示了如何在Spring Boot中支持Crontab任務(wù)改造,內(nèi)容簡明扼要并且容易理解,絕對能使你眼前一亮,通過這篇文章的詳細介紹希望你能有所收獲。

1. 監(jiān)聽目標對象

借助容器刷新事件來監(jiān)聽目標對象即可,可以認為,定時任務(wù)其實每次只是執(zhí)行一種操作而已。

比如這是一個寫好的例子,注意不要直接用 @Service 將其放入容器中,除非容器本身沒有其它自動運行的事件。

package com.github.zhgxun.learn.common.task;

import com.github.zhgxun.learn.common.task.annotation.ScheduleTask;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.SpringApplication;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextRefreshedEvent;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * 不自動加入容器, 用于區(qū)分是否屬于任務(wù)啟動, 否則放入容器中, Spring 無法選擇性執(zhí)行
 * 需要根據(jù)特殊參數(shù)在啟動時注入
 * 該監(jiān)聽器本身不能訪問容器變量, 如果需要訪問, 需要從上下文中獲取對象實例后方可繼續(xù)訪問實例信息
 * 如果其它類中啟動了多線程, 是無法接管異常拋出的, 需要子線程中正確處理退出操作
 * 該監(jiān)聽器最好不用直接做線程操作, 子類的實現(xiàn)不干預(yù)
 */
@Slf4j
public class TaskApplicationListener implements ApplicationListener<ContextRefreshedEvent> {
  /**
   * 任務(wù)啟動監(jiān)聽類標識, 啟動時注入
   * 即是 java -Dspring.task.class=com.github.zhgxun.learn.task.TestTask -jar learn.jar
   */
  private static final String SPRING_TASK_CLASS = "spring.task.class";

  /**
   * 支持該注解的方法個數(shù), 目前僅一個
   * 可以理解為控制臺一次執(zhí)行一個類, 依賴的任務(wù)應(yīng)該通過其它方式控制依賴
   */
  private static final int SUPPORT_METHOD_COUNT = 1;

  /**
   * 保存當前容器運行上下文
   */
  private ApplicationContext context;

  /**
   * 監(jiān)聽容器刷新事件
   *
   * @param event 容器刷新事件
   */
  @Override
  @SuppressWarnings("unchecked")
  public void onApplicationEvent(ContextRefreshedEvent event) {
    context = event.getApplicationContext();
    // 不存在時可能為正常的容器啟動運行, 無需關(guān)心
    String taskClass = System.getProperty(SPRING_TASK_CLASS);
    log.info("ScheduleTask spring task Class: {}", taskClass);
    if (taskClass != null) {
      try {
        // 獲取類字節(jié)碼文件
        Class clazz = findClass(taskClass);

        // 嘗試從內(nèi)容上下文中獲取已加載的目標類對象實例, 這個類實例是已經(jīng)加載到容器內(nèi)的對象實例, 即可以獲取類的信息
        Object object = context.getBean(clazz);

        Method method = findMethod(object);

        log.info("start to run task Class: {}, Method: {}", taskClass, method.getName());
        invoke(method, object);
      } catch (ClassNotFoundException | IllegalAccessException | InvocationTargetException e) {
        e.printStackTrace();
      } finally {
        // 需要確保容器正常出發(fā)停止事件, 否則容器會僵尸卡死
        shutdown();
      }
    }
  }

  /**
   * 根據(jù)class路徑名稱查找類文件
   *
   * @param clazz 類名稱
   * @return 類對象
   * @throws ClassNotFoundException ClassNotFoundException
   */
  private Class findClass(String clazz) throws ClassNotFoundException {
    return Class.forName(clazz);
  }

  /**
   * 獲取目標對象中符合條件的方法
   *
   * @param object 目標對象實例
   * @return 符合條件的方法
   */
  private Method findMethod(Object object) {
    Method[] methods = object.getClass().getDeclaredMethods();
    List<Method> schedules = Stream.of(methods)
        .filter(method -> method.isAnnotationPresent(ScheduleTask.class))
        .collect(Collectors.toList());
    if (schedules.size() != SUPPORT_METHOD_COUNT) {
      throw new IllegalStateException("only one method should be annotated with @ScheduleTask, but found "
          + schedules.size());
    }
    return schedules.get(0);
  }

  /**
   * 執(zhí)行目標對象方法
   *
   * @param method 目標方法
   * @param object 目標對象實例
   * @throws IllegalAccessException  IllegalAccessException
   * @throws InvocationTargetException InvocationTargetException
   */
  private void invoke(Method method, Object object) throws IllegalAccessException, InvocationTargetException {
    method.invoke(object);
  }

  /**
   * 執(zhí)行完畢退出運行容器, 并將返回值交給執(zhí)行環(huán)節(jié), 比如控制臺等
   */
  private void shutdown() {
    log.info("shutdown ...");
    System.exit(SpringApplication.exit(context));
  }
}

其實該處僅需要啟動執(zhí)行即可,容器啟動完畢事件也是可以的。

2. 標識目標方法

目標方法的標識,最方便的是使用注解標注。

package com.github.zhgxun.learn.common.task.annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Documented
public @interface ScheduleTask {
}

3. 編寫任務(wù)

package com.github.zhgxun.learn.task;

import com.github.zhgxun.learn.common.task.annotation.ScheduleTask;
import com.github.zhgxun.learn.service.first.LaunchInfoService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.concurrent.TimeUnit;

@Service
@Slf4j
public class TestTask {

  @Autowired
  private LaunchInfoService launchInfoService;

  @ScheduleTask
  public void test() {
    log.info("Start task ...");
    log.info("LaunchInfoList: {}", launchInfoService.findAll());

    log.info("模擬啟動線程操作");
    for (int i = 0; i < 5; i++) {
      new MyTask(i).start();
    }

    try {
      TimeUnit.SECONDS.sleep(3);
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
  }
}

class MyTask extends Thread {
  private int i;
  private int j;
  private String s;

  public MyTask(int i) {
    this.i = i;
  }

  @Override
  public void run() {
    super.run();
    System.out.println("第 " + i + " 個線程啟動..." + Thread.currentThread().getName());
    if (i == 2) {
      throw new RuntimeException("模擬運行時異常");
    }
    if (i == 3) {
      // 除數(shù)不為0
      int a = i / j;
    }
    // 未對字符串對象賦值, 獲取長度報空指針錯誤
    if (i == 4) {
      System.out.println(s.length());
    }
  }
}

4. 啟動改造

啟動時需要做一些調(diào)整,即跟普通的啟動區(qū)分開。這也是為什么不要把監(jiān)聽目標對象直接放入容器中的原因,在這里顯示添加到容器中,這樣就不影響項目中類似 CommandLineRunner 的功能,畢竟這種功能是容器啟動完畢就能運行的。如果要改造,會涉及到很多硬編碼。

package com.github.zhgxun.learn;

import com.github.zhgxun.learn.common.task.TaskApplicationListener;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;

@SpringBootApplication
public class LearnApplication {

  public static void main(String[] args) {
    SpringApplicationBuilder builder = new SpringApplicationBuilder(LearnApplication.class);
    // 根據(jù)啟動注入?yún)?shù)判斷是否為任務(wù)動作即可, 否則不干預(yù)啟動
    if (System.getProperty("spring.task.class") != null) {
      builder.listeners(new TaskApplicationListener()).run(args);
    } else {
      builder.run(args);
    }
  }
}

5. 啟動注入

-Dspring.task.class 即是啟動注入標識,當然這個標識不要跟默認的參數(shù)混淆,需要區(qū)分開,否則可能始終獲取到系統(tǒng)參數(shù),而無法獲取用戶參數(shù)。

java -Dspring.task.class=com.github.zhgxun.learn.task.TestTask -jar target/learn.jar

springboot是什么

springboot一種全新的編程規(guī)范,其設(shè)計目的是用來簡化新Spring應(yīng)用的初始搭建以及開發(fā)過程,SpringBoot也是一個服務(wù)于框架的框架,服務(wù)范圍是簡化配置文件。

上述內(nèi)容就是如何在Spring Boot中支持Crontab任務(wù)改造,你們學到知識或技能了嗎?如果還想學到更多技能或者豐富自己的知識儲備,歡迎關(guān)注億速云行業(yè)資訊頻道。

向AI問一下細節(jié)

免責聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI