当前位置: 首页 > news >正文

xxljob定时任务三种方式的实现

1:Bean 模式 - @XxlJob注解方式

核心机制

  • 执行器项目 的某个 Spring Bean 的方法上添加 @XxlJob("任务名称")注解。

  • 调度中心通过配置的“任务名称”来定位并远程调用该方法

Bean模式任务,支持基于方法的开发方式,每个任务对应一个方法。

  • 底层通过XxlJobSpringExecutor扫描注解方法,自动注册为JobHandler,无需手动干预。

  • 支持无参任务、分片任务等常见场景,参数通过XxlJobHelper统一获取,规范简洁。

  • 依赖XxlJobConfig配置类初始化执行器,Spring 环境缺失时无法使用。

基于方法开发的任务,底层会生成JobHandler代理,和基于类的方式一样,任务也会以JobHandler的形式存在于执行器任务容器中

@Component
public class SampleXxlJob {private static final Logger logger = LoggerFactory.getLogger(SampleXxlJob.class);/*** 方式1:简单的无参任务* 调度中心JobHandler填写:demoJob*/@XxlJob("demoJob")public void demoJobHandler() throws Exception {// 通过 XxlJobHelper 获取参数String param = XxlJobHelper.getJobParam();XxlJobLogger.log("XXL-JOB Hello World, param: {}", param);// 业务逻辑for (int i = 0; i < 5; i++) {XxlJobLogger.log("beat at: {}", i);TimeUnit.SECONDS.sleep(2);}// 设置执行结果XxlJobHelper.handleSuccess("任务执行成功");}/*** 方式2:分片任务* 调度中心JobHandler填写:shardingJob*/@XxlJob("shardingJob")public void shardingJobHandler() throws Exception {// 获取分片参数int shardIndex = XxlJobHelper.getShardIndex();int shardTotal = XxlJobHelper.getShardTotal();XxlJobLogger.log("分片参数:当前分片序号 = {}, 总分片数 = {}", shardIndex, shardTotal);// 分片处理逻辑// 模拟获取数据List<String> data =Arrays.asList("A", "B", "C", "D", "E");for (int i = 0; i < data.size(); i++) {// 简单分片逻辑:对数据取模分片if (i % shardTotal == shardIndex) {XxlJobLogger.log("处理数据: {}",data.get(i));}}XxlJobHelper.handleSuccess("分片任务执行完成");} 
}

@XxlJob注解可以自动扫描任务并注入到执行器容器

spring扫描xxl-job config配置类加载去创建执行器实例

@Configuration
public class XxlJobConfig {private Logger logger = LoggerFactory.getLogger(XxlJobConfig.class);@Value("${xxl.job.admin.addresses}")private String adminAddresses;@Value("${xxl.job.admin.accessToken}")private String accessToken;@Value("${xxl.job.admin.timeout}")private int timeout;@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() {logger.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.setTimeout(timeout);xxlJobSpringExecutor.setLogPath(logPath);xxlJobSpringExecutor.setLogRetentionDays(logRetentionDays);return xxlJobSpringExecutor;
}}

XxlJobSpringExecutor在启动时会自动扫描所有带有 @XxlJob注解的方法,并调用 registJobHandler进行注册(下面是底层方法)

自动注册的核心流程

  1. 项目启动时,Spring 扫描XxlJobConfig配置类,创建XxlJobSpringExecutor执行器实例。
  2. XxlJobSpringExecutor实现了SmartInitializingSingleton接口(在所有非懒加载单例 Bean 初始化完成后,执行特定逻辑),在所有 Spring Bean 初始化完成后,会调用afterSingletonsInstantiated方法。
  3. 该方法内部执行initJobHandlerMethodRepository,扫描容器中所有非懒加载 Bean 的方法。
  4. 找到带有@XxlJob注解的方法,封装为JobHandler,通过registJobHandler注册到执行器容器。
  5. 调度中心通过 “任务名称”(注解中指定的 value),就能定位到对应的方法并远程调用
  • 懒加载(@Lazy)首次使用时创建、启动时未创建
  • 非懒加载(默认)Spring 容器启动时创建
public class XxlJobSpringExecutor extends XxlJobExecutor implements ApplicationContextAware, SmartInitializingSingleton, DisposableBean {private static final Logger logger = LoggerFactory.getLogger(XxlJobSpringExecutor.class);private static ApplicationContext applicationContext;public void afterSingletonsInstantiated() {// 自动扫描并注册 @XxlJob 注解的方法this.initJobHandlerMethodRepository(applicationContext);GlueFactory.refreshInstance(1);try {super.start();} catch (Exception e) {throw new RuntimeException(e);}}public void destroy() {super.destroy();}private void initJobHandlerMethodRepository(ApplicationContext applicationContext) {if (applicationContext != null) {String[] beanDefinitionNames = applicationContext.getBeanNamesForType(Object.class, false, false);for(String beanDefinitionName : beanDefinitionNames) {if (!this.isSystemBean(beanDefinitionName)) {Object bean = null;Lazy onBean = (Lazy)applicationContext.findAnnotationOnBean(beanDefinitionName, Lazy.class);if (onBean != null) {logger.debug("xxl-job annotation scan, skip @Lazy Bean:{}", beanDefinitionName);} else {bean = applicationContext.getBean(beanDefinitionName);Map<Method, XxlJob> annotatedMethods = null;try {// 查找带有 @XxlJob 注解的方法//扫描类中所有方法,查找符合条件的方法annotatedMethods = MethodIntrospector.selectMethods(bean.getClass(), new MethodIntrospector.MetadataLookup<XxlJob>() {public XxlJob inspect(Method method) {return (XxlJob)AnnotatedElementUtils.findMergedAnnotation(method, XxlJob.class);}});} catch (Throwable ex) {logger.error("xxl-job method-jobhandler resolve error for bean[" + beanDefinitionName + "].", ex);}//遍历注册if (annotatedMethods != null && !annotatedMethods.isEmpty()) {for(Map.Entry<Method, XxlJob> methodXxlJobEntry : annotatedMethods.entrySet()) {Method executeMethod = (Method)methodXxlJobEntry.getKey();XxlJob xxlJob = (XxlJob)methodXxlJobEntry.getValue();// 注册到 jobHandlerRepositorythis.registJobHandler(xxlJob, bean, executeMethod);}}}}}}}private boolean isSystemBean(String beanClassName) {return beanClassName.startsWith("org.springframework") || beanClassName.startsWith("spring.");}public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {XxlJobSpringExecutor.applicationContext = applicationContext;}public static ApplicationContext getApplicationContext() {return applicationContext;}
}

2. Bean(类)模式 - 手动注册

这种方式需要手动调用XxlJobExecutor.registJobHandler方法。

  • 需实现IJobHandler接口,通过XxlJobExecutor.registJobHandler手动注册。

  • 推荐使用CommandLineRunner触发注册(应用完全启动后执行),避免依赖未就绪问题。---下面有实际应用

  • 可通过抽象类封装参数解析、日志记录等通用逻辑,提升复用性。

实现 IJobHandler 接口

@Component
public class CustomJobHandler extends IJobHandler {private static final Logger logger = LoggerFactory.getLogger(CustomJobHandler.class);@Autowiredprivate UserService userService; // 可以注入Spring Bean@Overridepublic void execute(String param) throws Exception {// 通过 XxlJobHelper 获取参数(返回的是json字符串)String param = XxlJobHelper.getJobParam();// 业务逻辑userService.syncUserData();}
}

需要手动调用XxlJobExecutor.registJobHandler方法进行注册

写配置类,必须直接写在XxlJobConfig配置类中,确保执行器的配置文件加载完毕创建执行器

@PostConstruct 在 XxlJobConfig 中只能保证当前配置类的参数和自身依赖就绪,不能保证其他Bean就绪

@Configuration
public class XxlJobConfig {
//注入@Autowiredprivate CustomJobHandler customJobHandler;private Logger logger = LoggerFactory.getLogger(XxlJobConfig.class);@Value("${xxl.job.admin.addresses}")private String adminAddresses;@Value("${xxl.job.admin.accessToken}")private String accessToken;@Value("${xxl.job.admin.timeout}")private int timeout;@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() {logger.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.setTimeout(timeout);xxlJobSpringExecutor.setLogPath(logPath);xxlJobSpringExecutor.setLogRetentionDays(logRetentionDays);return xxlJobSpringExecutor;
}//Bean初始化后立即执行@PostConstructpublic void manualRegisterJobHandlers() {// 注册自定义任务处理器XxlJobExecutor.registJobHandler("customJobHandler", customJobHandler);// 也可以注册匿名实现XxlJobExecutor.registJobHandler("simpleJob", new IJobHandler() {@Overridepublic void execute(String param) throws Exception {XxlJobLogger.log("简单任务执行,参数: {}", param);// 简单任务逻辑}});// 注册更复杂的处理器XxlJobExecutor.registJobHandler("dataProcessJob", new DataProcessJobHandler());
}
}

为什么使用@PostConstruct而不使用@Bean

  • @PostConstruct是 Java EE 规范中的注解,Spring 框架也支持它。它的核心作用是:在 Bean 完成依赖注入后,但在 Bean 正式投入使用前,执行初始化逻辑
  • @PostConstruct确保了任务注册在正确的时机执行,既不会太早(依赖未就绪),也不会太晚(错过执行器初始化窗口)
  • 确保执行器的配置都加载完成并完成创建

不过也可以通过类似@XxIjob底层自动注册的逻辑进行配置工具类进行自动注册

下面是我自己进行优化的代码:自动注册以及参数处理

@Component
@Slf4j
public class DemoJobHandler extends AbstractJobHandler<DemoJobHandler.ParamsObj> {@ResourceLogRecordService logRecordService;@Datapublic static class ParamsObj {private Long tenantId;}@Overrideprotected void handle() throws Exception {//        XxlJobHelper.log("XXL-JOB, Hello World.");log.info("参数 :{}", getParam());List<LogRecord> logRecordList = logRecordService.list();System.out.println("Json  ===  "+ JSONObject.toJSONString(logRecordList.get(0)));}
}
@Slf4j
public abstract class AbstractJobHandler<T> extends IJobHandler implements ApplicationContextAware {private ThreadLocal<T> param = ThreadLocal.withInitial(() -> null);private static ApplicationContext applicationContext;@Overridepublic void setApplicationContext(ApplicationContext applicationContext) {AbstractJobHandler.applicationContext = applicationContext;}protected String getJobHandlerName() {return this.getClass().getSimpleName();}//处理传入的参数@SuppressWarnings("unchecked")private void initParam() {String jobParam = XxlJobHelper.getJobParam();try {if (StringUtils.isNotBlank(jobParam)) {param.set(JSON.parseObject(jobParam, (Class<T>) ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[0]));} else {param.set(((Class<T>) ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[0]).getDeclaredConstructor().newInstance());}} catch (Exception e) {log.error("init param error => param:[{}] e:[{}]", jobParam, Throwables.getStackTraceAsString(e));} finally {log.info("init param => jobParam:[{}] param:[{}]", jobParam, param.get());}}@Overridepublic void execute() throws Exception {}
}设置自动注册执行任务@Slf4j
@Component
public class JobHandlerRegister implements CommandLineRunner {@Autowired(required = false)Map<String, AbstractJobHandler<?>> abstractJobHandlerMap = new HashMap<>();@Overridepublic void run(String... args) throws Exception {for (Map.Entry<String, AbstractJobHandler<?>> handlerEntry : abstractJobHandlerMap.entrySet()) {log.info("register handler => name:[{}] class:[{}]", handlerEntry.getKey(), handlerEntry.getValue().getClass());XxlJobExecutor.registJobHandler(handlerEntry.getKey(), handlerEntry.getValue());}}
}

 JobHandlerRegister执行流程

  • Spring 会将容器中所有 AbstractJobHandler<?>类型的 Bean 注入到 Map 中

  • Key: Bean 的名称(如 userSyncJobHandler

  • Value: Bean 的实例

  • required = false: 如果没有找到相关 Bean,不会报错,Map 为空

和@XxIjob底层自动注册的逻辑差不多,@XxIjob底层是去找有使用@XxIjob这个注解的

CommandLineRunner的作用

这是他的方法:

@FunctionalInterface
public interface CommandLineRunner extends Runner {void run(String... args) throws Exception;
}

CommandLineRunner.run()方法是 Spring Boot 应用完全启动后执行初始化逻辑的入口点。它的核心作用是在应用所有组件就绪后,执行一次性的启动任务

使用 CommandLineRunner可以确保:

  • 所有依赖的 Spring Bean 都已就绪

  • XXL-Job 执行器已完成基础初始化

  • 可以安全地进行任务处理器的注册

  • 拓展

    CommandLineRunner和 @PostConstruct区别

    特性@PostConstructCommandLineRunner
    执行时机Bean初始化后立即执行整个应用完全启动后执行
    执行顺序较早,在Bean生命周期内较晚,应用级初始化
    依赖保证当前Bean的依赖就绪所有Bean都就绪
    使用场景Bean级别的初始化应用级别的初始化
    参数支持无参数支持命令行参数
    执行控制自动执行,无法跳过可通过条件注解控制

3. GLUE(Java)模式 - 动态执行(无需注册)

GLUE 模式不需要在执行器端注册任务处理器,它的执行流程不同:

核心思想:将任务的实现代码(如 Java, Shell, Python 等脚本)直接保存在调度中心的数据库中。执行器在触发任务时,动态获取这些代码并加载、编译、执行,就像“胶水”一样将代码动态粘合到运行时环境中

  • 代码存储在调度中心数据库,执行器触发时动态编译执行,无需在执行器端写代码。

  • 支持 Java、Shell、Python 等多种脚本,Java 模式需手动导入依赖包。

  • 隔离性强(独立 Groovy 环境),但复杂业务逻辑开发和调试效率低。

执行流程

  1. 调度中心保存代码:GLUE 脚本保存在调度中心数据库

  2. 动态推送执行:任务触发时,调度中心将代码推送给执行器

  3. 动态编译执行:执行器动态编译并执行代码

示例 GLUE 代码(在调度中心界面编写)同样需要导包

总结:三种实现方式核心优劣对比

实现方式核心优势核心劣势适用场景
1. @XxlJob 注解模式开发快速(仅需注解 + 方法)、自动扫描注册、集成 Spring 容器依赖 Spring 环境、灵活性有限大多数常规定时任务、快速开发场景
2. 手动注册 Bean 模式注册时机可控、支持动态注册 / 注销、可测试性强代码量多、需手动管理注册逻辑复杂依赖场景、动态调整任务、单元测试需求
3. GLUE(Java)模式热部署(无需重启执行器)、在线开发、版本回溯调试不便、依赖调度中心存储、性能略低频繁修改业务逻辑
http://www.dtcms.com/a/565638.html

相关文章:

  • 咸阳机场停车省钱攻略
  • 毕设网站开发需要做什么梅州建站推荐
  • 装饰行业网站建设公司网站开发人员的的工资多少
  • 网站后台系统的易用性网站域名实名认证
  • 国产化Word处理控件Spire.Doc教程:如何使用 Java 将 TXT 文本转换为 Excel 表格
  • 结构化类型VS标称类型:TS类型系统全解析
  • Git笔记之Git下载、拉取项目、Webstorm更新Git项目报错识别不到git
  • Linux之arm SMMUv3 控制器注册过程分析(7)
  • 临沧市住房和城乡建设网站企业咨询管理公司简介
  • 13-卷积神经网络(CNN):探讨CNN在图像处理中的应用和优势
  • Spring Boot3零基础教程,StreamAPI 的基本用法,笔记99
  • seo关键词排名优化教程seo网站架构设计
  • 宿州做企业网站公司咸阳网站制作公司
  • 一个空间建多个网站的方法wordpress显示用户列表
  • Java中的数组(续)
  • 2025年内蒙古自治区职业院校技能大赛高职组 “信息安全管理与评估”竞赛样题(一)
  • 嵌入式Linux电源管理实战 --深入解析CPU调频governor原理与优化
  • PostIn零基础学习 - 如何快速设计并分享接口文档
  • 我想建立一个网站不知道怎么做啊小白怎么做网站
  • OpenLCA生命周期评估模型构建与分析
  • AR眼镜赋能船舶巡检:打造智能化运维新方案
  • 从“被动监控”到“主动预警”:EasyGBS远程视频监控方案助力企业高效安全运营
  • 《A Bilateral CFAR Algorithm for Ship Detection in SAR Images》译读笔记
  • 网站图标 psd门户网站的优点
  • 中国交通建设集团网站单页主题 wordpress
  • 网站建设 年终总结沈阳市建设工程安全监督站网站
  • 2.1.2.CSS3
  • 线性代数 - 线性方程组的 LU 分解解法
  • 学习中小牢骚1
  • 游戏网站怎么做seo网站怎么做下载网页代码吗