xxl-job中Bean任务模式详解

1,148 阅读4分钟

xxl-job作为分布式任务调度平台,一次调度的最终目的,是执行自定义的任务体。任务体有两种实现方式:

  • Bean模式:基于Java类或方法,定义在执行器端;
  • GLUE模式:任务以源码方式维护在调度中心,可以是python、shell脚本,也可以是一段Java代码。

GLUE含义即胶水,该模式使xxl-job能够支持多种脚本语言,且在调度平台就能维护,实时编译和生效。

而Bean模式在Java开发中使用广泛。今天我们一起来看看其实现。

本文基于xxl-job源码2.4.0版本

一 Bean模式任务

任务,其实就是一段自定义的业务逻辑,当时间点到来时,被触发执行。

在应用中如何标识类或方法,是xxl-job应当关注的任务体呢?这就是IJobHandler抽象类。 它有三个方法,init、destroy默认空实现,execute方法由子类复写来实现任务逻辑。

public abstract class IJobHandler {

 public abstract void execute() throws Exception;

 public void init() throws Exception {
  // do something
 }

 public void destroy() throws Exception {
  // do something
 }
}

1.1 基于类

每个任务对应一个IJobHandler的子类。如下是一个简单的本地缓存刷新任务。

import com.xxl.job.core.handler.IJobHandler;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class CacheRefreshJob extends IJobHandler {

  // local cache
  public static volatile Map<Integer, Object> CACHE = new HashMap<>();

  @Override
  public void init() throws Exception {
    // 初始化缓存
    execute();
  }

  @Override
  public void execute() throws Exception {
    List<Object> dataList = loadData();
    Map<Integer, Object> temporaryMap = new HashMap<>();
    for (Object data : dataList) {
      temporaryMap.put(data.hashCode(), data);
    }
    CACHE = temporaryMap;
  }

  private List<Object> loadData() {
    // 模拟查询数据源
    return new ArrayList<>();
  }

  public Object query(Integer key) {
    return CACHE.get(key);
  }

  @Override
  public void destroy() throws Exception {
    CACHE.clear();
  }
}

当项目未使用spring框架时,可手动将任务注入到执行器容器。

XxlJobExecutor.registJobHandler("cacheRefreshJob", new CacheRefreshJob());

在2.1.0版本中,提供了@JobHandler注解,可在执行器启动时自动注册。要求是基于spring开发应用,并在类上使用@Service、@JobHandler。

@Service
@JobHandler(value = "cacheRefreshJob") // value即Job name
public class CacheRefreshJob extends IJobHandler {}

但是在2.4.0版本中,已无@JobHandler注解,不支持自动扫描、注册任务。

1.2 基于方法

每个任务对应一个方法,该方法需要有@XxlJob注解。如下对CacheRefreshJob的实现。

import com.xxl.job.core.handler.annotation.XxlJob;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class CacheRefreshJob {

  // local cache
  public static volatile Map<Integer, Object> CACHE = new HashMap<>();

  public void init() throws Exception {
    refreshCache();
  }

  @XxlJob(value = "cacheRefreshJob", init = "init", destroy = "destroy")
  public void refreshCache() throws Exception {
    List<Object> dataList = loadData();
    Map<Integer, Object> temporaryMap = new HashMap<>();
    for (Object data : dataList) {
      temporaryMap.put(data.hashCode(), data);
    }
    CACHE = temporaryMap;
  }

  private List<Object> loadData() {
    // 模拟查询数据源
    return new ArrayList<>();
  }

  public void destroy() throws Exception {
    CACHE.clear();
  }

  public Object query(Integer key) {
    return CACHE.get(key);
  }
}

其实,基于方法的任务,会被封装为MethodJobHandler对象,它也是IJobHandler的子类。 image.png 通过反射执行目标方法。 image.png

二 加载任务

加载任务,即在xxl-job执行器启动时识别任务体并创建bean,放入XxlJobExecutor的jobHandlerRepository中。 image.png 源码中提供了两种实现:FrameLess(即无框架)和使用spring。

2.1 无框架开发时

无框架开发时,得自己手动完成任务Bean的创建。 在源码中提供了XxlJobExecutor的子类XxlJobSimpleExecutor,该类有一个属性,即任务Bean列表,需要自己编码对它赋值。

private List<Object> xxlJobBeanList = new ArrayList<>();

XxlJobSimpleExecutor的start方法,会调用initJobHandlerMethodRepository来注册被@XxlJob标注的方法。 image.png@XxlJob中解析initMethod、destroyMethod方法,封装为MethodJobHandler对象。

// 若@XxlJob有init、destroy属性
initMethod = clazz.getDeclaredMethod(xxlJob.init());
destroyMethod = clazz.getDeclaredMethod(xxlJob.destroy());

registJobHandler(name, new MethodJobHandler(bean, executeMethod, initMethod, destroyMethod));

2.2 使用spring框架时

2.4.0版本

IJobHandler的子类,以及使用@XxlJob注解的类,使用@Service或@Component注解,交由spring容器管理。

源码提供了XxlJobSpringExecutor类,它是XxlJobExecutor的子类,同时实现了SmartInitializingSingleton接口,将在spring初始化所有单例Bean后,回调afterSingletonsInstantiated方法,其中做了两件事:

  • 扫描所有bean,获取被@XxlJob标注的方法,封装成MethodJobHandler对象,添加到jobHandlerRepository中;
  • 调用super.start()启动执行器。 image.png

其中用到了spring工具类MethodIntrospector,来查找bean中使用了@XxlJob注解的方法,然后一一注册。

	Map<Method, XxlJob> annotatedMethods = = MethodIntrospector.selectMethods(bean.getClass(),
    new MethodIntrospector.MetadataLookup<XxlJob>() {
            @Override
            public XxlJob inspect(Method method) {
                    return AnnotatedElementUtils.findMergedAnnotation(method, XxlJob.class);
            }
    });

image.png

2.1.2版本

注意,在2.4.0版本不支持基于类的任务,但是在2.1.0版本中支持,如图。 image.png 从spring容器中获取@JobHandler标注的bean,如果这个类是IJobHandler的子类,则需要注册到jobHandlerRepository中。 image.png

三 总结

xxl-job定义了GLUE概念,可以整合多种语言实现的任务体,不仅仅只是Java。 image.png

在使用Java Bean模式任务时,又分为基于类、基于方法两种方式:

  1. 基于类时,一个任务对应一个类,类的职责单一,避免耦合;
  2. 基于方式时,虽然可以减少类的数量,但是相比于基于类,它将通过反射调用,性能上有降低。
  3. 从2.2版本起,已没有@JobHandler注解,即不支持基于类的任务体。