Skip to content

XXL-Job GLUE 代码开发模式

传统方式下,改一行代码需要:修改代码 → 编译 → 测试 → 打包 → 部署。

对于临时需求、紧急修复,这个流程太慢了。

GLUE 模式就是为了解决这个问题——在线编写代码,实时生效,无需部署。

什么是 GLUE 模式?

┌─────────────────────────────────────────────────────────────┐
│                    GLUE 模式原理                            │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│   传统模式:                                                 │
│   ┌─────────────────────────────────────────────────────┐  │
│   │  编写代码 → 编译 → 测试 → 部署                       │  │
│   │                          至少 30 分钟                 │  │
│   └─────────────────────────────────────────────────────┘  │
│                                                             │
│   GLUE 模式:                                               │
│   ┌─────────────────────────────────────────────────────┐  │
│   │  在 Web 界面写代码 → 保存 → 立即生效                   │  │
│   │                          几秒钟!                      │  │
│   └─────────────────────────────────────────────────────┘  │
│                                                             │
│   就像「胶水」一样,把代码「粘」到任务上                       │
│                                                             │
└─────────────────────────────────────────────────────────────┘

GLUE 模式类型

XXL-Job 支持多种 GLUE 类型:

类型说明执行方式
GLUE_JAVAJava 代码动态编译
GLUE_SHELLShell 脚本ProcessBuilder
GLUE_PYTHONPython 脚本ProcessBuilder
GLUE_NODEJSNode.jsProcessBuilder
GLUE_PHPPHP 脚本ProcessBuilder
GLUE_GOGo 程序ProcessBuilder
GLUE_POWERSHELLPowerShellProcessBuilder

GLUE_JAVA:在线编写 Java 代码

编写 GLUE 代码

在调度中心编写代码:

java
package com.xxl.job.service.handler;

import com.xxl.job.core.context.XxlJobHelper;
import com.xxl.job.core.handler.IJobHandler;

public class MyGlueJob extends IJobHandler {

    @Override
    public void execute() throws Exception {
        String param = XxlJobHelper.getJobParam();
        XxlJobHelper.log("开始执行 GLUE 任务,参数:" + param);

        // 执行业务逻辑
        for (int i = 0; i < 10; i++) {
            XxlJobHelper.log("处理第 " + i + " 条数据");
            
            // 模拟处理
            Thread.sleep(1000);
        }

        XxlJobHelper.log("GLUE 任务执行完成");
    }
}

代码更新机制

┌─────────────────────────────────────────────────────────────┐
│                    GLUE 代码更新流程                        │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│   1. 修改代码并保存                                         │
│      ┌─────────────────────────────────────────────────┐   │
│      │ UPDATE xxl_job_info                              │   │
│      │ SET glue_source = '新代码',                        │   │
│      │     glue_updatetime = NOW()                       │   │
│      │ WHERE id = :jobId                                │   │
│      └─────────────────────────────────────────────────┘   │
│                                                             │
│   2. 下次执行时,加载最新代码                               │
│      ┌─────────────────────────────────────────────────┐   │
│      │ SELECT glue_source FROM xxl_job_info             │   │
│      │ WHERE id = :jobId                                │   │
│      └─────────────────────────────────────────────────┘   │
│                            │                               │
│                            ▼                               │
│   3. 动态编译                                              │
│      ┌─────────────────────────────────────────────────┐   │
│      │ 使用 JavaCompiler API 编译源代码                  │   │
│      │ 生成的 .class 文件存入内存或临时文件              │   │
│      └─────────────────────────────────────────────────┘   │
│                            │                               │
│                            ▼                               │
│   4. 反射调用                                              │
│      ┌─────────────────────────────────────────────────┐   │
│      │ Class cls = loader.loadClass(className);        │   │
│      │ IJobHandler handler = (IJobHandler) cls.newInstance();│
│      │ handler.execute();                               │   │
│      └─────────────────────────────────────────────────┘   │
│                                                             │
└─────────────────────────────────────────────────────────────┘

编译实现

java
public class GlueCompiler {
    
    public IJobHandler compile(String javaSource) {
        // 1. 获取 Java 编译器
        JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
        
        // 2. 创建临时文件
        String className = extractClassName(javaSource);
        JavaSourceFromString sourceCode = new JavaSourceFromString(className, javaSource);
        
        // 3. 编译
        StandardJavaFileManager fileManager = compiler.getStandardFileManager(null, null, null);
        Iterable<? extends JavaFileObject> compilationUnits = Arrays.asList(sourceCode);
        
        boolean success = compiler.getTask(null, fileManager, null, null, null, compilationUnits).call();
        
        if (!success) {
            throw new RuntimeException("GLUE 代码编译失败");
        }
        
        // 4. 加载类
        ClassLoader loader = new MemoryClassLoader();
        return (IJobHandler) loader.loadClass(className).newInstance();
    }
}

GLUE_SHELL:执行 Shell 脚本

编写 Shell 脚本

bash
#!/bin/bash

echo "========================================="
echo "开始执行 Shell 脚本任务"
echo "参数: $1"
echo "========================================="

# 模拟业务逻辑
for i in {1..5}
do
    echo "处理第 $i 批数据..."
    sleep 1
done

# 检查上一个命令的退出状态
if [ $? -eq 0 ]; then
    echo "脚本执行成功"
    exit 0
else
    echo "脚本执行失败"
    exit 1
fi

执行过程

java
public class ShellGlueExecutor {
    
    public int execute(String scriptContent, String params) {
        // 1. 创建临时脚本文件
        File tempFile = File.createTempFile("xxl-job-", ".sh");
        tempFile.deleteOnExit();
        
        // 2. 写入脚本内容
        Files.write(tempFile.toPath(), scriptContent.getBytes());
        
        // 3. 设置执行权限
        tempFile.setExecutable(true);
        
        // 4. 执行脚本
        ProcessBuilder pb = new ProcessBuilder("/bin/bash", tempFile.getPath(), params);
        pb.redirectErrorStream(true);
        
        Process process = pb.start();
        
        // 5. 读取输出
        try (BufferedReader reader = new BufferedReader(
                new InputStreamReader(process.getInputStream()))) {
            String line;
            while ((line = reader.readLine()) != null) {
                XxlJobHelper.log(line);
            }
        }
        
        // 6. 等待完成
        int exitCode = process.waitFor();
        
        // 7. 删除临时文件
        tempFile.delete();
        
        return exitCode;
    }
}

GLUE_PYTHON:执行 Python 脚本

编写 Python 脚本

python
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import sys
import time

def main():
    print("=" * 40)
    print("开始执行 Python 脚本任务")
    
    # 获取参数
    params = sys.argv[1] if len(sys.argv) > 1 else "无参数"
    print(f"参数: {params}")
    
    # 模拟业务逻辑
    for i in range(1, 6):
        print(f"处理第 {i} 批数据...")
        time.sleep(1)
    
    print("脚本执行成功")
    return 0

if __name__ == "__main__":
    sys.exit(main())

版本管理

GLUE 代码支持版本管理:

┌─────────────────────────────────────────────────────────────┐
│                    GLUE 版本管理                             │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│   ┌─────────────────────────────────────────────────────┐  │
│   │ 版本历史                                             │  │
│   ├─────────────────────────────────────────────────────┤  │
│   │ v1  2024-01-01 10:00:00  初始版本                    │  │
│   │ v2  2024-01-02 14:30:00  修复 BUG                    │  │
│   │ v3  2024-01-03 09:15:00  新增功能                    │  │
│   └─────────────────────────────────────────────────────┘  │
│                                                             │
│   [查看] [回滚] [对比]                                      │
│                                                             │
└─────────────────────────────────────────────────────────────┘

回滚功能

java
public class GlueVersionService {
    
    public void rollback(long jobId, int version) {
        // 1. 获取指定版本的代码
        GlueSource oldSource = glueSourceDao.findByJobIdAndVersion(jobId, version);
        
        if (oldSource == null) {
            throw new RuntimeException("版本不存在");
        }
        
        // 2. 恢复到当前版本
        JobInfo job = jobInfoDao.findById(jobId);
        job.setGlueSource(oldSource.getSource());
        jobInfoDao.update(job);
        
        // 3. 记录操作日志
        log("回滚任务 " + jobId + " 到版本 " + version);
    }
}

安全机制

GLUE 模式虽然方便,但也带来安全风险,需要注意:

风险一:代码注入

❌ 危险写法
String userInput = XxlJobHelper.getJobParam();
String code = "SELECT * FROM users WHERE name = '" + userInput + "'";

// 如果 userInput = "'; DROP TABLE users; --"
// 实际执行的 SQL 变成:
// SELECT * FROM users WHERE name = ''; DROP TABLE users; --'
✅ 安全写法
String userInput = XxlJobHelper.getJobParam();
String code = "SELECT * FROM users WHERE name = ?";
// 使用参数化查询

风险二:资源耗尽

❌ 危险写法:无限循环
while (true) {
    processData();
}
✅ 安全写法:设置超时
long startTime = System.currentTimeMillis();
while (true) {
    if (System.currentTimeMillis() - startTime > 60000) {
        throw new RuntimeException("执行超时");
    }
    if (!hasMoreData()) {
        break;
    }
    processData();
}

风险三:权限控制

java
// 限制可使用的包
public class GlueSecurityManager {
    
    private static final Set<String> ALLOWED_PACKAGES = new HashSet<>(
        Arrays.asList(
            "com.xxl.job.core",
            "java.util",
            "java.lang",
            "java.text",
            "java.math"
        )
    );
    
    public void validateCode(String code) {
        // 禁止使用反射
        if (code.contains("java.lang.reflect")) {
            throw new SecurityException("禁止使用反射");
        }
        
        // 禁止使用 Runtime.exec
        if (code.contains("Runtime.getRuntime().exec")) {
            throw new SecurityException("禁止执行系统命令");
        }
        
        // 禁止访问文件系统
        if (code.contains("java.io.File")) {
            throw new SecurityException("禁止访问文件系统");
        }
    }
}

适用场景

┌─────────────────────────────────────────────────────────────┐
│                    GLUE 模式适用场景                        │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│   ✅ 适合使用 GLUE 的场景:                                   │
│   · 临时需求、快速验证                                       │
│   · 紧急修复线上问题                                         │
│   · 简单脚本任务                                             │
│   · 不经常修改的一次性任务                                   │
│                                                             │
│   ❌ 不适合使用 GLUE 的场景:                                 │
│   · 复杂业务逻辑                                             │
│   · 需要单元测试                                             │
│   · 频繁修改的核心任务                                       │
│   · 需要版本控制                                             │
│                                                             │
└─────────────────────────────────────────────────────────────┘

总结

维度GLUE 模式Bean 模式
部署无需部署需要重新部署
生效速度实时需要构建
代码管理界面编辑IDE 开发
版本控制简单记录Git 管理
调试日志输出IDE 断点
安全性风险较高较安全
适用场景临时/简单正式/复杂

思考题

GLUE 代码存在数据库中,每次执行都需要从数据库读取并编译。

如果任务并发执行 1000 次,编译会成为瓶颈吗?

如何优化 GLUE 代码的编译和执行性能?

基于 VitePress 构建