前言
看该文章之前,最好看一下之前的文章,比较方便我们理解
XXL-Job详解(一):组件架构
XXL-Job详解(二):安装部署
XXL-Job详解(三):任务开发
任务注册
我们在执行器开发任务的时候,只需要一个@XxlJob注解就可以定义一个任务,那么它是怎么做到的呢
xxl-job之前的版本中是通过继承 IJobHandler 和在类上加注解的方式进行任务标识,在最新版中则抛弃了原有的做法,将任务的粒度细化到了方法级别。
前者的好处是任务编写的范式已经规定好,只需要重写对应抽象类中的方法并加上注解,但每一次编写新的任务执行程序都需要创建新的类来重新实现接口。后者的好处是在于细化了任务的粒度,将注解细化到了方法级别,不需要再重复地继承方法,很好地实现了类的复用。
在SpringBoot版本中,我们使用的执行器是XxlJobSpringExecutor,它是XxlJobExecutor的子类,并且实现了ApplicationContextAware 、SmartInitializingSingleton、DisposableBean 三个接口
public class XxlJobSpringExecutor extends XxlJobExecutor implements ApplicationContextAware, SmartInitializingSingleton, DisposableBean {
}
下面介绍一下XxlJobSpringExecutor实现的三个接口
ApplicationContextAware
ApplicationContextAware 是 Spring 框架中的一个接口,用于在 Spring 容器中获取 ApplicationContext 对象。通过实现这个接口,可以将 Spring 容器中的上下文信息注入到 JavaBean 中,从而让 JavaBean 访问 Spring 配置文件中的 Bean 和其他资源。
当一个类实现 ApplicationContextAware 接口时,Spring 容器会自动将当前应用程序的 ApplicationContext 对象注入到该类的 setApplicationContext 方法中。这样,该类就可以通过 ApplicationContext 对象访问 Spring 容器中的其他 Bean 和资源。
SmartInitializingSingleton
SmartInitializingSingleton 是 Spring 框架中的一个接口,它用于定义在单例对象初始化时执行特定操作的接口。主要用于在Spring容器启动完成时进行扩展操作,即afterSingletonsInstantiated()方法;
DisposableBean
DisposableBean,是在Spring容器关闭的时候预留的一个扩展点,实现DisposableBean接口,并重写destroy(),可以在Spring容器销毁bean的时候获得一次回调
注册方法
下面是XxlJobSpringExecutor实现SmartInitializingSingleton接口的afterSingletonsInstantiated方法,任务注册就在这个方法里
@Override
public void afterSingletonsInstantiated() {
// init JobHandler Repository
/*initJobHandlerRepository(applicationContext);*/
// init JobHandler Repository (for method)
initJobHandlerMethodRepository(applicationContext);
// refresh GlueFactory
GlueFactory.refreshInstance(1);
// super start
try {
super.start();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
可以看到在 afterSingletonsInstantiated 方法做了这样几件事情:
1、初始化任务执行程序仓库,即进行任务注册
2、刷新GlueFactory,获取SpringGlueFactory(用于动态脚本任务)
3、启动执行器,此处调用父类start()方法
可以看到任务注册的方法就在initJobHandlerMethodRepository方法
private void initJobHandlerMethodRepository(ApplicationContext applicationContext) {
if (applicationContext == null) {
return;
}
// 获取bean名称列表
String[] beanDefinitionNames = applicationContext.getBeanNamesForType(Object.class, false, true);
for (String beanDefinitionName : beanDefinitionNames) {
// 从上下文中根据bean元数据名称获取bean对象
Object bean = applicationContext.getBean(beanDefinitionName);
Map<Method, XxlJob> annotatedMethods = null; // referred to :org.springframework.context.event.EventListenerMethodProcessor.processBean
try {
// 根据bean类元信息获取被@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);
}
//如果没有xxljob方法,则跳过
if (annotatedMethods==null || annotatedMethods.isEmpty()) {
continue;
}
for (Map.Entry<Method, XxlJob> methodXxlJobEntry : annotatedMethods.entrySet()) {
//获取被注解的方法
Method executeMethod = methodXxlJobEntry.getKey();
//获取注解信息
XxlJob xxlJob = methodXxlJobEntry.getValue();
// 注册handler
registJobHandler(xxlJob, bean, executeMethod);
}
}
}
protected void registJobHandler(XxlJob xxlJob, Object bean, Method executeMethod){
if (xxlJob == null) {
return;
}
//获取任务名称
String name = xxlJob.value();
//make and simplify the variables since they'll be called several times later
Class<?> clazz = bean.getClass();
String methodName = executeMethod.getName();
//判断任务名称是否有效
if (name.trim().length() == 0) {
throw new RuntimeException("xxl-job method-jobhandler name invalid, for[" + clazz + "#" + methodName + "] .");
}
//检查当前任务名是否已经被使用,注意这里是通过任务名称来进行任务判重的
if (loadJobHandler(name) != null) {
throw new RuntimeException("xxl-job jobhandler[" + name + "] naming conflicts.");
}
executeMethod.setAccessible(true);
// 设置初始化方法和销毁方法
Method initMethod = null;
Method destroyMethod = null;
if (xxlJob.init().trim().length() > 0) {
try {
initMethod = clazz.getDeclaredMethod(xxlJob.init());
initMethod.setAccessible(true);
} catch (NoSuchMethodException e) {
throw new RuntimeException("xxl-job method-jobhandler initMethod invalid, for[" + clazz + "#" + methodName + "] .");
}
}
if (xxlJob.destroy().trim().length() > 0) {
try {
destroyMethod = clazz.getDeclaredMethod(xxlJob.destroy());
destroyMethod.setAccessible(true);
} catch (NoSuchMethodException e) {
throw new RuntimeException("xxl-job method-jobhandler destroyMethod invalid, for[" + clazz + "#" + methodName + "] .");
}
}
//进行任务处理程序注册
registJobHandler(name, new MethodJobHandler(bean, executeMethod, initMethod, destroyMethod));
}
首先initJobHandlerMethodRepository方法的applicationContext参数就是通过ApplicationContextAware 获取的,下面我们来看下initJobHandlerMethodRepository方法的具体逻辑
1、从ApplicationContext中获取所有Bean元数据名称,通过Bean元数据名称获取所有Bean;
2、遍历获取到的Bean,找到有XxlJob注解的类,获取类中被注解的所有方法;
3、获取被注解方法的相应信息,根据注解中的任务名称,调用loadJobHandler(name)方法检查该任务是否已经注册;
4、进行方法编写范式检查,主要检查方法名称、入参类型以及返回值类型是否符合要求;
5、设置被注解方法的初始化方法和销毁方法;
6、最后,将上述被注解方法注册到任务处理程序仓库中;