xxljob源码

xxljob源码最有用的地方就是能够亲身感受NIO网络连接的具体实现EmbedServer。​​​​​​​

NIO(Non-blocking I/O) 是 Java 提供的非阻塞 I/O 模型,核心特点是:

(1)非阻塞:线程不会因为等待 I/O 而阻塞

(2)事件驱动:基于事件通知机制

(3)单线程多连接:一个线程可以处理多个连接

BIO(Blocking I/O,阻塞I/O)是一种同步阻塞的网络通信模式。在这种模式下,当线程执行I/O操作时,必须等待操作完成才能继续执行后续代码。核心特点是:

(1)读取数据时:如果没有数据可读,线程会一直阻塞,直到有数据到达

(2)写入数据时:如果缓冲区已满,线程会阻塞直到可以写入数据

(3)接收连接时:如果没有新的连接请求,线程会阻塞等待

java 复制代码
**传统 BIO(阻塞 I/O)**:
// 伪代码:BIO 模式
while (true) {
    Socket socket = serverSocket.accept();  // 阻塞等待连接
    new Thread(() -> {
        socket.read();  // 阻塞读取数据
        process();      // 处理业务
        socket.write(); // 阻塞写入数据
    }).start();
}
- 问题:每个连接需要一个线程,线程资源消耗大
- 问题:线程阻塞在 I/O 操作上,CPU 利用率低

**NIO(非阻塞 I/O)**:
// 伪代码:NIO 模式
while (true) {
    selector.select();  // 非阻塞,检查就绪事件
    for (SelectionKey key : selectedKeys) {
        if (key.isReadable()) {
            channel.read();  // 非阻塞读取
            process();       // 处理业务
        }
    }
}
- 优势:一个线程可以处理多个连接
- 优势:线程不会阻塞,CPU 利用率高
- 优势:支持高并发连接

其次就是调度中心和执行器的关系,调度中心是定时器是Admin中手动触发任务的地方,执行器是我们希望定时到了的时候能执行的动作。二者之间的关系是通过发送EmbedServer走http协议完成的。

java 复制代码
调度中心(xxl-job admin)       执行器(你的程序)
  │                                │
  │  1. 触发任务                    │
  │  JobTriggerPoolHelper.trigger() │
  │         │                       │
  │         │  2. HTTP POST /run    │
  │         ├──────────────────────>│
  │         │  TriggerParam         │
  │         │                       │  3. 接收请求
  │         │                       │  EmbedServer.process()
  │         │                       │
  │         │  4. 调用业务方法       │
  │         │                       │  ExecutorBizImpl.run()
  │         │                       │
  │         │  5. 加入队列          │
  │         │                       │  jobThread.pushTriggerQueue()
  │         │                       │
  │         │  6. HTTP 200 OK       │
  │         │<──────────────────────┤
  │         │  ReturnT              │
  │         │                       │
  │         │                       │  7. 执行任务
  │         │                       │  JobThread.run()
  │         │                       │  handler.execute()
  │         │                       │
  │         │                       │  8. 推送回调
  │         │                       │  TriggerCallbackThread.pushCallBack()
  │         │                       │
  │         │  9. HTTP POST /api/callback│
  │         │<──────────────────────┤
  │         │  HandleCallbackParam  │
  │         │                       │
  │  10. 处理回调                   │
  │  JobCompleteHelper.callback()   │
  │         │                       │
  │         │  11. HTTP 200 OK      │
  │         ├──────────────────────>│
  │         │  ReturnT              │
  │         │                       │

接下来,就是根据下述代码流程,跟着源代码单步追踪,即可执行xxl-job-admin(调度中心)和xxl-job-executor-samples(执行器)。

一、调度中心的启动阶段

java 复制代码
xxl-job-admin/src/main/java/com/xxl/job/admin/scheduler/scheduler/XxlJobScheduler.java
    public void init() throws Exception {
        // init i18n
        initI18n();

        // admin trigger pool start
        JobTriggerPoolHelper.toStart();

        // admin registry monitor run
        JobRegistryHelper.getInstance().start();

        // admin fail-monitor run
        JobFailMonitorHelper.getInstance().start();

        // admin lose-monitor run ( depend on JobTriggerPoolHelper )
        JobCompleteHelper.getInstance().start();

        // admin log report start
        JobLogReportHelper.getInstance().start();

        // start-schedule  ( depend on JobTriggerPoolHelper )
        JobScheduleHelper.getInstance().start();

        logger.info(">>>>>>>>> init xxl-job admin success.");
    }

二、执行器的启动阶段

2.1 配置application.properties

java 复制代码
### xxl-job admin address list
xxl.job.admin.addresses=http://127.0.0.1:9091/xxl-job-admin
### xxl-job, access token
xxl.job.admin.accessToken=default_token
### xxl-job timeout by second
xxl.job.admin.timeout=3

### xxl-job executor appname
xxl.job.executor.appname=xxl-job-executor-sample
### xxl-job executor registry-address
xxl.job.executor.address=
### xxl-job executor server-info
xxl.job.executor.ip=
xxl.job.executor.port=9999
### xxl-job executor log-path
xxl.job.executor.logpath=D:/logs/xxl-job/jobhandler
### xxl-job executor log-retention-days
xxl.job.executor.logretentiondays=30

2.2 JobHandler注册

2.2.1 扫描 Bean

java 复制代码
private void initJobHandlerMethodRepository(ApplicationContext applicationContext) {
        if (applicationContext == null) {
            return;
        }
        // init job handler from method
        // 获取所有 Bean 定义,单例,allowEagerInit=false,避免提前初始化
        String[] beanDefinitionNames = applicationContext.getBeanNamesForType(Object.class, false, false);  // allowEagerInit=false, avoid early initialization
        for (String beanDefinitionName : beanDefinitionNames) {

            // filter system bean
            // 跳过 Spring 框架自身的 Bean,只处理业务 Bean
            if (isSystemBean(beanDefinitionName)) {
                continue;
            }

            // get bean
            // 跳过标记为 @Lazy 的 Bean,避免触发延迟初始化,非 @Lazy 的 Bean 才获取实例
            Object bean = null;
            Lazy onBean = applicationContext.findAnnotationOnBean(beanDefinitionName, Lazy.class);
            if (onBean!=null){
                logger.debug("xxl-job annotation scan, skip @Lazy Bean:{}", beanDefinitionName);
                continue;
            }else {
                bean = applicationContext.getBean(beanDefinitionName);
            }

            // filter method
            // 使用 MethodIntrospector.selectMethods 扫描类的方法
            // 通过 MetadataLookup 对每个方法检查 @XxlJob 注解
            // AnnotatedElementUtils.findMergedAnnotation 支持注解继承与合并
            // 返回 Map<Method, XxlJob>,键为方法,值为注解对象
            Map<Method, XxlJob> annotatedMethods = null;   // referred to :org.springframework.context.event.EventListenerMethodProcessor.processBean
            try {
                // 查找 @XxlJob 方法
                annotatedMethods = MethodIntrospector.selectMethods(bean.getClass(),
                        new MethodIntrospector.MetadataLookup<XxlJob>() {
                            @Override
                            public XxlJob inspect(Method method) {
                                return 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()) {
                continue;
            }

            // generate and regist method job handler
            // 遍历扫描结果,调用 registJobHandler 注册
            for (Map.Entry<Method, XxlJob> methodXxlJobEntry : annotatedMethods.entrySet()) {
                Method executeMethod = methodXxlJobEntry.getKey();
                XxlJob xxlJob = methodXxlJobEntry.getValue();
                // regist
                // 注册处理器
                registJobHandler(xxlJob, bean, executeMethod);
            }

        }
    }

2.2.2 GlueFactory刷新

java 复制代码
public class SpringGlueFactory extends GlueFactory {
    private static Logger logger = LoggerFactory.getLogger(SpringGlueFactory.class);


    /**
     * inject action of spring
     * @param instance
     */
    @Override
    public void injectService(Object instance){
        if (instance==null) {
            return;
        }

        if (XxlJobSpringExecutor.getApplicationContext() == null) {
            return;
        }

        Field[] fields = instance.getClass().getDeclaredFields();
        for (Field field : fields) {
            if (Modifier.isStatic(field.getModifiers())) {
                continue;
            }

            Object fieldBean = null;
            // with bean-id, bean could be found by both @Resource and @Autowired, or bean could only be found by @Autowired

            if (AnnotationUtils.getAnnotation(field, Resource.class) != null) {
                try {
                    Resource resource = AnnotationUtils.getAnnotation(field, Resource.class);
                    if (resource.name()!=null && resource.name().length()>0){
                        fieldBean = XxlJobSpringExecutor.getApplicationContext().getBean(resource.name());
                    } else {
                        fieldBean = XxlJobSpringExecutor.getApplicationContext().getBean(field.getName());
                    }
                } catch (Exception e) {
                }
                if (fieldBean==null ) {
                    fieldBean = XxlJobSpringExecutor.getApplicationContext().getBean(field.getType());
                }
            } else if (AnnotationUtils.getAnnotation(field, Autowired.class) != null) {
                Qualifier qualifier = AnnotationUtils.getAnnotation(field, Qualifier.class);
                if (qualifier!=null && qualifier.value()!=null && qualifier.value().length()>0) {
                    fieldBean = XxlJobSpringExecutor.getApplicationContext().getBean(qualifier.value());
                } else {
                    fieldBean = XxlJobSpringExecutor.getApplicationContext().getBean(field.getType());
                }
            }

            if (fieldBean!=null) {
                field.setAccessible(true);
                try {
                    field.set(instance, fieldBean);
                } catch (Exception e) {
                    logger.error(e.getMessage(), e);
                }
            }
        }
    }

}

2.2.3创建客户端列表

java 复制代码
private void initAdminBizList(String adminAddresses, String accessToken, int timeout) throws Exception {
        if (adminAddresses!=null && adminAddresses.trim().length()>0) {
            for (String address: adminAddresses.trim().split(",")) {
                if (address!=null && address.trim().length()>0) {

                    AdminBiz adminBiz = new AdminBizClient(address.trim(), accessToken, timeout);

                    if (adminBizList == null) {
                        adminBizList = new ArrayList<AdminBiz>();
                    }
                    adminBizList.add(adminBiz);
                }
            }
        }
    }

2.2.4 回调线程启动

java 复制代码
public void start() {

        // valid
        if (XxlJobExecutor.getAdminBizList() == null) {
            logger.warn(">>>>>>>>>>> xxl-job, executor callback config fail, adminAddresses is null.");
            return;
        }

        // callback
        triggerCallbackThread = new Thread(new Runnable() {

            @Override
            public void run() {

                // normal callback
                while(!toStop){
                    try {
                        HandleCallbackParam callback = getInstance().callBackQueue.take();
                        if (callback != null) {

                            // callback list param
                            List<HandleCallbackParam> callbackParamList = new ArrayList<HandleCallbackParam>();
                            int drainToNum = getInstance().callBackQueue.drainTo(callbackParamList);
                            callbackParamList.add(callback);

                            // callback, will retry if error
                            if (callbackParamList!=null && callbackParamList.size()>0) {
                                doCallback(callbackParamList);
                            }
                        }
                    } catch (Throwable e) {
                        if (!toStop) {
                            logger.error(e.getMessage(), e);
                        }
                    }
                }

                // last callback
                try {
                    List<HandleCallbackParam> callbackParamList = new ArrayList<HandleCallbackParam>();
                    int drainToNum = getInstance().callBackQueue.drainTo(callbackParamList);
                    if (callbackParamList!=null && callbackParamList.size()>0) {
                        doCallback(callbackParamList);
                    }
                } catch (Throwable e) {
                    if (!toStop) {
                        logger.error(e.getMessage(), e);
                    }
                }
                logger.info(">>>>>>>>>>> xxl-job, executor callback thread destroy.");

            }
        });
        triggerCallbackThread.setDaemon(true);
        triggerCallbackThread.setName("xxl-job, executor TriggerCallbackThread");
        triggerCallbackThread.start();


        // retry
        triggerRetryCallbackThread = new Thread(new Runnable() {
            @Override
            public void run() {
                while(!toStop){
                    try {
                        retryFailCallbackFile();
                    } catch (Throwable e) {
                        if (!toStop) {
                            logger.error(e.getMessage(), e);
                        }

                    }
                    try {
                        TimeUnit.SECONDS.sleep(RegistryConfig.BEAT_TIMEOUT);
                    } catch (Throwable e) {
                        if (!toStop) {
                            logger.error(e.getMessage(), e);
                        }
                    }
                }
                logger.info(">>>>>>>>>>> xxl-job, executor retry callback thread destroy.");
            }
        });
        triggerRetryCallbackThread.setDaemon(true);
        triggerRetryCallbackThread.start();

    }

2.2.5 内嵌服务器启动

java 复制代码
// XxlJobExecutor
private void initEmbedServer(String address, String ip, int port, String appname, String accessToken) throws Exception {

        // fill ip port
        port = port>0?port: NetUtil.findAvailablePort(9999);
        ip = (ip!=null&&ip.trim().length()>0)?ip: IpUtil.getIp();

        // generate address
        if (address==null || address.trim().length()==0) {
            String ip_port_address = IpUtil.getIpPort(ip, port);   // registry-address:default use address to registry , otherwise use ip:port if address is null
            address = "http://{ip_port}/".replace("{ip_port}", ip_port_address);
        }

        // accessToken
        if (accessToken==null || accessToken.trim().length()==0) {
            logger.warn(">>>>>>>>>>> xxl-job accessToken is empty. To ensure system security, please set the accessToken.");
        }

        // start
        embedServer = new EmbedServer();
        embedServer.start(address, port, appname, accessToken);
    }

// EmbedServer
public void start(final String address, final int port, final String appname, final String accessToken) {
        executorBiz = new ExecutorBizImpl();
        thread = new Thread(new Runnable() {
            @Override
            public void run() {
                // param
                EventLoopGroup bossGroup = new NioEventLoopGroup();
                EventLoopGroup workerGroup = new NioEventLoopGroup();
                ThreadPoolExecutor bizThreadPool = new ThreadPoolExecutor(
                        0,
                        200,
                        60L,
                        TimeUnit.SECONDS,
                        new LinkedBlockingQueue<Runnable>(2000),
                        new ThreadFactory() {
                            @Override
                            public Thread newThread(Runnable r) {
                                return new Thread(r, "xxl-job, EmbedServer bizThreadPool-" + r.hashCode());
                            }
                        },
                        new RejectedExecutionHandler() {
                            @Override
                            public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
                                throw new RuntimeException("xxl-job, EmbedServer bizThreadPool is EXHAUSTED!");
                            }
                        });
                try {
                    // start server
                    ServerBootstrap bootstrap = new ServerBootstrap();
                    bootstrap.group(bossGroup, workerGroup)
                            .channel(NioServerSocketChannel.class)
                            .childHandler(new ChannelInitializer<SocketChannel>() {
                                @Override
                                public void initChannel(SocketChannel channel) throws Exception {
                                    channel.pipeline()
                                            .addLast(new IdleStateHandler(0, 0, 30 * 3, TimeUnit.SECONDS))  // beat 3N, close if idle
                                            .addLast(new HttpServerCodec())
                                            .addLast(new HttpObjectAggregator(5 * 1024 * 1024))  // merge request & reponse to FULL
                                            .addLast(new EmbedHttpServerHandler(executorBiz, accessToken, bizThreadPool));
                                }
                            })
                            .childOption(ChannelOption.SO_KEEPALIVE, true);

                    // bind
                    ChannelFuture future = bootstrap.bind(port).sync();

                    logger.info(">>>>>>>>>>> xxl-job remoting server start success, nettype = {}, port = {}", EmbedServer.class, port);

                    // start registry
                    startRegistry(appname, address);

                    // wait util stop
                    future.channel().closeFuture().sync();

                } catch (InterruptedException e) {
                    logger.info(">>>>>>>>>>> xxl-job remoting server stop.");
                } catch (Throwable e) {
                    logger.error(">>>>>>>>>>> xxl-job remoting server error.", e);
                } finally {
                    // stop
                    try {
                        workerGroup.shutdownGracefully();
                        bossGroup.shutdownGracefully();
                    } catch (Throwable e) {
                        logger.error(e.getMessage(), e);
                    }
                }
            }
        });
        thread.setDaemon(true);    // daemon, service jvm, user thread leave >>> daemon leave >>> jvm leave
        thread.start();
    }

三、执行阶段:阶段1: 调度中心触发阶段

JobScheduleHelper / JobTriggerPoolHelper

java 复制代码
**位置**:`XxlJobTrigger.runExecutor()`

**调用链**:

```207:211:xxl-job-admin/src/main/java/com/xxl/job/admin/scheduler/trigger/XxlJobTrigger.java
    public static ReturnT<String> runExecutor(TriggerParam triggerParam, String address){
        ReturnT<String> runResult = null;
        try {
            ExecutorBiz executorBiz = XxlJobScheduler.getExecutorBiz(address);
            runResult = executorBiz.run(triggerParam);
```

**关键操作**:

1. **获取执行器客户端**
   - 类:`XxlJobScheduler`
   - 方法:`getExecutorBiz(address)`
   - 作用:从缓存获取或创建 `ExecutorBizClient` 实例
   - 缓存键:执行器地址(如 `http://192.168.1.100:9999/`)

2. **调用执行器接口**
   - 接口:`ExecutorBiz.run(TriggerParam)`
   - 实现类:`ExecutorBizClient`
   - 参数:`TriggerParam`(包含任务ID、Handler、参数等)

**TriggerParam 参数内容**:

```java
TriggerParam {
    jobId: 1,                              // 任务ID
    executorHandler: "demoJobHandler",     // 执行器Handler名称
    executorParams: "param1=value1",       // 执行器参数
    executorBlockStrategy: "SERIAL_EXECUTION",  // 阻塞策略
    executorTimeout: 0,                    // 超时时间
    logId: 12345,                          // 日志ID
    logDateTime: 1699123456789,            // 日志时间
    glueType: "BEAN",                      // 任务类型
    glueSource: null,                      // GLUE源码(GLUE类型时使用)
    glueUpdatetime: 0,                     // GLUE更新时间
    broadcastIndex: 0,                     // 广播分片索引
    broadcastTotal: 1                      // 广播分片总数
}
```

四、执行阶段:阶段2: HTTP请求发送阶段 (调用执行器)

ExecutorBizClient → HTTP POST

java 复制代码
**位置**:`ExecutorBizClient.run()`

**关键代码**:

```45:48:xxl-job-core/src/main/java/com/xxl/job/core/biz/client/ExecutorBizClient.java
    @Override
    public ReturnT<String> run(TriggerParam triggerParam) {
        return XxlJobRemotingUtil.postBody(addressUrl + "run", accessToken, timeout, triggerParam, String.class);
    }
```

**HTTP请求详情**:

```
POST http://192.168.1.100:9999/run HTTP/1.1
Host: 192.168.1.100:9999
Connection: Keep-Alive
Content-Type: application/json;charset=UTF-8
XXL-JOB-ACCESS-TOKEN: your-access-token
Content-Length: 256

{
  "jobId": 1,
  "executorHandler": "demoJobHandler",
  "executorParams": "param1=value1",
  "executorBlockStrategy": "SERIAL_EXECUTION",
  "executorTimeout": 0,
  "logId": 12345,
  "logDateTime": 1699123456789,
  "glueType": "BEAN",
  "glueSource": null,
  "glueUpdatetime": 0,
  "broadcastIndex": 0,
  "broadcastTotal": 1
}
```

**关键点**:
- **协议**:HTTP POST
- **URL**:`http://执行器地址/run`
- **认证**:通过 `XXL-JOB-ACCESS-TOKEN` 请求头传递Token
- **数据格式**:JSON(`TriggerParam` 对象序列化)
- **超时时间**:默认3秒(可配置)

五、执行阶段:阶段3: 执行器接收处理阶段

EmbedServer → ExecutorBizImpl

java 复制代码
**位置**:`EmbedServer.process()` → `ExecutorBizImpl.run()`

**处理流程**:

1. **HTTP服务器接收请求**
   - 类:`EmbedServer.EmbedHttpServerHandler`
   - 操作:Netty HTTP服务器接收HTTP请求
   - 路由:根据URI(`/run`)路由到对应的处理方法

2. **解析请求参数**
   - 操作:将HTTP请求体(JSON)反序列化为 `TriggerParam` 对象
   - 工具:`GsonTool.fromJson(requestData, TriggerParam.class)`

3. **调用业务处理方法**
   - 类:`ExecutorBizImpl`
   - 方法:`run(TriggerParam triggerParam)`
   - 作用:
     - 加载或创建 `JobThread`
     - 加载任务处理器(`IJobHandler`)
     - 将任务推送到 `JobThread` 的队列中

**ExecutorBizImpl.run() 核心逻辑**:

```47:75:xxl-job-core/src/main/java/com/xxl/job/core/biz/impl/ExecutorBizImpl.java
    @Override
    public ReturnT<String> run(TriggerParam triggerParam) {
        // load old:jobHandler + jobThread
        JobThread jobThread = XxlJobExecutor.loadJobThread(triggerParam.getJobId());
        IJobHandler jobHandler = jobThread!=null?jobThread.getHandler():null;
        String removeOldReason = null;

        // valid:jobHandler + jobThread
        GlueTypeEnum glueTypeEnum = GlueTypeEnum.match(triggerParam.getGlueType());
        if (GlueTypeEnum.BEAN == glueTypeEnum) {

            // new jobhandler
            IJobHandler newJobHandler = XxlJobExecutor.loadJobHandler(triggerParam.getExecutorHandler());

            // valid old jobThread
            if (jobThread!=null && jobHandler != newJobHandler) {
                // change handler, need kill old thread
                removeOldReason = "change jobhandler or glue type, and terminate the old job thread.";

                jobThread = null;
                jobHandler = null;
            }

            // valid handler
            if (jobHandler == null) {
                jobHandler = newJobHandler;
                if (jobHandler == null) {
                    return ReturnT.ofFail( "job handler [" + triggerParam.getExecutorHandler() + "] not found.");
                }
            }
```

**关键步骤**:

1. **加载JobThread**
   - 方法:`XxlJobExecutor.loadJobThread(jobId)`
   - 作用:从执行器的 `jobThreadRepository` 中获取对应的任务线程

2. **加载JobHandler**
   - 方法:`XxlJobExecutor.loadJobHandler(executorHandler)`
   - 作用:从执行器的 `jobHandlerRepository` 中获取任务处理器

3. **推送任务到队列**
   - 方法:`jobThread.pushTriggerQueue(triggerParam)`
   - 作用:将 `TriggerParam` 添加到 `JobThread` 的 `triggerQueue` 队列中

4. **返回响应**
   - 返回:`ReturnT<String>`(包含状态码和消息)
   - HTTP响应:JSON格式返回给调度中心

**HTTP响应内容**:

```
HTTP/1.1 200 OK
Content-Type: text/html;charset=UTF-8
Content-Length: 45
Connection: Keep-Alive

{
  "code": 200,
  "msg": null,
  "content": null
}
```

**关键点**:
- **异步执行**:`run()` 方法只负责将任务加入队列,立即返回,不等待任务执行完成
- **队列机制**:使用 `LinkedBlockingQueue` 作为任务队列,支持阻塞操作
- **线程模型**:每个 `jobId` 对应一个 `JobThread`,线程持续运行,从队列中取任务执行

##### 步骤1:Netty接收HTTP请求

**类**:`EmbedServer.EmbedHttpServerHandler`
**方法**:`channelRead0(ChannelHandlerContext ctx, FullHttpRequest msg)`

**触发方式**:Netty框架自动调用(当HTTP请求到达时)

```148:172:xxl-job-core/src/main/java/com/xxl/job/core/server/EmbedServer.java
        @Override
        protected void channelRead0(final ChannelHandlerContext ctx, FullHttpRequest msg) throws Exception {
            // request parse
            //final byte[] requestBytes = ByteBufUtil.getBytes(msg.content());    // byteBuf.toString(io.netty.util.CharsetUtil.UTF_8);
            String requestData = msg.content().toString(CharsetUtil.UTF_8);
            String uri = msg.uri();
            HttpMethod httpMethod = msg.method();
            boolean keepAlive = HttpUtil.isKeepAlive(msg);
            String accessTokenReq = msg.headers().get(XxlJobRemotingUtil.XXL_JOB_ACCESS_TOKEN);

            // invoke
            bizThreadPool.execute(new Runnable() {
                @Override
                public void run() {
                    // do invoke
                    Object responseObj = process(httpMethod, uri, requestData, accessTokenReq);

                    // to json
                    String responseJson = GsonTool.toJson(responseObj);

                    // write response
                    writeResponse(ctx, keepAlive, responseJson);
                }
            });
        }
```

**关键方法调用**:

1. **解析请求内容**
   - 类:`FullHttpRequest`
   - 方法:`content().toString(CharsetUtil.UTF_8)`
   - 作用:将HTTP请求体转换为字符串(JSON格式)

2. **解析URI**
   - 类:`FullHttpRequest`
   - 方法:`uri()`
   - 作用:获取请求URI(如 `/run`)

3. **解析HTTP方法**
   - 类:`FullHttpRequest`
   - 方法:`method()`
   - 作用:获取HTTP方法(POST)

4. **解析Token**
   - 类:`FullHttpRequest`
   - 方法:`headers().get(XxlJobRemotingUtil.XXL_JOB_ACCESS_TOKEN)`
   - 作用:获取访问令牌

5. **提交到业务线程池**
   - 类:`ThreadPoolExecutor`(`bizThreadPool`)
   - 方法:`execute(Runnable)`
   - 作用:异步处理业务逻辑,不阻塞I/O线程

##### 步骤2:路由处理和参数解析

**类**:`EmbedServer.EmbedHttpServerHandler`
**方法**:`process(HttpMethod httpMethod, String uri, String requestData, String accessTokenReq)`

```174:212:xxl-job-core/src/main/java/com/xxl/job/core/server/EmbedServer.java
        private Object process(HttpMethod httpMethod, String uri, String requestData, String accessTokenReq) {
            // valid
            if (HttpMethod.POST != httpMethod) {
                return ReturnT.ofFail("invalid request, HttpMethod not support.");
            }
            if (uri == null || uri.trim().length() == 0) {
                return ReturnT.ofFail( "invalid request, uri-mapping empty.");
            }
            if (accessToken != null
                    && accessToken.trim().length() > 0
                    && !accessToken.equals(accessTokenReq)) {
                return ReturnT.ofFail("The access token is wrong.");
            }

            // services mapping
            try {
                switch (uri) {
                    case "/beat":
                        return executorBiz.beat();
                    case "/idleBeat":
                        IdleBeatParam idleBeatParam = GsonTool.fromJson(requestData, IdleBeatParam.class);
                        return executorBiz.idleBeat(idleBeatParam);
                    case "/run":
                        TriggerParam triggerParam = GsonTool.fromJson(requestData, TriggerParam.class);
                        return executorBiz.run(triggerParam);
                    case "/kill":
                        KillParam killParam = GsonTool.fromJson(requestData, KillParam.class);
                        return executorBiz.kill(killParam);
                    case "/log":
                        LogParam logParam = GsonTool.fromJson(requestData, LogParam.class);
                        return executorBiz.log(logParam);
                    default:
                        return ReturnT.ofFail( "invalid request, uri-mapping(" + uri + ") not found.");
                }
            } catch (Throwable e) {
                logger.error(e.getMessage(), e);
                return ReturnT.ofFail("request error:" + ThrowableUtil.toString(e));
            }
        }
```

**关键方法调用**:

1. **验证HTTP方法**
   - 操作:检查是否为POST方法
   - 作用:只支持POST请求

2. **验证URI**
   - 操作:检查URI是否为空
   - 作用:确保URI有效

3. **验证AccessToken**
   - 操作:比较请求Token和配置Token
   - 作用:安全认证

4. **反序列化请求参数**
   - 类:`GsonTool`
   - 方法:`fromJson(requestData, TriggerParam.class)`
   - 作用:将JSON字符串反序列化为 `TriggerParam` 对象
   - 参数:`requestData`(JSON字符串)、`TriggerParam.class`(目标类型)

5. **调用业务方法**
   - 类:`ExecutorBizImpl`
   - 方法:`run(triggerParam)`
   - 作用:处理任务触发请求

##### 步骤3:业务处理 - ExecutorBizImpl.run()

**类**:`ExecutorBizImpl`
**方法**:`run(TriggerParam triggerParam)`

```47:149:xxl-job-core/src/main/java/com/xxl/job/core/biz/impl/ExecutorBizImpl.java
    @Override
    public ReturnT<String> run(TriggerParam triggerParam) {
        // load old:jobHandler + jobThread
        JobThread jobThread = XxlJobExecutor.loadJobThread(triggerParam.getJobId());
        IJobHandler jobHandler = jobThread!=null?jobThread.getHandler():null;
        String removeOldReason = null;

        // valid:jobHandler + jobThread
        GlueTypeEnum glueTypeEnum = GlueTypeEnum.match(triggerParam.getGlueType());
        if (GlueTypeEnum.BEAN == glueTypeEnum) {

            // new jobhandler
            IJobHandler newJobHandler = XxlJobExecutor.loadJobHandler(triggerParam.getExecutorHandler());

            // valid old jobThread
            if (jobThread!=null && jobHandler != newJobHandler) {
                // change handler, need kill old thread
                removeOldReason = "change jobhandler or glue type, and terminate the old job thread.";

                jobThread = null;
                jobHandler = null;
            }

            // valid handler
            if (jobHandler == null) {
                jobHandler = newJobHandler;
                if (jobHandler == null) {
                    return ReturnT.ofFail( "job handler [" + triggerParam.getExecutorHandler() + "] not found.");
                }
            }

        } else if (GlueTypeEnum.GLUE_GROOVY == glueTypeEnum) {

            // valid old jobThread
            if (jobThread != null &&
                    !(jobThread.getHandler() instanceof GlueJobHandler
                        && ((GlueJobHandler) jobThread.getHandler()).getGlueUpdatetime()==triggerParam.getGlueUpdatetime() )) {
                // change handler or gluesource updated, need kill old thread
                removeOldReason = "change job source or glue type, and terminate the old job thread.";

                jobThread = null;
                jobHandler = null;
            }

            // valid handler
            if (jobHandler == null) {
                try {
                    IJobHandler originJobHandler = GlueFactory.getInstance().loadNewInstance(triggerParam.getGlueSource());
                    jobHandler = new GlueJobHandler(originJobHandler, triggerParam.getGlueUpdatetime());
                } catch (Exception e) {
                    logger.error(e.getMessage(), e);
                    return ReturnT.ofFail( e.getMessage());
                }
            }
        } else if (glueTypeEnum!=null && glueTypeEnum.isScript()) {

            // valid old jobThread
            if (jobThread != null &&
                    !(jobThread.getHandler() instanceof ScriptJobHandler
                            && ((ScriptJobHandler) jobThread.getHandler()).getGlueUpdatetime()==triggerParam.getGlueUpdatetime() )) {
                // change script or gluesource updated, need kill old thread
                removeOldReason = "change job source or glue type, and terminate the old job thread.";

                jobThread = null;
                jobHandler = null;
            }

            // valid handler
            if (jobHandler == null) {
                jobHandler = new ScriptJobHandler(triggerParam.getJobId(), triggerParam.getGlueUpdatetime(), triggerParam.getGlueSource(), GlueTypeEnum.match(triggerParam.getGlueType()));
            }
        } else {
            return ReturnT.ofFail("glueType[" + triggerParam.getGlueType() + "] is not valid.");
        }

        // executor block strategy
        if (jobThread != null) {
            ExecutorBlockStrategyEnum blockStrategy = ExecutorBlockStrategyEnum.match(triggerParam.getExecutorBlockStrategy(), null);
            if (ExecutorBlockStrategyEnum.DISCARD_LATER == blockStrategy) {
                // discard when running
                if (jobThread.isRunningOrHasQueue()) {
                    return ReturnT.ofFail("block strategy effect:"+ExecutorBlockStrategyEnum.DISCARD_LATER.getTitle());
                }
            } else if (ExecutorBlockStrategyEnum.COVER_EARLY == blockStrategy) {
                // kill running jobThread
                if (jobThread.isRunningOrHasQueue()) {
                    removeOldReason = "block strategy effect:" + ExecutorBlockStrategyEnum.COVER_EARLY.getTitle();

                    jobThread = null;
                }
            } else {
                // just queue trigger
            }
        }

        // replace thread (new or exists invalid)
        if (jobThread == null) {
            jobThread = XxlJobExecutor.registJobThread(triggerParam.getJobId(), jobHandler, removeOldReason);
        }

        // push data to queue
        ReturnT<String> pushResult = jobThread.pushTriggerQueue(triggerParam);
        return pushResult;
    }
```

**关键方法调用**:

1. **加载JobThread**
   - 类:`XxlJobExecutor`
   - 方法:`loadJobThread(triggerParam.getJobId())`
   - 作用:从 `jobThreadRepository` 中获取已存在的任务线程

2. **匹配任务类型**
   - 类:`GlueTypeEnum`
   - 方法:`match(triggerParam.getGlueType())`
   - 作用:根据 `glueType` 匹配任务类型(BEAN、GLUE_GROOVY、脚本等)

3. **加载JobHandler**(BEAN模式)
   - 类:`XxlJobExecutor`
   - 方法:`loadJobHandler(triggerParam.getExecutorHandler())`
   - 作用:从 `jobHandlerRepository` 中获取任务处理器
   - 返回值:`IJobHandler`(如 `MethodJobHandler`)

4. **处理阻塞策略**
   - 类:`ExecutorBlockStrategyEnum`
   - 方法:`match(triggerParam.getExecutorBlockStrategy(), null)`
   - 作用:匹配阻塞策略(SERIAL_EXECUTION、DISCARD_LATER、COVER_EARLY)

5. **检查任务状态**(阻塞策略处理)
   - 类:`JobThread`
   - 方法:`isRunningOrHasQueue()`
   - 作用:检查任务是否正在运行或队列中有任务

6. **注册JobThread**(如果不存在)
   - 类:`XxlJobExecutor`
   - 方法:`registJobThread(triggerParam.getJobId(), jobHandler, removeOldReason)`
   - 作用:创建并启动新的任务线程
   - 内部调用:`new JobThread(jobId, handler)` → `jobThread.start()`

7. **推送任务到队列**
   - 类:`JobThread`
   - 方法:`pushTriggerQueue(triggerParam)`
   - 作用:将 `TriggerParam` 添加到 `triggerQueue` 队列中

##### 步骤4:响应序列化和返回

**类**:`EmbedServer.EmbedHttpServerHandler`
**方法**:`writeResponse(ChannelHandlerContext ctx, boolean keepAlive, String responseJson)`

**关键方法调用**:

1. **序列化响应**
   - 类:`GsonTool`
   - 方法:`toJson(responseObj)`
   - 作用:将响应对象序列化为JSON字符串

2. **写回响应**
   - 操作:通过Netty的 `ChannelHandlerContext` 写回HTTP响应
   - 作用:返回 `ReturnT<String>` 给调度中心

六、执行阶段 阶段4: 任务执行阶段

JobThread → IJobHandler.execute()

java 复制代码
**位置**:`JobThread.run()`

**执行流程**:

1. **JobThread持续运行**
   - 类:`JobThread`(继承 `Thread`)
   - 状态:线程持续运行,循环从队列中取任务

2. **从队列获取任务**
   - 操作:`triggerQueue.poll(3L, TimeUnit.SECONDS)`
   - 作用:阻塞等待,最多等待3秒

3. **执行任务处理器**
   - 方法:`handler.execute(executorParams)`
   - 作用:调用任务处理器的 `execute()` 方法执行任务
   - 返回:`ReturnT<String>`(执行结果)

4. **推送回调**
   - 方法:`TriggerCallbackThread.pushCallBack(handleCallbackParam)`
   - 作用:将执行结果推送到回调队列,异步回调调度中心

**JobThread执行关键代码**(伪代码):

```java
// JobThread.run()
@Override
public void run() {
    while (!toStop) {
        try {
            // 1. 从队列获取任务
            TriggerParam triggerParam = triggerQueue.poll(3L, TimeUnit.SECONDS);
            
            if (triggerParam != null) {
                // 2. 执行任务
                IJobHandler handler = this.handler;
                ReturnT<String> executeResult = handler.execute(triggerParam.getExecutorParams());
                
                // 3. 构建回调参数
                HandleCallbackParam handleCallbackParam = new HandleCallbackParam();
                handleCallbackParam.setLogId(triggerParam.getLogId());
                handleCallbackParam.setExecuteResult(executeResult);
                
                // 4. 推送回调
                TriggerCallbackThread.pushCallBack(handleCallbackParam);
            }
        } catch (Throwable e) {
            // 异常处理
            HandleCallbackParam handleCallbackParam = new HandleCallbackParam();
            handleCallbackParam.setLogId(triggerParam.getLogId());
            handleCallbackParam.setExecuteCode(ReturnT.FAIL_CODE);
            handleCallbackParam.setExecuteMsg(e.getMessage());
            TriggerCallbackThread.pushCallBack(handleCallbackParam);
        }
    }
}
```

**HandleCallbackParam 参数内容**:

```java
HandleCallbackParam {
    logId: 12345,                    // 日志ID(对应触发时的logId)
    executeCode: 200,                // 执行状态码(200-成功,500-失败)
    executeMsg: "执行成功",          // 执行消息
    logDateTime: 1699123456789       // 日志时间
}
```

##### 步骤1:JobThread初始化

**类**:`JobThread`
**方法**:`run()`

```98:105:xxl-job-core/src/main/java/com/xxl/job/core/thread/JobThread.java
	public void run() {

    	// init
    	try {
			handler.init();
		} catch (Throwable e) {
    		logger.error(e.getMessage(), e);
		}
```

**关键方法调用**:

1. **初始化任务处理器**
   - 类:`IJobHandler`(实际是 `MethodJobHandler`)
   - 方法:`init()`
   - 作用:调用任务处理器的初始化方法
   - **MethodJobHandler.init()**:
     - 类:`MethodJobHandler`
     - 方法:`init()`
     - 操作:如果 `initMethod != null`,调用 `initMethod.invoke(target)`
     - 作用:反射调用用户定义的初始化方法

##### 步骤2:从队列获取任务

**类**:`JobThread`
**方法**:`run()` → `triggerQueue.poll()`

```108:120:xxl-job-core/src/main/java/com/xxl/job/core/thread/JobThread.java
		// execute
		while(!toStop){
			running = false;
			idleTimes++;

            TriggerParam triggerParam = null;
            try {
				// to check toStop signal, we need cycle, so we cannot use queue.take(), instead of poll(timeout)
				triggerParam = triggerQueue.poll(3L, TimeUnit.SECONDS);
				if (triggerParam!=null) {
					running = true;
					idleTimes = 0;
					triggerLogIdSet.remove(triggerParam.getLogId());
```

**关键方法调用**:

1. **从队列获取任务**
   - 类:`LinkedBlockingQueue<TriggerParam>`(`triggerQueue`)
   - 方法:`poll(3L, TimeUnit.SECONDS)`
   - 作用:阻塞等待,最多等待3秒,获取任务参数
   - 返回值:`TriggerParam`(如果队列为空且超时,返回null)

2. **更新运行状态**
   - 操作:`running = true`
   - 操作:`idleTimes = 0`
   - 操作:`triggerLogIdSet.remove(triggerParam.getLogId())`
   - 作用:标记任务正在运行,清除日志ID记录

##### 步骤3:准备执行环境

**类**:`JobThread`
**方法**:`run()` → 创建任务上下文

```121:132:xxl-job-core/src/main/java/com/xxl/job/core/thread/JobThread.java
					// log filename, like "logPath/yyyy-MM-dd/9999.log"
					String logFileName = XxlJobFileAppender.makeLogFileName(new Date(triggerParam.getLogDateTime()), triggerParam.getLogId());
					XxlJobContext xxlJobContext = new XxlJobContext(
							triggerParam.getJobId(),
							triggerParam.getExecutorParams(),
							logFileName,
							triggerParam.getBroadcastIndex(),
							triggerParam.getBroadcastTotal());

					// init job context
					XxlJobContext.setXxlJobContext(xxlJobContext);
```

**关键方法调用**:

1. **生成日志文件名**
   - 类:`XxlJobFileAppender`
   - 方法:`makeLogFileName(new Date(triggerParam.getLogDateTime()), triggerParam.getLogId())`
   - 作用:生成日志文件路径(格式:`logPath/yyyy-MM-dd/{logId}.log`)

2. **创建任务上下文**
   - 类:`XxlJobContext`(构造函数)
   - 参数:`jobId`、`executorParams`、`logFileName`、`broadcastIndex`、`broadcastTotal`
   - 作用:创建任务执行上下文对象

3. **设置任务上下文**
   - 类:`XxlJobContext`
   - 方法:`setXxlJobContext(xxlJobContext)`
   - 作用:将上下文存储到线程局部变量(`ThreadLocal`)中,供任务执行时使用

##### 步骤4:记录开始日志

**类**:`JobThread`
**方法**:`run()` → `XxlJobHelper.log()`

```133:134:xxl-job-core/src/main/java/com/xxl/job/core/thread/JobThread.java
					// execute
					XxlJobHelper.log("<br>----------- xxl-job job execute start -----------<br>----------- Param:" + xxlJobContext.getJobParam());
```

**关键方法调用**:

1. **记录日志**
   - 类:`XxlJobHelper`
   - 方法:`log(String appendLogPattern, Object... appendLogArguments)`
   - 作用:记录任务开始执行的日志
   - 内部调用:
     - `XxlJobContext.getXxlJobContext()` - 获取任务上下文
     - `XxlJobFileAppender.appendLog()` - 追加日志到文件

##### 步骤5:执行任务处理器

**类**:`JobThread`
**方法**:`run()` → `handler.execute()`

**情况1:有超时时间**

```136:164:xxl-job-core/src/main/java/com/xxl/job/core/thread/JobThread.java
					if (triggerParam.getExecutorTimeout() > 0) {
						// limit timeout
						Thread futureThread = null;
						try {
							FutureTask<Boolean> futureTask = new FutureTask<Boolean>(new Callable<Boolean>() {
								@Override
								public Boolean call() throws Exception {

									// init job context
									XxlJobContext.setXxlJobContext(xxlJobContext);

									handler.execute();
									return true;
								}
							});
							futureThread = new Thread(futureTask);
							futureThread.start();

							Boolean tempResult = futureTask.get(triggerParam.getExecutorTimeout(), TimeUnit.SECONDS);
						} catch (TimeoutException e) {

							XxlJobHelper.log("<br>----------- xxl-job job execute timeout");
							XxlJobHelper.log(e);

							// handle result
							XxlJobHelper.handleTimeout("job execute timeout ");
						} finally {
							futureThread.interrupt();
						}
```

**情况2:无超时时间**

```165:168:xxl-job-core/src/main/java/com/xxl/job/core/thread/JobThread.java
					} else {
						// just execute
						handler.execute();
					}
```

**关键方法调用**:

1. **创建Future任务**(有超时时间的情况)
   - 类:`FutureTask<Boolean>`(构造函数)
   - 参数:`Callable<Boolean>`(包含任务执行逻辑)
   - 作用:创建可获取结果的异步任务

2. **启动任务线程**(有超时时间的情况)
   - 类:`Thread`(构造函数)
   - 参数:`futureTask`
   - 方法:`start()`
   - 作用:启动线程执行任务

3. **等待任务完成**(有超时时间的情况)
   - 类:`FutureTask<Boolean>`
   - 方法:`get(triggerParam.getExecutorTimeout(), TimeUnit.SECONDS)`
   - 作用:等待任务完成,最多等待 `executorTimeout` 秒
   - 如果超时:抛出 `TimeoutException`

4. **执行任务处理器**
   - 类:`IJobHandler`(实际是 `MethodJobHandler`)
   - 方法:`execute()`
   - 作用:调用任务处理器的执行方法
   - **MethodJobHandler.execute()**:
     - 类:`MethodJobHandler`
     - 方法:`execute()`
     - 操作:
       - 获取方法参数类型:`method.getParameterTypes()`
       - 反射调用:`method.invoke(target)` 或 `method.invoke(target, new Object[paramTypes.length])`
     - 作用:通过反射调用用户定义的业务方法

5. **处理超时**(有超时时间的情况)
   - 类:`XxlJobHelper`
   - 方法:`log()` - 记录超时日志
   - 方法:`handleTimeout()` - 设置超时结果
   - 方法:`futureThread.interrupt()` - 中断任务线程

##### 步骤6:处理执行结果

**类**:`JobThread`
**方法**:`run()` → 验证和处理执行结果

```170:184:xxl-job-core/src/main/java/com/xxl/job/core/thread/JobThread.java
					// valid execute handle data
					if (XxlJobContext.getXxlJobContext().getHandleCode() <= 0) {
						XxlJobHelper.handleFail("job handle result lost.");
					} else {
						String tempHandleMsg = XxlJobContext.getXxlJobContext().getHandleMsg();
						tempHandleMsg = (tempHandleMsg!=null&&tempHandleMsg.length()>50000)
								?tempHandleMsg.substring(0, 50000).concat("...")
								:tempHandleMsg;
						XxlJobContext.getXxlJobContext().setHandleMsg(tempHandleMsg);
					}
					XxlJobHelper.log("<br>----------- xxl-job job execute end(finish) -----------<br>----------- Result: handleCode="
							+ XxlJobContext.getXxlJobContext().getHandleCode()
							+ ", handleMsg = "
							+ XxlJobContext.getXxlJobContext().getHandleMsg()
					);
```

**关键方法调用**:

1. **获取执行结果**
   - 类:`XxlJobContext`
   - 方法:`getXxlJobContext()` - 获取任务上下文
   - 方法:`getHandleCode()` - 获取处理状态码
   - 方法:`getHandleMsg()` - 获取处理消息

2. **处理结果丢失**
   - 类:`XxlJobHelper`
   - 方法:`handleFail("job handle result lost.")`
   - 作用:如果任务没有调用 `handleSuccess()` 或 `handleFail()`,设置为失败
   - 内部调用:`handleResult(HANDLE_CODE_FAIL, handleMsg)`

3. **限制消息长度**
   - 操作:如果 `handleMsg` 长度超过50000字符,截断并添加 "..."
   - 作用:避免消息过长

4. **记录结束日志**
   - 类:`XxlJobHelper`
   - 方法:`log()` - 记录任务执行结束日志

##### 步骤7:异常处理

**类**:`JobThread`
**方法**:`run()` → catch块

```193:205:xxl-job-core/src/main/java/com/xxl/job/core/thread/JobThread.java
			} catch (Throwable e) {
				if (toStop) {
					XxlJobHelper.log("<br>----------- JobThread toStop, stopReason:" + stopReason);
				}

				// handle result
				StringWriter stringWriter = new StringWriter();
				e.printStackTrace(new PrintWriter(stringWriter));
				String errorMsg = stringWriter.toString();

				XxlJobHelper.handleFail(errorMsg);

				XxlJobHelper.log("<br>----------- JobThread Exception:" + errorMsg + "<br>----------- xxl-job job execute end(error) -----------");
			} finally {
```

**关键方法调用**:

1. **捕获异常堆栈**
   - 类:`StringWriter`、`PrintWriter`
   - 操作:`e.printStackTrace(new PrintWriter(stringWriter))`
   - 作用:将异常堆栈信息转换为字符串

2. **设置失败结果**
   - 类:`XxlJobHelper`
   - 方法:`handleFail(errorMsg)`
   - 作用:设置执行结果为失败
   - 内部调用:`handleResult(HANDLE_CODE_FAIL, errorMsg)`

3. **记录异常日志**
   - 类:`XxlJobHelper`
   - 方法:`log()` - 记录异常日志

##### 步骤8:推送回调

**类**:`JobThread`
**方法**:`run()` → finally块 → `TriggerCallbackThread.pushCallBack()`

```206:226:xxl-job-core/src/main/java/com/xxl/job/core/thread/JobThread.java
            finally {
                if(triggerParam != null) {
                    // callback handler info
                    if (!toStop) {
                        // common
                        TriggerCallbackThread.pushCallBack(new HandleCallbackParam(
                        		triggerParam.getLogId(),
								triggerParam.getLogDateTime(),
								XxlJobContext.getXxlJobContext().getHandleCode(),
								XxlJobContext.getXxlJobContext().getHandleMsg() )
						);
                    } else {
                        // is killed
                        TriggerCallbackThread.pushCallBack(new HandleCallbackParam(
                        		triggerParam.getLogId(),
								triggerParam.getLogDateTime(),
								XxlJobContext.HANDLE_CODE_FAIL,
								stopReason + " [job running, killed]" )
						);
                    }
                }
            }
```

**关键方法调用**:

1. **构建回调参数**
   - 类:`HandleCallbackParam`(构造函数)
   - 参数:
     - `logId` - 日志ID
     - `logDateTime` - 日志时间
     - `handleCode` - 处理状态码(从 `XxlJobContext` 获取)
     - `handleMsg` - 处理消息(从 `XxlJobContext` 获取)

2. **获取执行结果**
   - 类:`XxlJobContext`
   - 方法:`getXxlJobContext()` - 获取任务上下文
   - 方法:`getHandleCode()` - 获取处理状态码
   - 方法:`getHandleMsg()` - 获取处理消息

3. **推送回调**
   - 类:`TriggerCallbackThread`
   - 方法:`pushCallBack(HandleCallbackParam)`
   - 作用:将回调参数添加到回调队列
   - 内部调用:`callBackQueue.add(callback)`

##### 步骤9:清理任务上下文

**类**:`JobThread`
**方法**:`run()` → finally块(隐式)

**关键操作**:

- 任务执行完成后,`finally` 块执行,当前循环结束
- 下一轮循环开始前,如果有新的任务,会重新设置 `XxlJobContext`
- 如果线程结束,会调用 `handler.destroy()` 清理资源

七、执行阶段 阶段5: 结果回调阶段

TriggerCallbackThread → AdminBiz.callback()

java 复制代码
**位置**:`TriggerCallbackThread` → `AdminBiz.callback()`

**回调流程**:

1. **执行器推送回调到队列**
   - 类:`TriggerCallbackThread`
   - 方法:`pushCallBack(HandleCallbackParam)`
   - 操作:将回调参数添加到 `callBackQueue` 队列

2. **回调线程批量处理**
   - 类:`TriggerCallbackThread`
   - 线程:`triggerCallbackThread`
   - 操作:
     - 从队列中取出回调参数
     - 批量收集(使用 `drainTo()` 提高效率)
     - 调用调度中心的回调接口

**TriggerCallbackThread 回调处理**:

```58:84:xxl-job-core/src/main/java/com/xxl/job/core/thread/TriggerCallbackThread.java
        triggerCallbackThread = new Thread(new Runnable() {

            @Override
            public void run() {

                // normal callback
                while(!toStop){
                    try {
                        HandleCallbackParam callback = getInstance().callBackQueue.take();
                        if (callback != null) {

                            // callback list param
                            List<HandleCallbackParam> callbackParamList = new ArrayList<HandleCallbackParam>();
                            int drainToNum = getInstance().callBackQueue.drainTo(callbackParamList);
                            callbackParamList.add(callback);

                            // callback, will retry if error
                            if (callbackParamList!=null && callbackParamList.size()>0) {
                                doCallback(callbackParamList);
                            }
                        }
                    } catch (Throwable e) {
                        if (!toStop) {
                            logger.error(e.getMessage(), e);
                        }
                    }
                }
```

3. **HTTP回调请求发送**
   - 操作:通过 `AdminBizClient` 发送HTTP POST请求
   - URL:`http://调度中心地址/api/callback`
   - 数据:`List<HandleCallbackParam>`(批量回调)

**HTTP回调请求**:

```
POST http://192.168.1.1:9091/xxl-job-admin/api/callback HTTP/1.1
Host: 192.168.1.1:9091
Content-Type: application/json;charset=UTF-8
XXL-JOB-ACCESS-TOKEN: your-access-token
Content-Length: 512

[
  {
    "logId": 12345,
    "executeCode": 200,
    "executeMsg": "执行成功",
    "logDateTime": 1699123456789
  }
]
```

4. **调度中心接收回调**
   - 类:`JobApiController`
   - 路径:`/api/callback`
   - 方法:`callback(HttpServletRequest request, @RequestBody String data)`

5. **处理回调**
   - 类:`JobCompleteHelper`
   - 方法:`callback(HandleCallbackParam handleCallbackParam)`
   - 操作:
     - 验证日志记录(防止重复回调)
     - 更新任务日志(执行时间、状态码、消息)
     - 触发子任务(如果执行成功)
     - 更新日志到数据库

**JobCompleteHelper.callback() 处理**:

```154:180:xxl-job-admin/src/main/java/com/xxl/job/admin/scheduler/thread/JobCompleteHelper.java
	private ReturnT<String> callback(HandleCallbackParam handleCallbackParam) {
		// valid log item
		XxlJobLog log = XxlJobAdminConfig.getAdminConfig().getXxlJobLogMapper().load(handleCallbackParam.getLogId());
		if (log == null) {
			return ReturnT.ofFail( "log item not found.");
		}
		if (log.getHandleCode() > 0) {
			return ReturnT.ofFail("log repeate callback.");     // avoid repeat callback, trigger child job etc
		}

		// handle msg
		StringBuffer handleMsg = new StringBuffer();
		if (log.getHandleMsg()!=null) {
			handleMsg.append(log.getHandleMsg()).append("<br>");
		}
		if (handleCallbackParam.getHandleMsg() != null) {
			handleMsg.append(handleCallbackParam.getHandleMsg());
		}

		// success, save log
		log.setHandleTime(new Date());
		log.setHandleCode(handleCallbackParam.getHandleCode());
		log.setHandleMsg(handleMsg.toString());
		XxlJobCompleter.updateHandleInfoAndFinish(log);

		return ReturnT.ofSuccess();
	}
```

**回调处理关键点**:

1. **防重复回调**
   - 检查:`log.getHandleCode() > 0`
   - 作用:如果日志已经处理过,拒绝重复回调

2. **更新日志**
   - 字段:`handleTime`(处理时间)、`handleCode`(状态码)、`handleMsg`(消息)
   - 操作:更新到 `xxl_job_log` 表

3. **触发子任务**
   - 类:`XxlJobCompleter.finishJob()`
   - 条件:任务执行成功(`handleCode == 200`)且有子任务配置
   - 操作:调用 `JobTriggerPoolHelper.trigger()` 触发子任务


##### 步骤1:推送回调到队列

**类**:`TriggerCallbackThread`
**方法**:`pushCallBack(HandleCallbackParam callback)`

```38:41:xxl-job-core/src/main/java/com/xxl/job/core/thread/TriggerCallbackThread.java
    public static void pushCallBack(HandleCallbackParam callback){
        getInstance().callBackQueue.add(callback);
        logger.debug(">>>>>>>>>>> xxl-job, push callback request, logId:{}", callback.getLogId());
    }
```

**关键方法调用**:

1. **添加到回调队列**
   - 类:`LinkedBlockingQueue<HandleCallbackParam>`(`callBackQueue`)
   - 方法:`add(callback)`
   - 作用:将回调参数添加到队列中
   - 调用位置:`JobThread.run()` 的 `finally` 块中

##### 步骤2:回调线程处理

**类**:`TriggerCallbackThread`
**线程**:`triggerCallbackThread`
**方法**:`run()`

```58:84:xxl-job-core/src/main/java/com/xxl/job/core/thread/TriggerCallbackThread.java
        triggerCallbackThread = new Thread(new Runnable() {

            @Override
            public void run() {

                // normal callback
                while(!toStop){
                    try {
                        HandleCallbackParam callback = getInstance().callBackQueue.take();
                        if (callback != null) {

                            // callback list param
                            List<HandleCallbackParam> callbackParamList = new ArrayList<HandleCallbackParam>();
                            int drainToNum = getInstance().callBackQueue.drainTo(callbackParamList);
                            callbackParamList.add(callback);

                            // callback, will retry if error
                            if (callbackParamList!=null && callbackParamList.size()>0) {
                                doCallback(callbackParamList);
                            }
                        }
                    } catch (Throwable e) {
                        if (!toStop) {
                            logger.error(e.getMessage(), e);
                        }
                    }
                }
```

**关键方法调用**:

1. **从队列获取回调参数**
   - 类:`LinkedBlockingQueue<HandleCallbackParam>`(`callBackQueue`)
   - 方法:`take()`
   - 作用:阻塞等待,直到队列中有数据

2. **批量获取回调参数**
   - 类:`LinkedBlockingQueue<HandleCallbackParam>`(`callBackQueue`)
   - 方法:`drainTo(callbackParamList)`
   - 作用:批量取出队列中的所有回调参数,提高效率

3. **执行回调**
   - 类:`TriggerCallbackThread`
   - 方法:`doCallback(callbackParamList)`
   - 作用:向调度中心发送回调请求

##### 步骤3:执行回调 - doCallback()

**类**:`TriggerCallbackThread`
**方法**:`doCallback(List<HandleCallbackParam> callbackParamList)`

```163:183:xxl-job-core/src/main/java/com/xxl/job/core/thread/TriggerCallbackThread.java
    private void doCallback(List<HandleCallbackParam> callbackParamList){
        boolean callbackRet = false;
        // callback, will retry if error
        for (AdminBiz adminBiz: XxlJobExecutor.getAdminBizList()) {
            try {
                ReturnT<String> callbackResult = adminBiz.callback(callbackParamList);
                if (callbackResult!=null && callbackResult.isSuccess()) {
                    callbackLog(callbackParamList, "<br>----------- xxl-job job callback finish.");
                    callbackRet = true;
                    break;
                } else {
                    callbackLog(callbackParamList, "<br>----------- xxl-job job callback fail, callbackResult:" + callbackResult);
                }
            } catch (Throwable e) {
                callbackLog(callbackParamList, "<br>----------- xxl-job job callback error, errorMsg:" + e.getMessage());
            }
        }
        if (!callbackRet) {
            appendFailCallbackFile(callbackParamList);
        }
    }
```

**关键方法调用**:

1. **获取调度中心客户端列表**
   - 类:`XxlJobExecutor`
   - 方法:`getAdminBizList()`
   - 作用:获取所有调度中心客户端(支持多个调度中心)
   - 返回值:`List<AdminBiz>`(`AdminBizClient` 列表)

2. **回调调度中心**(遍历多个调度中心)
   - 类:`AdminBiz`(实际是 `AdminBizClient`)
   - 方法:`callback(callbackParamList)`
   - 作用:向调度中心发送回调请求
   - 成功条件:返回 `ReturnT.isSuccess() == true`
   - 如果成功:跳出循环
   - 如果失败:尝试下一个调度中心

3. **记录回调日志**
   - 类:`TriggerCallbackThread`
   - 方法:`callbackLog(callbackParamList, logContent)`
   - 作用:记录回调结果日志

4. **写入失败回调文件**(所有调度中心都失败时)
   - 类:`TriggerCallbackThread`
   - 方法:`appendFailCallbackFile(callbackParamList)`
   - 作用:将失败的回调参数序列化后写入文件,等待重试

##### 步骤4:HTTP回调请求发送

**类**:`AdminBizClient`
**方法**:`callback(List<HandleCallbackParam> callbackParamList)`

**关键方法调用**:

1. **发送HTTP POST请求**
   - 类:`XxlJobRemotingUtil`
   - 方法:`postBody(url, accessToken, timeout, callbackParamList, String.class)`
   - 参数:
     - `url` - 调度中心回调地址(`http://调度中心地址/api/callback`)
     - `accessToken` - 访问令牌
     - `timeout` - 超时时间
     - `callbackParamList` - 回调参数列表(`List<HandleCallbackParam>`)
   - 作用:发送HTTP POST请求到调度中心

2. **序列化回调参数**
   - 类:`GsonTool`
   - 方法:`toJson(callbackParamList)`
   - 作用:将 `List<HandleCallbackParam>` 序列化为JSON字符串
   - HTTP请求体:
     ```json
     [
       {
         "logId": 12345,
         "executeCode": 200,
         "executeMsg": "执行成功",
         "logDateTime": 1699123456789
       }
     ]
     ```

3. **HTTP请求发送**(`XxlJobRemotingUtil.postBody()` 内部)
   - 操作:使用 `HttpURLConnection` 发送HTTP POST请求
   - 请求头:
     - `Content-Type: application/json;charset=UTF-8`
     - `XXL-JOB-ACCESS-TOKEN: {accessToken}`
   - 请求体:JSON格式的回调参数列表

4. **解析响应**
   - 操作:读取HTTP响应,反序列化为 `ReturnT<String>`
   - 类:`GsonTool`
   - 方法:`fromJson(responseJson, ReturnT.class, String.class)`

##### 步骤5:失败回调文件处理

**类**:`TriggerCallbackThread`
**方法**:`appendFailCallbackFile()`、`retryFailCallbackFile()`

**关键方法调用**:

1. **序列化回调参数**
   - 类:`JdkSerializeTool`
   - 方法:`serialize(callbackParamList)`
   - 作用:将回调参数列表序列化为字节数组(用于文件存储)

2. **写入文件**
   - 类:`FileUtil`
   - 方法:`writeFileContent(callbackLogFile, callbackParamList_bytes)`
   - 作用:将序列化后的数据写入文件
   - 文件路径:`logPath/callbacklog/xxl-job-callback-{timestamp}.log`

3. **重试失败回调**(`triggerRetryCallbackThread` 线程)
   - 类:`TriggerCallbackThread`
   - 方法:`retryFailCallbackFile()`
   - 执行频率:每30秒执行一次
   - 操作:
     - 读取失败回调文件
     - 反序列化回调参数
     - 调用 `doCallback()` 重新回调
     - 删除已处理的文件

八、停止阶段:

java 复制代码
xxl-job-admin/src/main/java/com/xxl/job/admin/scheduler/scheduler/XxlJobScheduler.java
    public void destroy() throws Exception {

        // stop-schedule
        JobScheduleHelper.getInstance().toStop();

        // admin log report stop
        JobLogReportHelper.getInstance().toStop();

        // admin lose-monitor stop
        JobCompleteHelper.getInstance().toStop();

        // admin fail-monitor stop
        JobFailMonitorHelper.getInstance().toStop();

        // admin registry stop
        JobRegistryHelper.getInstance().toStop();

        // admin trigger pool stop
        JobTriggerPoolHelper.toStop();

    }
相关推荐
A懿轩A2 分钟前
【Maven 构建工具】从零到上手 Maven:安装配置 + IDEA 集成 + 第一个项目(保姆级教程)
java·maven·intellij-idea
diediedei9 分钟前
模板编译期类型检查
开发语言·c++·算法
野犬寒鸦12 分钟前
从零起步学习并发编程 || 第一章:初步认识进程与线程
java·服务器·后端·学习
我爱娃哈哈16 分钟前
SpringBoot + Flowable + 自定义节点:可视化工作流引擎,支持请假、报销、审批全场景
java·spring boot·后端
穿过锁扣的风18 分钟前
零基础入门 Python 爬虫:从基础到实战,爬取虎扑 / 豆瓣 / 图片全掌握
开发语言·爬虫·python
XiaoFan01235 分钟前
将有向工作流图转为结构树的实现
java·数据结构·决策树
一切尽在,你来42 分钟前
C++多线程教程-1.2.1 C++11/14/17 并发特性迭代
开发语言·c++
80530单词突击赢1 小时前
C++入门指南:从零到精通
开发语言·c++
小突突突1 小时前
浅谈Java中的反射
java·开发语言
csbysj20201 小时前
JSP 发送邮件教程
开发语言