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的子类。
通过反射执行目标方法。
二 加载任务
加载任务,即在xxl-job执行器启动时识别任务体并创建bean,放入XxlJobExecutor的jobHandlerRepository中。
源码中提供了两种实现:FrameLess(即无框架)和使用spring。
2.1 无框架开发时
无框架开发时,得自己手动完成任务Bean的创建。
在源码中提供了XxlJobExecutor的子类XxlJobSimpleExecutor,该类有一个属性,即任务Bean列表,需要自己编码对它赋值。
private List<Object> xxlJobBeanList = new ArrayList<>();
XxlJobSimpleExecutor的start方法,会调用initJobHandlerMethodRepository来注册被@XxlJob标注的方法。
从
@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()启动执行器。
其中用到了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);
}
});
2.1.2版本
注意,在2.4.0版本不支持基于类的任务,但是在2.1.0版本中支持,如图。
从spring容器中获取@JobHandler标注的bean,如果这个类是IJobHandler的子类,则需要注册到jobHandlerRepository中。
三 总结
xxl-job定义了GLUE概念,可以整合多种语言实现的任务体,不仅仅只是Java。
在使用Java Bean模式任务时,又分为基于类、基于方法两种方式:
- 基于类时,一个任务对应一个类,类的职责单一,避免耦合;
- 基于方式时,虽然可以减少类的数量,但是相比于基于类,它将通过反射调用,性能上有降低。
- 从2.2版本起,已没有
@JobHandler注解,即不支持基于类的任务体。