Spring定時任務與XXL-JOB靈活切換方案

👉 這是一個或許對你有用的社群
🐱 一對一交流/面試小冊/簡歷最佳化/求職解惑,歡迎加入芋道快速開發平臺知識星球。下面是星球提供的部分資料:
👉這是一個或許對你有用的開源專案
國產 Star 破 10w+ 的開源專案,前端包括管理後臺 + 微信小程式,後端支援單體和微服務架構。
功能涵蓋 RBAC 許可權、SaaS 多租戶、資料許可權、商城、支付、工作流、大屏報表、微信公眾號、CRM 等等功能:
  • Boot 倉庫:https://gitee.com/zhijiantianya/ruoyi-vue-pro
  • Cloud 倉庫:https://gitee.com/zhijiantianya/yudao-cloud
  • 影片教程:https://doc.iocoder.cn
【國內首批】支援 JDK 21 + SpringBoot 3.2.2、JDK 8 + Spring Boot 2.7.18 雙版本 

背景

在使用XXL—JOB的實現定時任務過程中,有時候可能由於部署環境的要求,就只能用Spring自帶的實現方式。
所以為了通用性和靈活性,突發奇想地看看能不能實現在不修改原本Spring定時任務程式碼的前提下,透過配置靈活控制定時任務具體的實現,同時任務的日誌的管理也要同步進行切換。
基於 Spring Boot + MyBatis Plus + Vue & Element 實現的後臺管理系統 + 使用者小程式,支援 RBAC 動態許可權、多租戶、資料許可權、工作流、三方登入、支付、簡訊、商城等功能
  • 專案地址:https://github.com/YunaiV/ruoyi-vue-pro
  • 影片教程:https://doc.iocoder.cn/video/

分析並列出需要解決的問題思路

根據需求背景可以初步分析實現的大致方向和實現流程。實現的思路其實不復雜,重點在於如何具體去實現落地。
基於 Spring Cloud Alibaba + Gateway + Nacos + RocketMQ + Vue & Element 實現的後臺管理系統 + 使用者小程式,支援 RBAC 動態許可權、多租戶、資料許可權、工作流、三方登入、支付、簡訊、商城等功能
  • 專案地址:https://github.com/YunaiV/yudao-cloud
  • 影片教程:https://doc.iocoder.cn/video/

具體實現

判斷是否啟用XXl-JOB的實現方式
和大多數第三方starter包一樣,我們可以利用SpringBoot的自動裝配,讀取配置中的某個屬性值,作為是否裝配我們寫的類。特別注意的是SpringBoot不同版本的配置方式有所不同。
自動裝配類如下:

/**

 * 自動裝配類

 */


@Configuration
@ConditionalOnProperty

(name = 

"xxl.job.enable"

,havingValue = 

"true"

)

@ComponentScan

(

"com.teoan.job.auto.core"

)

publicclassXxlJobAutoConfiguration

{

}

這裡我們根據xxl.job.enable 的值,決定是否啟用XXl-JOB的實現方式,如果xxl.job.enable 為false,則就什麼都不裝配不修改實現方式,預設就是Spring自帶的實現方式。
掃描並讀取註解值
熟悉SpringBoot的的朋友都應該知道,SpringBoot啟動的時候,會去掃描目標註解,然後去做對應的初始化操作,比如@Service@Component就是使被掃描到並將對應的類注入到Spring容器中。所以我們可以按照相同的思路,可以在應用啟動就緒之後,掃描@Scheduled註解,對其進行對應的操作。
Spring中的@EventListener註解
Spring中使用@EventListener標記某個方法為應用監聽事件的處理邏輯,還能配合非同步註解@Async實現非同步觸發,@EventListener透過傳值的方式設定需要被監聽的事件型別,比如應用啟動時、應用就緒時、啟動失敗時等,具體有哪些監聽的事件,可以參考Spring原始碼包org.springframework.boot.context.event
現在,我們可以利用Spring提供的監聽註解,在應用啟動就緒後,掃描對應註解,去實現我們的程式碼邏輯,同時為了不影響程式的正常啟動速度,使用非同步執行的方式。
虛擬碼如下:
@Component
@Slf

4j

publicclassJobAutoRegister

{

@EventListener

(ApplicationReadyEvent

.

class

)

    @

Async
publicvoidonApplicationEvent

(

ApplicationReadyEventevent

{

// 執行掃描註解,自動註冊xxl-job任務邏輯

    }

}

掃描並獲取被@Scheduled標記的方法和物件
我們知道,使用@Scheduled註解對應的物件,必須是被Spring所託管的類,定時任務才會生效,所以我們可以掃描被@Component標記的類,再定位@Scheduled註解,獲取對應的值、物件、方法等資訊。
虛擬碼如下:
privatevoidaddJobInfo()

{

    List<Object> beanList = applicationContext.getBeansWithAnnotation(Component

.class).values().stream().toList()

;

    beanList.forEach(bean -> {

        Map<Method, Scheduled> annotatedMethods = MethodIntrospector.selectMethods(bean.getClass(),

                (MethodIntrospector.MetadataLookup<Scheduled>) method -> AnnotatedElementUtils.findMergedAnnotation(method, Scheduled

.class))

;

        annotatedMethods.forEach((k, v) -> {

// 停止Spring自帶的定時任務

// 自動註冊到xxl-job任務 

// 註冊xxl-job的任務

        });

    });

}

關閉Spring自帶的定時任務
ScheduledAnnotationBeanPostProcessor類是一個Spring框架的類,用於處理@Scheduled註解,實現定時任務的功能。我們可以透過這個類,對Spring中的定時任務進行一定的操作。
透過閱讀Spring原始碼,發現ScheduledAnnotationBeanPostProcessor有這麼一個方法postProcessBeforeDestruction,該方法實現DestructionAwareBeanPostProcessor介面,用於銷燬一個Bean的前置操作,而在ScheduledAnnotationBeanPostProcessor類中,這個方法的實現是取消某個Bean中的所有定時任務。具體可以看一下這個方法的原始碼。
@Override
publicvoidpostProcessBeforeDestruction(Object bean, String beanName)

{

 Set<ScheduledTask> tasks;

synchronized

 (

this

.scheduledTasks) {

  tasks = 

this

.scheduledTasks.remove(bean);

 }

if

 (tasks != 

null

) {

for

 (ScheduledTask task : tasks) {

   task.cancel();

  }

 }

}

由於我們上一步已經掃描獲取到被@Scheduled註解標記過方法,我們可以直接透過方法物件,獲取到對應的Bean,將Bean作為入參傳入postProcessBeforeDestruction方法中,關閉Spring自帶的定時任務。

/**

 * 停止Spring自帶的定時註解

 *

 * 

@param

 clazz 帶有定時註解的類

 */


privatevoidstopScheduled(Class<?> clazz)

{

    ScheduledAnnotationBeanPostProcessor processor = (ScheduledAnnotationBeanPostProcessor) applicationContext

            .getBean(

"org.springframework.context.annotation.internalScheduledAnnotationProcessor"

);

    processor.postProcessBeforeDestruction(applicationContext.getBean(clazz), 

""

);

}

讀取註解資訊並將任務自動註冊到XXl-JOB
有使用過XXL-JOB的小夥伴都清楚,在使用方法模式時,除了使用註解標記定時任務的方法,還需要在排程中心上進行任務的配置,定時任務才會生效。
目前我們已經獲取到 @Scheduled 註解的資訊,我們可以將 @Scheduled 所帶的資訊轉換為對應XXL-JOB上對應的任務型別,在啟動的時候自動地註冊到排程中心,簡化XXl-JOB任務排程的使用配置步驟。
註冊JobHandler
翻看XXl-JOB中關於@XxlJob的原始碼,發現會將@XxlJob所標記的方法,向排程中心註冊一個MethodJobHandler型別的JobHandler,表示方法模式對應的處理器。
入口程式碼及位置如下
com.xxl.job.core.executor.impl.XxlJobSpringExecutor#initJobHandlerMethodRepository
protectedvoidregistJobHandler(XxlJob xxlJob, Object bean, Method executeMethod)

{

if

 (xxlJob == 

null

) {

return

;

    }
    String name = xxlJob.value();

//make and simplify the variables since they'll be called several times later

    Class<?> clazz = bean.getClass();

    String methodName = executeMethod.getName();

if

 (name.trim().length() == 

0

) {

thrownew

 RuntimeException(

"xxl-job method-jobhandler name invalid, for["

 + clazz + 

"#"

 + methodName + 

"] ."

);

    }

if

 (loadJobHandler(name) != 

null

) {

thrownew

 RuntimeException(

"xxl-job jobhandler["

 + name + 

"] naming conflicts."

);

    }

// execute method

/*if (!(method.getParameterTypes().length == 1 && method.getParameterTypes()[0].isAssignableFrom(String.class))) {

        throw new RuntimeException("xxl-job method-jobhandler param-classtype invalid, for[" + bean.getClass() + "#" + method.getName() + "] , " +

                "The correct method format like \" public ReturnT<String> execute(String param) \" .");

    }

    if (!method.getReturnType().isAssignableFrom(ReturnT.class)) {

        throw new RuntimeException("xxl-job method-jobhandler return-classtype invalid, for[" + bean.getClass() + "#" + method.getName() + "] , " +

                "The correct method format like \" public ReturnT<String> execute(String param) \" .");

    }*/

    executeMethod.setAccessible(

true

);

// init and destroy

    Method initMethod = 

null

;

    Method destroyMethod = 

null

;

if

 (xxlJob.init().trim().length() > 

0

) {

try

 {

            initMethod = clazz.getDeclaredMethod(xxlJob.init());

            initMethod.setAccessible(

true

);

        } 

catch

 (NoSuchMethodException e) {

thrownew

 RuntimeException(

"xxl-job method-jobhandler initMethod invalid, for["

 + clazz + 

"#"

 + methodName + 

"] ."

);

        }

    }

if

 (xxlJob.destroy().trim().length() > 

0

) {

try

 {

            destroyMethod = clazz.getDeclaredMethod(xxlJob.destroy());

            destroyMethod.setAccessible(

true

);

        } 

catch

 (NoSuchMethodException e) {

thrownew

 RuntimeException(

"xxl-job method-jobhandler destroyMethod invalid, for["

 + clazz + 

"#"

 + methodName + 

"] ."

);

        }

    }

// 核心方法

    registJobHandler(name, 

new

 MethodJobHandler(bean, executeMethod, initMethod, destroyMethod));
}

我們可以參考原始碼,將被@Scheduled標記的方法,以同樣的方式,註冊到排程中心中去。從而實現@XxlJob同樣的效果。

/**

 * 註冊任務到xxl-job上

 *

 * 

@param

 handlerName   JobHandler名稱

 * 

@param

 executeMethod 執行定時任務的方法

 */


privatevoidregistJobHandler(String handlerName, Method executeMethod)

{

    executeMethod.setAccessible(

true

);

// xxl-job初始化和銷燬方法物件,後續有需要再賦值

    Method initMethod = 

null

;

    Method destroyMethod = 

null

;

//獲取方法的Bean物件

    Object bean = applicationContext.getBean(executeMethod.getDeclaringClass());

    XxlJobExecutor.registJobHandler(handlerName, 

new

 MethodJobHandler(bean, executeMethod, initMethod, destroyMethod));

}

自動向排程中心註冊執行器和對應的任務資訊
「註冊執行器」
XXL-JOB沒有像PowerJob一樣,提供類似powerjob-client的OpenAPI介面,但是問題不大,根據XXL-JOB的原始碼,我們可以自己實現一個,將獲取token,新增執行器資訊,新增任務資訊等包裝為service。
具體程式碼可以檢視文章後的github地址,這裡簡單貼出向排程中心註冊執行器的程式碼。
publicbooleanautoRegisterGroup()

{

    String url = adminAddresses + 

"/jobgroup/save"

;

    HttpRequest httpRequest = HttpRequest.post(url)

            .form(

"appname"

, appName)

            .form(

"title"

, title);
    httpRequest.form(

"addressType"

, addressType);

if

 (addressType.equals(

1

)) {

if

 (Strings.isBlank(addressList)) {

thrownew

 RuntimeException(

"手動錄入模式下,執行器地址列表不能為空"

);

        }

        httpRequest.form(

"addressList"

, addressList);

    }
    HttpResponse response = httpRequest.cookie(jobLoginService.getCookie())

            .execute();

    Object code = JSONUtil.parse(response.body()).getByPath(

"code"

);

if

(!code.equals(

200

)){

        log.error(

">>>>>>>>>>> xxl-job auto register group fail!msg[{}]"

,JSONUtil.parse(response.body()).getByPath(

"msg"

));

returnfalse

;

    }

returntrue

;

}

「新增對應任務資訊」
同樣的,新增任務資訊的邏輯也包裝為一個service。考慮到可能重複註冊的問題,這裡需要判斷註冊的任務是否已存在在排程中心中。
privatevoidaddJobInfo()

{

    List<XxlJobGroup> jobGroups = jobGroupService.getJobGroup();

    XxlJobGroup xxlJobGroup = jobGroups.get(

0

);

    List<Object> beanList = applicationContext.getBeansWithAnnotation(Component

.class).values().stream().toList()

;

    beanList.forEach(bean -> {

        Map<Method, Scheduled> annotatedMethods = MethodIntrospector.selectMethods(bean.getClass(),

                (MethodIntrospector.MetadataLookup<Scheduled>) method -> AnnotatedElementUtils.findMergedAnnotation(method, Scheduled

.class))

;

        annotatedMethods.forEach((k, v) -> {

// 停止Spring自帶的定時任務

            stopScheduled(k.getDeclaringClass());

// 自動註冊到xxl-job 暫定Handle名稱規則beanName#MethodName

            String handlerName = StringUtils.joinWith(

"#"

, k.getDeclaringClass().getName(), k.getName());

// 註冊xxl-job的任務

            registJobHandler(handlerName, k);

//因為是模糊查詢,需要再過濾一次

            Optional<XxlJobInfo> first = jobInfoService.getJobInfo(xxlJobGroup.getId(), handlerName).stream()

                    .filter(xxlJobInfo -> xxlJobInfo.getExecutorHandler().equals(handlerName))

                    .findFirst();

            XxlJobInfo xxlJobInfo = createXxlJobInfo(xxlJobGroup, v, handlerName);

if

 (first.isEmpty()) {

                Integer jobInfoId = jobInfoService.addJobInfo(xxlJobInfo);

if

 (ObjectUtils.isNotEmpty(jobInfoId)) {

                    log.info(

">>>>>>>>>>> xxl-job auto add jobInfo success! JobInfoId[{}] JobInfo[{}]"

, jobInfoId,

                            JSONUtil.toJsonStr(xxlJobInfo));

                }

            }

        });

    });

}

將定時任務中的log.info()日誌輸出一份到XXL-JOB的線上日誌上
XXl-JOB中提供了XxlJobHelper類,用於將任務中的日誌輸出到排程中心,方便在排程中心上進行檢視。而 lombok 生成的log.info()依賴於Slf4j日誌門面。
而我們知道,SpringBoot預設Slf4j的實現是Logback,Logback中提供類自定義Appender的介面,用於自定義日誌資訊的處理邏輯。我們可以在自定義的Appender中將日誌列印到XXl-JOB中的排程中心。

/**

 * 

@author

 Teoan

 * 

@description

 處理日誌事件

 */


@Component
publicclassXxlJobLogAppenderextendsAppenderBase<ILoggingEvent

{

@Override
protectedvoidappend(ILoggingEvent iLoggingEvent)

{

if

 (XxlJobHelper.getJobId() == -

1

) {

return

;

        }

if

 (Level.ERROR.equals(iLoggingEvent.getLevel())) {

            ThrowableProxy throwableProxy = (ThrowableProxy) iLoggingEvent.getThrowableProxy();

if

 (throwableProxy != 

null

) {

                XxlJobHelper.log(throwableProxy.getThrowable());

            } 

else

 {

                XxlJobHelper.log(iLoggingEvent.getMessage());

            }

        } 

else

 {

            XxlJobHelper.log(iLoggingEvent.getMessage());

        }

    }

}

第三方應用整合Starter使用
為了讓使用方更加方便的整合使用,減少其他依賴的配置,以上的實現封裝為一個Starter,使用起來將非常的方便,具體的使用步驟如下。
在POM檔案中引入Starter依賴
提供的Starter對XXL-JOB沒有強依賴,所以使用方還得引入XXL-JOB的依賴。

<!-- xxl-job-core -->

<dependency>

    <groupId>com.xuxueli</groupId>

    <artifactId>xxl-job-core</artifactId>

    <version>${xxl-job.version}</version>

</dependency>

<dependency>

    <groupId>com.teoan</groupId>

    <artifactId>xxl-job-auto-spring-boot-starter</artifactId>

    <version>${project.version}</version>

</dependency>

SpringBoor配置檔案中新增XXL-JOB的配置
除了配置XXL-JOB的基本配置,還需要配置我們自定義實現功能所需要的配置項,具體如下:

server:

  port: 

8080

spring:

  application:

    name: xxlJobAuto

xxl:

  job:

    # 自動註冊自定義新增配置項 是否使用Xxl實現定時任務

    enable: 

true

    accessToken: 

    admin:

      addresses: http:

//localhost:8080/xxl-job-admin

      # 以下admin配置為自動註冊自定義新增配置項,必須項

      username: admin                         #admin 使用者名稱

      password: password                      #admin 密碼

    executor:

      appname: ${spring.application.name}

      ip: 

      address:

      logpath: 

      logretentiondays: 

3

      port: 

0

      # 以下executor配置為自動註冊自定義新增配置項,可選

      addressList:    #在addressType為

1

的情況下,手動錄入執行器地址列表,多地址逗號分隔

      addressType: 

0

      #執行器地址型別:

0

=自動註冊、

1

=手動錄入,預設為

0

      title: ${spring.application.name}    #執行器名稱

XXL-JOB執行器元件配置
這個是XXL-JOB執行器所需要的配置。
@Configuration
@Slf

4j

publicclassXxlJobConfig

{

@Value

(

"${xxl.job.admin.addresses}"

)

private

 String adminAddresses;

@Value

(

"${xxl.job.accessToken}"

)

private

 String accessToken;

@Value

(

"${xxl.job.executor.appname}"

)

private

 String appname;

@Value

(

"${xxl.job.executor.address}"

)

private

 String address;

@Value

(

"${xxl.job.executor.ip}"

)

private

 String ip;

@Value

(

"${xxl.job.executor.port}"

)

privateint

 port;

@Value

(

"${xxl.job.executor.logpath}"

)

private

 String logPath;

@Value

(

"${xxl.job.executor.logretentiondays}"

)

privateint

 logRetentionDays;

@Bean
public XxlJobSpringExecutor xxlJobExecutor()

{

        log.info(

">>>>>>>>>>> xxl-job config init."

);

        XxlJobSpringExecutor xxlJobSpringExecutor = 

new

 XxlJobSpringExecutor();

        xxlJobSpringExecutor.setAdminAddresses(adminAddresses);

        xxlJobSpringExecutor.setAppname(appname);

        xxlJobSpringExecutor.setAddress(address);

        xxlJobSpringExecutor.setIp(ip);

        xxlJobSpringExecutor.setPort(port);

        xxlJobSpringExecutor.setAccessToken(accessToken);

        xxlJobSpringExecutor.setLogPath(logPath);

        xxlJobSpringExecutor.setLogRetentionDays(logRetentionDays);

return

 xxlJobSpringExecutor;

    }
}

使用SpringBoot自帶的@Scheduled註解開發定時任務
新建一個Job類模擬使用定時任務的場景。

/**

 * 

@author

 Teoan

 */


@Slf

4j

@Component
publicclassXxlJobAutoSamplesJob

{

@Scheduled

(fixedRate = 

10000

)

publicvoidsamplesJob()

{

        log.info(

"samplesJob executor success!"

);

    }

}

啟動專案驗證
先將配置檔案中的xxl.job.enable設定為false,使用Spring預設的實現方式。

  .   ____          _            __ _ _

 /\\ / ___

'_ __ _ _(_)_ __  __ _ \ \ \ \

( ( )\___ | '

_ | 

'_| | '

_ \/ _` | \ \ \ \

 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )

'  |____| .__|_| |_|_| |_\__, | / / / /

 =========|_|==============|___/=/_/_/_/

 :: Spring Boot ::                (v3.0.5)
2023-05-07 15:46:19.633 [main] INFO  com.teoan.job.auto.samples.XxlJobAutoApplication - Starting XxlJobAutoApplication using Java 17.0.6 with PID 28253 (/Users/teoan/Project/xxl-job-auto/xxl-job-auto-spring-boot-samples/target/classes started by teoan in /Users/teoan/Project/xxl-job-auto)

2023-05-07 15:46:19.645 [main] INFO  com.teoan.job.auto.samples.XxlJobAutoApplication - No active profile set, falling back to 1 default profile: "default"

2023-05-07 15:46:21.083 [main] INFO  o.s.boot.web.embedded.tomcat.TomcatWebServer - Tomcat initialized with port(s): 8080 (http)

2023-05-07 15:46:21.091 [main] INFO  org.apache.coyote.http11.Http11NioProtocol - Initializing ProtocolHandler ["http-nio-8080"]

2023-05-07 15:46:21.092 [main] INFO  org.apache.catalina.core.StandardService - Starting service [Tomcat]

2023-05-07 15:46:21.092 [main] INFO  org.apache.catalina.core.StandardEngine - Starting Servlet engine: [Apache Tomcat/10.1.7]

2023-05-07 15:46:21.179 [main] INFO  o.a.c.core.ContainerBase.[Tomcat].[localhost].[/] - Initializing Spring embedded WebApplicationContext

2023-05-07 15:46:21.179 [main] INFO  o.s.b.w.s.c.ServletWebServerApplicationContext - Root WebApplicationContext: initialization completed in 1295 ms

2023-05-07 15:46:21.367 [main] INFO  com.teoan.job.auto.samples.config.XxlJobConfig - >>>>>>>>>>> xxl-job config init.

2023-05-07 15:46:21.797 [main] INFO  o.s.b.actuate.endpoint.web.EndpointLinksResolver - Exposing 1 endpoint(s) beneath base path '

/actuator

'

2023-05-07 15:46:21.954 [main] INFO  org.apache.coyote.http11.Http11NioProtocol - Starting ProtocolHandler ["http-nio-8080"]

2023-05-07 15:46:21.969 [main] INFO  o.s.boot.web.embedded.tomcat.TomcatWebServer - Tomcat started on port(s): 8080 (http) with context path '

'

2023-05-07 15:46:21.998 [scheduling-1] INFO  c.teoan.job.auto.samples.job.XxlJobAutoSamplesJob - samplesJob executor success!

2023-05-07 15:46:22.000 [main] INFO  com.teoan.job.auto.samples.XxlJobAutoApplication - Started XxlJobAutoApplication in 3.014 seconds (process running for 3.887)

2023-05-07 15:46:22.020 [Thread-4] INFO  com.xxl.job.core.server.EmbedServer - >>>>>>>>>>> xxl-job remoting server start success, nettype = class com.xxl.job.core.server.EmbedServer, port = 9999

2023-05-07 15:46:22.397 [RMI TCP Connection(2)-192.168.123.139] INFO  o.a.c.core.ContainerBase.[Tomcat].[localhost].[/] - Initializing Spring DispatcherServlet '

dispatcherServlet

'

2023-05-07 15:46:22.399 [RMI TCP Connection(2)-192.168.123.139] INFO  org.springframework.web.servlet.DispatcherServlet - Initializing Servlet '

dispatcherServlet

'

2023-05-07 15:46:22.402 [RMI TCP Connection(2)-192.168.123.139] INFO  org.springframework.web.servlet.DispatcherServlet - Completed initialization in 3 ms

2023-05-07 15:47:31.997 [scheduling-1] INFO  c.teoan.job.auto.samples.job.XxlJobAutoSamplesJob - samplesJob executor success!

2023-05-07 15:47:41.997 [scheduling-1] INFO  c.teoan.job.auto.samples.job.XxlJobAutoSamplesJob - samplesJob executor success!

2023-05-07 15:47:51.996 [scheduling-1] INFO  c.teoan.job.auto.samples.job.XxlJobAutoSamplesJob - samplesJob executor success!

2023-05-07 15:48:01.994 [scheduling-1] INFO  c.teoan.job.auto.samples.job.XxlJobAutoSamplesJob - samplesJob executor success!

嗯,沒啥毛病。scheduling-1 用的啥Spring自帶的scheduling執行緒池去執行定時任務。 接下來將配置檔案中的xxl.job.enable設定為true,再看看日誌。

  .   ____          _            __ _ _

 /\\ / ___

'_ __ _ _(_)_ __  __ _ \ \ \ \

( ( )\___ | '

_ | 

'_| | '

_ \/ _` | \ \ \ \

 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )

'  |____| .__|_| |_|_| |_\__, | / / / /

 =========|_|==============|___/=/_/_/_/

 :: Spring Boot ::                (v3.0.5)
2023-05-07 15:56:50.011 [main] INFO  com.teoan.job.auto.samples.XxlJobAutoApplication - Starting XxlJobAutoApplication using Java 17.0.6 with PID 30937 (/Users/teoan/Project/xxl-job-auto/xxl-job-auto-spring-boot-samples/target/classes started by teoan in /Users/teoan/Project/xxl-job-auto)

2023-05-07 15:56:50.025 [main] INFO  com.teoan.job.auto.samples.XxlJobAutoApplication - No active profile set, falling back to 1 default profile: "default"

2023-05-07 15:56:51.538 [main] INFO  o.s.boot.web.embedded.tomcat.TomcatWebServer - Tomcat initialized with port(s): 8080 (http)

2023-05-07 15:56:51.548 [main] INFO  org.apache.coyote.http11.Http11NioProtocol - Initializing ProtocolHandler ["http-nio-8080"]

2023-05-07 15:56:51.549 [main] INFO  org.apache.catalina.core.StandardService - Starting service [Tomcat]

2023-05-07 15:56:51.549 [main] INFO  org.apache.catalina.core.StandardEngine - Starting Servlet engine: [Apache Tomcat/10.1.7]

2023-05-07 15:56:51.642 [main] INFO  o.a.c.core.ContainerBase.[Tomcat].[localhost].[/] - Initializing Spring embedded WebApplicationContext

2023-05-07 15:56:51.642 [main] INFO  o.s.b.w.s.c.ServletWebServerApplicationContext - Root WebApplicationContext: initialization completed in 1351 ms

2023-05-07 15:56:51.835 [main] INFO  com.teoan.job.auto.samples.config.XxlJobConfig - >>>>>>>>>>> xxl-job config init.

2023-05-07 15:56:52.282 [main] INFO  o.s.b.actuate.endpoint.web.EndpointLinksResolver - Exposing 1 endpoint(s) beneath base path '

/actuator

'

2023-05-07 15:56:52.444 [main] INFO  org.apache.coyote.http11.Http11NioProtocol - Starting ProtocolHandler ["http-nio-8080"]

2023-05-07 15:56:52.457 [main] INFO  o.s.boot.web.embedded.tomcat.TomcatWebServer - Tomcat started on port(s): 8080 (http) with context path '

'

2023-05-07 15:56:52.477 [scheduling-1] INFO  c.teoan.job.auto.samples.job.XxlJobAutoSamplesJob - samplesJob executor success!

2023-05-07 15:56:52.480 [main] INFO  com.teoan.job.auto.samples.XxlJobAutoApplication - Started XxlJobAutoApplication in 3.118 seconds (process running for 3.86)

2023-05-07 15:56:52.515 [Thread-4] INFO  com.xxl.job.core.server.EmbedServer - >>>>>>>>>>> xxl-job remoting server start success, nettype = class com.xxl.job.core.server.EmbedServer, port = 9999

2023-05-07 15:56:52.712 [RMI TCP Connection(3)-192.168.123.139] INFO  o.a.c.core.ContainerBase.[Tomcat].[localhost].[/] - Initializing Spring DispatcherServlet '

dispatcherServlet

'

2023-05-07 15:56:52.714 [RMI TCP Connection(3)-192.168.123.139] INFO  org.springframework.web.servlet.DispatcherServlet - Initializing Servlet '

dispatcherServlet

'

2023-05-07 15:56:52.715 [RMI TCP Connection(3)-192.168.123.139] INFO  org.springframework.web.servlet.DispatcherServlet - Completed initialization in 1 ms

2023-05-07 15:56:53.145 [main] INFO  com.teoan.job.auto.core.JobAutoRegister - >>>>>>>>>>> xxl-job auto register group success!

2023-05-07 15:56:53.490 [main] INFO  com.xxl.job.core.executor.XxlJobExecutor - >>>>>>>>>>> xxl-job register jobhandler success, name:com.teoan.job.auto.samples.job.XxlJobAutoSamplesJob#samplesJob, jobHandler:com.xxl.job.core.handler.impl.MethodJobHandler@223cbf0d[class com.teoan.job.auto.samples.job.XxlJobAutoSamplesJob#samplesJob]

2023-05-07 15:56:53.647 [main] INFO  com.teoan.job.auto.core.JobAutoRegister - >>>>>>>>>>> xxl-job auto add jobInfo success! JobInfoId[11085] JobInfo[{"id":0,"jobGroup":2080,"jobDesc":"com.teoan.job.auto.samples.job.XxlJobAutoSamplesJob#samplesJob","author":"JobAutoRegister","scheduleType":"FIX_RATE","scheduleConf":"10","misfireStrategy":"DO_NOTHING","executorRouteStrategy":"FIRST","executorHandler":"com.teoan.job.auto.samples.job.XxlJobAutoSamplesJob#samplesJob","executorBlockStrategy":"SERIAL_EXECUTION","executorTimeout":0,"executorFailRetryCount":0,"glueType":"BEAN","glueRemark":"GLUE程式碼初始化","triggerStatus":1,"triggerLastTime":0,"triggerNextTime":0}]

2023-05-07 15:56:53.650 [main] INFO  com.teoan.job.auto.core.JobAutoRegister - >>>>>>>>>>> xxl-job auto register success

2023-05-07 15:57:24.538 [xxl-job, EmbedServer bizThreadPool-123827075] INFO  com.xxl.job.core.executor.XxlJobExecutor - >>>>>>>>>>> xxl-job regist JobThread success, jobId:11085, handler:com.xxl.job.core.handler.impl.MethodJobHandler@223cbf0d[class com.teoan.job.auto.samples.job.XxlJobAutoSamplesJob#samplesJob]

2023-05-07 15:57:24.540 [xxl-job, JobThread-11085-1683446244537] INFO  c.teoan.job.auto.samples.job.XxlJobAutoSamplesJob - samplesJob executor success!

日誌看起來沒啥問題,註冊執行器和註冊任務資訊的相關日誌都列印了出來,定時任務的執行日誌也有了。我們上排程中心看看。
嗯,符合預期,執行器和任務詳情都自動新增到排程中心了,任務中心的日誌也能在排程中心中查看了。

實現過程中思考的幾個問題

是否實現任務資訊的更新
一開始想著是否需要監聽註解上值的變化,對應地去更新XXL-JOB上的任務資訊,如經常需要改變的定時任務的間隙時間或者corn表示式,後來還是決定不實現了,考慮到大多數場景下,自動註冊任務只是作為應用啟動的初始化工作,後續需要調整還是得上排程中心進行操作,所以任務的配置就不能一直以註解上配置為準了。
是否採用修改資料庫資料的方式實現任務的註冊
自動註冊任務和執行器資訊,其實可以直接利用ORM操作資料庫去實現。不過如果XXL-JOB的資料庫和當前應用不在同一臺機器上,就需要配置多個數據源了,相對比較麻煩,對於第三方使用者來說,也會多出一些配置。總體看起來不夠優雅,最後還是採用讀取排程中心地址,利用http工具呼叫API的方式去實現。
是否在自動裝配類上加上@Scheduled
在提供的自動裝配類中,其實可以幫使用者預設加上 @Scheduled 開啟SpringBoot的自動任務,但是為了儘量不影響正常的開發配置,開頭說到的儘量讓使用者無感知,所以這個 @Scheduled 還是需要starter的使用方自己去配置,然後走預設實現的定時任務開發。
提供的Starter是否加上XXL-Job的依賴
提供的strarter包只是作為增強功能的存在,所以是可選的,不應該耦合XXL-JOB的核心依賴,就像Hutool中POI工具一樣,本身並不依賴POI的核心依賴,作為Strarter包,應該只提供自己的核心功能就行。

總結

第一次根據自己的突發奇想,對中介軟體進行二次開發,瞭解了XXL-JOB的具體實現的過程中,也間接鍛鍊了自己閱讀開原始碼的能力。 從有想法到實現,這個過程中自己收穫頗多,也就有了這篇部落格,當作自己的過程筆記吧,分享給有需要的人。原始碼什麼的也在github上開源了,喜歡的小夥伴也不妨點個stars。

專案地址

https://github.com/Teoan/xxl-job-auto

歡迎加入我的知識星球,全面提升技術能力。
👉 加入方式,長按”或“掃描”下方二維碼噢
星球的內容包括:專案實戰、面試招聘、原始碼解析、學習路線。
文章有幫助的話,在看,轉發吧。
謝謝支援喲 (*^__^*)

相關文章