这篇文章主要是解析调度中心xxl-job-admin启动流程。

准备

我们先来看一下调度中心项目的maven依赖。依赖了Spring Boot的一些starter依赖:webtestfreemarkermailactuatormybatis等,最后依赖了xxl-job-core包。

还记得我们在 修炼xxl-job之搭建本地调度平台 时启动调度中心过程中控制台输出的一句日志吗?

1
13:01:40.001 logback [xxl-job, admin JobScheduleHelper#scheduleThread] INFO  c.x.j.a.c.thread.JobScheduleHelper - >>>>>>>>> init xxl-job admin scheduler success.

JobScheduleHelper类打印了>>>>>>>>> init xxl-job admin scheduler success.。表示初始化任务调度器成功。

启动分析

我们找到com.xxl.job.admin.core.thread.JobScheduleHelper类中打印启动日志的代码,发现其在该类的start方法中,start方法初始化了一个子线程,在子线程中打印了日志。

那么该start方法是何时被执行的呢?我们发现该类并未交由Spring管理,借助于IDEA的快捷键Ctrl+Alt+H查看该方法的调用处,发现其在XxlJobScheduler.init()方法中被调用了,来看一下init方法的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public void init() throws Exception {
// init i18n
initI18n();

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

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

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

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

// start-schedule
JobScheduleHelper.getInstance().start();

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

JobScheduleHelper.getInstance().start();这一行代码使用“饿汉式”单例模式得到JobScheduleHelper类对象并随之调用了其start方法,然后打印了一行日志:>>>>>>>>> init xxl-job admin success.

思考:既然JobScheduleHelper类中的子线程中的日志都进行了打印,那么在其之后的日志打印在了何处呢?

我们复制代码中的日志信息,前往调度中心启动的控制台中按下Ctrl+F搜索相关信息,发现确实打印了,并且打印的位置非常靠前。

再来看一下init()方法的写法,几乎都是调用某个XxxxxHelper类的getInstance()方法然后再调用其start()方法。

猜想:这里所有的XxxxxHelper类的start()方法都是开启子线程执行相关任务,才导致init()方法中的日志打印位置非常靠前。

实际上这是全异步化设计思想的体现。

我们发现init()方法所在的类XxlJobScheduler也未交由Spring管理,继续借助IDEA的快捷键Ctrl+Alt+H查看init()方法的调用处,发现是在XxlJobAdminConfig.afterPropertiesSet()方法中被调用的。

XxlJobAdminConfig.afterPropertiesSet

XxlJobAdminConfig类使用@Component注解交由Spring进行管理,并实现了InitializingBeanDisposableBean这两个接口,重写的afterPropertiesSet()方法中只有简单的三行代码,第二行使用new关键字创建了XxlJobScheduler对象赋给私有成员变量xxlJobScheduler,随后第三行调用了其init()方法。

至此,我们找到了启动的起点,不妨先来分析一下这个“起点类”。

从类名来看,它是一个配置类。其第一个静态成员变量adminConfig是它本身,并在afterPropertiesSet()方法的第一行将this关键字赋给了该变量。

这是什么意思?“自身包含自身”吗?在该静态成员变量下还提供了一个静态方法getAdminConfig()用来获取该静态成员变量的引用。那我可以无限链式调用静态方法getAdminConfig()了:

1
XxlJobAdminConfig.getAdminConfig().getAdminConfig().getAdminConfig().......getAdminConfig();

只有第一次调用是通过类名.静态方法名,第一次调用返回了类的实例对象adminConfig,所以第二次以及之后的每次链式调用都是通过实例对象.静态方法名。这是不被建议和认可的,编译器也发出了黄色警告。所以每次使用时建议是只调用一次,调用一次就拿到了XxlJobAdminConfig类的对象,多次链式调用并无任何作用。

再来看一下XxlJobAdminConfig类的其它成员变量,发现是一些通过@Value注解获取的一些配置信息,还有一些是通过@Resource注解注入的XxxxxxDao类和其它业务类,同时提供了每个成员变量的访问器(getter方法)。

为什么这么做呢?为什么要将这些XxxxxxDao等类放到这里并提供访问器呢?

简单猜想:实际上XxlJobAdminConfig可以看做是一个简单的“容器”,调度中心在启动时将整个系统需要用到的配置信息和XxxxxxDao类对象初始化到该“容器”中,在需要使用某个XxxxxxDao类时,不使用Spring的依赖注入,而是从该“容器”中拿,还记得前面分析过的静态方法getAdminConfig(),它返回了XxlJobAdminConfig类的对象,我们可以这样来使用:

1
XxlJobAdminConfig.getAdminConfig().getXxxxxxDao()

这样做有什么好处呢?

猜想:这样做对系统的配置信息和依赖信息进行了统一管理,有一种全局配置感。

总结

这篇文章主要分析了调度中心项目xxl-job-admin的启动流程。发现主要执行了XxlJobScheduler.init()方法,该方法中有六个初始化动作,接下来我们会逐个进行分析。