Loading [MathJax]/jax/output/CommonHTML/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >Spring Boot 定时任务与 xxl-job 灵活切换方案

Spring Boot 定时任务与 xxl-job 灵活切换方案

作者头像
终码一生
发布于 2024-06-18 07:17:27
发布于 2024-06-18 07:17:27
56200
代码可运行
举报
文章被收录于专栏:终码一生终码一生
运行总次数:0
代码可运行

在使用XXL—JOB的实现定时任务过程中,有时候可能由于部署环境的要求,就只能用Spring自带的实现方式。

所以为了通用性和灵活性,突发奇想地看看能不能实现在不修改原本Spring定时任务代码的前提下,通过配置灵活控制定时任务具体的实现,同时任务的日志的管理也要同步进行切换。

分析并列出需要解决的问题思路

根据需求背景可以初步分析实现的大致方向和实现流程。实现的思路其实不复杂,重点在于如何具体去实现落地。

具体实现

判断是否启用XXl-JOB的实现方式

和大多数第三方starter包一样,我们可以利用SpringBoot的自动装配,读取配置中的某个属性值,作为是否装配我们写的类。特别注意的是SpringBoot不同版本的配置方式有所不同。

自动装配类如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
/**
 * 自动装配类
 */
@Configuration
@ConditionalOnProperty(name = "xxl.job.enable",havingValue = "true")
@ComponentScan("com.teoan.job.auto.core")
public class XxlJobAutoConfiguration {
}

这里我们根据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提供的监听注解,在应用启动就绪后,扫描对应注解,去实现我们的代码逻辑,同时为了不影响程序的正常启动速度,使用异步执行的方式。

伪代码如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@Component
@Slf4j
public class JobAutoRegister {
    @EventListener(ApplicationReadyEvent.class)
    @Async
    public void onApplicationEvent(ApplicationReadyEvent event) {
        // 执行扫描注解,自动注册xxl-job任务逻辑
    }
}
扫描并获取被@Scheduled标记的方法和对象

我们知道,使用@Scheduled注解对应的对象,必须是被Spring所托管的类,定时任务才会生效,所以我们可以扫描被@Component标记的类,再定位@Scheduled注解,获取对应的值、对象、方法等信息。

伪代码如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
private void addJobInfo() {
    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中的所有定时任务。具体可以看一下这个方法的源码。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@Override
public void postProcessBeforeDestruction(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自带的定时任务。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
/**
 * 停止Spring自带的定时注解
 *
 * @param clazz 带有定时注解的类
 */
private void stopScheduled(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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
protected void registJobHandler(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) {
        throw new RuntimeException("xxl-job method-jobhandler name invalid, for[" + clazz + "#" + methodName + "] .");
    }
    if (loadJobHandler(name) != null) {
        throw new 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) {
            throw new 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) {
            throw new RuntimeException("xxl-job method-jobhandler destroyMethod invalid, for[" + clazz + "#" + methodName + "] .");
        }
    }

    // 核心方法
    registJobHandler(name, new MethodJobHandler(bean, executeMethod, initMethod, destroyMethod));

}

我们可以参考源码,将被@Scheduled标记的方法,以同样的方式,注册到调度中心中去。从而实现@XxlJob同样的效果。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
/**
 * 注册任务到xxl-job上
 *
 * @param handlerName   JobHandler名称
 * @param executeMethod 执行定时任务的方法
 */
private void registJobHandler(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地址,这里简单贴出向调度中心注册执行器的代码。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public boolean autoRegisterGroup() {
    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)) {
            throw new 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"));
        return false;
    }
    return true;
}

「添加对应任务信息」

同样的,添加任务信息的逻辑也包装为一个service。考虑到可能重复注册的问题,这里需要判断注册的任务是否已存在在调度中心中。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
private void addJobInfo() {
    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));java
        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中的调度中心。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
/**
 * @author Teoan
 * @description 处理日志事件
 */
@Component
public class XxlJobLogAppender extends AppenderBase<ILoggingEvent> {

    @Override
    protected void append(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的依赖。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
<!-- 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的基本配置,还需要配置我们自定义实现功能所需要的配置项,具体如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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执行器所需要的配置。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@Configuration
@Slf4j
public class XxlJobConfig {

    @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}")
    private int port;

    @Value("${xxl.job.executor.logpath}")
    private String logPath;

    @Value("${xxl.job.executor.logretentiondays}")
    private int 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类模拟使用定时任务的场景。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
/**
 * @author Teoan
 */
@Slf4j
@Component
public class XxlJobAutoSamplesJob {

    @Scheduled(fixedRate = 10000)
    public void samplesJob(){
        log.info("samplesJob executor success!");
    }
}
启动项目验证

先将配置文件中的xxl.job.enable设置为false,使用Spring默认的实现方式。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: 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,再看看日志。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: 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

—END—

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2024-06-17,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 终码一生 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
敏捷技术学习【1】
今天是2018年的最后一天,这两天一番对自己的2019年也制定一些可行可期的年度计划,并尝试克服一些自己人性的弱点去坚持实施下来。
efonfighting
2019/08/02
3620
敏捷技术学习【1】
敏捷开发
现在有许多公司专门从事软件开发项目。他们中的一些人正在使用标准的业务方法(瀑布),有些人已经涉及敏捷原则。产品开发人员和开发团队一直在寻找更有效的生产方式。虽然瀑布过程在过去被广泛采用,但越来越多的团队正在转向敏捷开发,这是一种现代化的项目管理和产品开发方法。在本文档中,我们想向您介绍敏捷的世界,并揭示与在工作中使用敏捷方法的开发团队合作的好处。
杜金房
2022/12/12
1.3K0
敏捷开发
深入核心的敏捷开发
如何破局? 正如《管理3.0:培养和提升敏捷领导力》所说,所有变革最后的失败都是管理的问题。应该把绩效考核这种管理手段当成『敏捷铁三角』中一角来对待,那就是调整约束
yeedomliu
2021/03/16
1.4K0
深入核心的敏捷开发
关于敏捷开发的思考
刚接触敏捷一个月左右,看各种设计模式、TDD、scrum、xp,但是当别人问到我什么是敏捷开发的时候,还是不知道怎么去回答,所以就想着,是时候理一下这些天学习的关于敏捷的所有,在心里搭一个框架。
刘开心_1266679
2019/02/14
6620
关于敏捷开发的思考
敏捷项目管理介绍及实施
敏捷开发 Scrum Scrum就像你的丈母娘,不断支出你的问题在哪,错在哪 Scurm只是不断的暴露你的问题
Freedom123
2024/03/29
2950
敏捷项目管理介绍及实施
敏捷开发流程之Scrum:3个角色、5个会议、12原则
本文主要从Scrum的定义和目的、敏捷宣言、Scrum中的人员角色、Scrum开发流程、敏捷的12原则等几方面帮助大家理解Scrum敏捷开发的全过程。
宜信技术学院
2020/01/07
13K0
敏捷开发流程之Scrum:3个角色、5个会议、12原则
【软件工程】敏捷开发:促进创新、高效交付的软件工程方法
在现代软件开发领域中,敏捷开发已经成为一种备受推崇的方法。通过其灵活性、迭代性和注重团队协作的特点,敏捷开发在推动软件工程的发展和成功项目交付方面发挥了关键作用。本文将深入探讨敏捷开发的核心原则、实践方法以及它在当今软件行业中的重要性。
人不走空
2024/02/21
2890
[转] Agile Software Development 敏捷软件开发
  敏捷开发是一种软件开发方法,基于迭代和增量开发,通过自组织,跨团队,沟通协作完成开发工作。
Edison Zhou
2018/08/20
7120
[转] Agile Software Development 敏捷软件开发
敏捷开发:5种主流开发方法介绍
极限编程(ExtremeProgramming,简称XP)是由KentBeck在1996年提出的。极限编程是一个轻量级的、灵巧的软件开发方法;同时它也是一个非常严谨和周密的方法。XP是一种近螺旋式的开发方法,它将复杂的开发过程分解为一个个相对比较简单的小周期;通过积极的交流、反馈以及其它一系列的方法,开发人员和客户可以非常清楚开发进度、变化、待解决的问题和潜在的困难等,并根据实际情况及时地调整开发过程。
DevOps时代
2019/07/30
2K0
敏捷 | 如何正确理解敏捷?
在过去的五年时间里,我所在的公司和团队一直使用的都是敏捷开发模式,我也在2018年底获取了Scrum联盟的CSM认证,对于敏捷的理解也是从最初的感性认识到现在的理性认识。今天开始和你一起重新温习敏捷,先来正确理解一下敏捷吧。
Edison Zhou
2020/12/25
8780
敏捷 | 如何正确理解敏捷?
.NET 云原生架构师训练营(模块二 基础巩固 敏捷开发)--学习笔记
从60年代中期开始到20世纪末,软件行业得到了非常迅猛的发展,软件系统的规模和复杂度也越来越高,行业普遍面临不满足需求,永远无法交付等一系列严重的问题,史称“软件危机”
郑子铭
2021/01/18
1.8K0
.NET 云原生架构师训练营(模块二 基础巩固 敏捷开发)--学习笔记
敏捷软件开发简述
前言:由于我读了邹欣老师的《构建之法:现代软件工程(第二版)》,因此对敏捷软件开发有了比较大的兴趣。于是我在网上找了一些论文,比如Requirements Engineering and Agile Software Development、A decade of agile methodologies: Towards explaining agile software development。在读了这些论文之后,对敏捷软件开发有了大致的了解。这篇博文主要是简单介绍敏捷软件开发,重点集中在主要的敏捷开发方法和它的优势,同时也作为一个备忘录,来记录我在这个过程中收获到的重要的知识。
庞小明
2018/09/19
1.5K0
敏捷软件开发简述
敏捷开发入门普及
*****三个角色,三个工件,四个流程(五个事件),四大支柱,五大价值观*****
RobinsonZhang
2018/08/28
1.6K0
敏捷开发入门普及
什么是敏捷软件开发?
Scrum是一个框架,在这个框架中,人们可以解决复杂的适应性问题,同时高效、创造性地交付最高价值的产品。它用于管理软件项目、产品或应用程序开发。它的重点是自适应产品开发策略,其中跨职能团队作为一个单位,在2-4周内(Sprint)达到一个共同的目标。它由价值、工件、角色、仪式、规则和最佳实践组成。
增强现实核心技术产业联盟
2020/06/12
1.5K0
什么是敏捷软件开发?
什么是敏捷框架 Scrum 中的 “3355”?
接触过敏捷的我们,一定对Scrum都不陌生,Scrum是众多轻量级敏捷框架中应用最广泛的一种。
DevOps时代
2019/03/08
10.6K0
什么是敏捷框架 Scrum 中的 “3355”?
敏捷开发:拥抱变化,持续交付价值的艺术
在快速变化的技术和市场环境中,软件开发项目面临着前所未有的挑战。传统的瀑布模型,尽管在某些情况下仍然有效,但往往因为其僵化和缺乏灵活性而受到批评。敏捷开发,作为一种新兴的软件开发方法论,应运而生,旨在解决这些问题,提供一种更加灵活、响应快速的开发方式。
正在走向自律
2024/12/18
2600
敏捷开发:拥抱变化,持续交付价值的艺术
敏捷开发实践(一)--谈谈我对敏捷开发的理解
随着敏捷开发越来越流行,人人都在谈敏捷,人人也都在学习scrum等敏捷开发方法。。。当然,自己也是敏捷开发的实施者和受益者。
程序猿小亮
2021/01/29
1.5K0
ThoughtWorks的敏捷开发 | 洞见
ThoughtWorks的敏捷开发方法一直是一种神秘存在。在敏捷开发还没有主流化的年代,为了让外界理解ThoughtWorks全球团队怎么做敏捷,我们商定了一个“60% Scrum + 40% XP”的经典答案。当然其实ThoughtWorks的敏捷开发既不是Scrum,也不是XP。
ThoughtWorks
2018/08/03
1.3K0
ThoughtWorks的敏捷开发 | 洞见
【敏捷1.2】敏捷宣言的官方解释:12条敏捷原则
上一篇文章中说到的敏捷宣言,可以说是整个敏捷体系中最精髓的部分了。说实话,不仅你觉得,我也觉得这四句话有点太简单,太抽象了。难道真正的敏捷只是遵循这四句话就可以了吗?不要 too young too simple 了。
硬核项目经理
2023/03/09
7310
【敏捷1.2】敏捷宣言的官方解释:12条敏捷原则
远程项目交付的敏捷管理
对于日益重要的国际化市场,越来越多的离岸项目(内包或外包)在进行中,即需求方/客户在A地,开发团队在B地甚至海外。这种情形下,常见的敏捷实践活动也都是适用的。敏捷和精益关注的是价值观和原则。价值观也是文化的一部分,因此需要和离岸团队交谈并学习如何共同工作才能建立。本文内容也适用于任何远程项目的敏捷管理。
用户10443079
2023/03/22
1K0
远程项目交付的敏捷管理
相关推荐
敏捷技术学习【1】
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验