完成Spring守时使命与XXL-JOB灵敏切换

背景

在运用XXL—JOB的完成守时使命过程中,有时候可能由于布置环境的要求,就只能用Spring自带的完成办法。所以为了通用性和灵敏性,突发奇想地看看能不能完成在不修正原本Spring守时使命代码的前提下,经过装备灵敏控制守时使命详细的完成,一起使命的日志的管理也要同步进行切换。

剖析并列出需求处理的问题思路

依据需求背景能够开始剖析完成的大致方向和完成流程。完成的思路其实不复杂,重点在于如何详细去完成落地。


graph TD
A[运用发动] --> B{是否启用XXl-JOB的完成办法?};
B -- 是 --> C[扫描并读取注解值];
B -- 否 --> D[不做修正,运用Spring的完成];
C --> F[封闭Spring自带的守时使命];
F --> G[读取注解信息并将使命主动注册到XXl-JOB];
G --> H[将守时使命中的log.info日志输出一份到XXL-JOB的在线日志上];
H -->E;
D --> E[运用发动结束]

详细完成

判别是否启用XXl-JOB的完成办法

和大多数第三方starter包一样,咱们能够运用SpringBoot的主动安装,读取装备中的某个属性值,作为是否安装咱们写的类。特别注意的是SpringBoot不同版本的装备办法有所不同。 主动安装类如下:

/**
 * 主动安装类
 *
 * @author Teoan
 * @since 2023/04/18 10:18
 */
@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供给的监听注解,在运用发动就绪后,扫描对应注解,去完成咱们的代码逻辑,一起为了不影响程序的正常发动速度,运用异步履行的办法。 伪代码如下:

@Component
@Slf4j
public class JobAutoRegister {
    @EventListener(ApplicationReadyEvent.class)
    @Async
    public void onApplicationEvent(ApplicationReadyEvent event) {
        // 履行扫描注解,主动注册xxl-job使命逻辑
    }
}

扫描并获取被@Scheduled符号的办法和目标

咱们知道,运用@Scheduled注解对应的目标,有必要是被Spring所托管的类,守时使命才会收效,所以咱们能够扫描被@Component符号的类,再定位@Scheduled注解,获取对应的值、目标、办法等信息。 伪代码如下:

    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中的一切守时使命。详细能够看一下这个办法的源码。

	@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自带的守时使命。

    /**
     * 中止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

    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**相同的作用。

    /**
     * 注册使命到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地址,这里简单贴出向调度中心注册履行器的代码。

    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。考虑到可能重复注册的问题,这里需求判别注册的使命是否已存在在调度中心中。

    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));
            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 处理日志事情
 * @since 2023/04/11 15:17
 */
@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的依靠。

    <!-- 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: pass@bingocloud1
    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
@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类模拟运用守时使命的场景。

/**
 * @author Teoan
 * @since 2023/04/19 10:14
 */
@Slf4j
@Component
public class XxlJobAutoSamplesJob {
    @Scheduled(fixedRate = 10000)
    public void samplesJob(){
        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!

日志看起来没啥问题,注册履行器和注册使命信息的相关日志都打印了出来,守时使命的履行日志也有了。咱们上调度中心看看。

实现Spring定时任务与XXL-JOB灵活切换

实现Spring定时任务与XXL-JOB灵活切换

实现Spring定时任务与XXL-JOB灵活切换

嗯,符合预期,履行器和使命详情都主动增加到调度中心了,使命中心的日志也能在调度中心中查看了。

完成过程中思考的几个问题

是否完成使命信息的更新

一开始想着是否需求监听注解上值的改变,对应地去更新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。

项目地址

xxl-job-auto