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进行注册(下面是底层方法)
自动注册的核心流程
- 项目启动时,Spring 扫描
XxlJobConfig配置类,创建XxlJobSpringExecutor执行器实例。 XxlJobSpringExecutor实现了SmartInitializingSingleton接口(在所有非懒加载单例 Bean 初始化完成后,执行特定逻辑),在所有 Spring Bean 初始化完成后,会调用afterSingletonsInstantiated方法。- 该方法内部执行
initJobHandlerMethodRepository,扫描容器中所有非懒加载 Bean 的方法。 - 找到带有
@XxlJob注解的方法,封装为JobHandler,通过registJobHandler注册到执行器容器。 - 调度中心通过 “任务名称”(注解中指定的 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 环境),但复杂业务逻辑开发和调试效率低。
执行流程
调度中心保存代码:GLUE 脚本保存在调度中心数据库
动态推送执行:任务触发时,调度中心将代码推送给执行器
动态编译执行:执行器动态编译并执行代码
示例 GLUE 代码(在调度中心界面编写)同样需要导包

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