1.SpringBoot 的启动流程
SpringBoot 启动流程中主要完成的动作如下:
1. 执行SpringApplication构造方法,初始化属性。判断应用类型是REACTIVE还是SERVLET。读取spring.factories文件加载初始化器ApplicationContextInitializer和监听器ApplicationListener。
2. 实例调用run方法,记录启动时间,通过SpringFactoriesLoader加载Listeners,发布SpringBoot starting事件。
3. 准备环境变量,创建和配置environment,包含系统属性和用户配置的属性等,发布事件SpringApplicationRunListeners#environmentPrepared。
4. 打印SpringBoot的banner和版本。
5. 创建对应类型的应用程序的上下文ApplicationContext 。
6. 准备ApplicationContext以及实例化bean对象,然后发布事件SpringApplicationRunListeners# contextPrepared打印启动日志和Profile;增加懒加载处理器,然后初始化Bean,再发布contextLoaded事件。
7. 刷新上下文:refreshContext主要就是Spring IOC容器的创建过程,并且会进行自动装配,ApplicationContext发布refresh事件,说明ApplicationContext初始化完成。onRefresh() 内部创建应用容器tomcat,finishRefresh()中将会启动tomcat。
8. 计算启动耗时,Listeners发布started事件,表示启动成功
9. 回调所有的ApplicationRunner和CommandLineRunner
10. 发布Ready事件ApplicationReadyEvent,表示SpringBoot可以接收处理的请求。
SpringApplication .run() 方法主要代码
public ConfigurableApplicationContext run(String... args) {
long startTime = System.nanoTime();
DefaultBootstrapContext bootstrapContext = this.createBootstrapContext();
ConfigurableApplicationContext context = null;
this.configureHeadlessProperty();
SpringApplicationRunListeners listeners = this.getRunListeners(args);
listeners.starting(bootstrapContext, this.mainApplicationClass);
try {
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
ConfigurableEnvironment environment = this.prepareEnvironment(listeners, bootstrapContext, applicationArguments);
this.configureIgnoreBeanInfo(environment);
Banner printedBanner = this.printBanner(environment);
context = this.createApplicationContext();
context.setApplicationStartup(this.applicationStartup);
this.prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
this.refreshContext(context);
this.afterRefresh(context, applicationArguments);
Duration timeTakenToStartup = Duration.ofNanos(System.nanoTime() - startTime);
if (this.logStartupInfo) {
(new StartupInfoLogger(this.mainApplicationClass)).logStarted(this.getApplicationLog(), timeTakenToStartup);
}
listeners.started(context, timeTakenToStartup);
this.callRunners(context, applicationArguments);
} catch (Throwable var12) {
this.handleRunFailure(context, var12, listeners);
throw new IllegalStateException(var12);
}
try {
Duration timeTakenToReady = Duration.ofNanos(System.nanoTime() - startTime);
listeners.ready(context, timeTakenToReady);
return context;
} catch (Throwable var11) {
this.handleRunFailure(context, var11, (SpringApplicationRunListeners)null);
throw new IllegalStateException(var11);
}
}
1.1 SpringBoot的生命周期
SpringApplicationRunListener 接口规定了SpringBoot的生命周期,在各个生命周期广播相应的事件,调用实际的ApplicationListener类。如下为每个生命周期阶段以及对应的方法,其中started 和running方法在2.6.6版本的SpringBoot中已经被标记为Deprecated.
public interface SpringApplicationRunListener {
// 在run()方法开始执行时,该方法就立即被调用,可用于在初始化最早期时做一些工作
void starting();
// 当environment构建完成,ApplicationContext创建之前,该方法被调用
void environmentPrepared(ConfigurableEnvironment environment);
// 当ApplicationContext构建完成时,该方法被调用
void contextPrepared(ConfigurableApplicationContext context);
// 在ApplicationContext完成加载,但没有被刷新前,该方法被调用
void contextLoaded(ConfigurableApplicationContext context);
// 在ApplicationContext刷新并启动后,CommandLineRunners和ApplicationRunner未被调用前,该方法被调用
void started(ConfigurableApplicationContext context);
// 在run()方法执行完成前该方法被调用
void running(ConfigurableApplicationContext context);
// 当应用运行出错时该方法被调用
void failed(ConfigurableApplicationContext context, Throwable exception);
}
2. Nacos 加载和初始化
2.1 Nacos 的初始化
实现SpringApplicationRunListener ,并将自定义实现的Listener加入到Listener列表,实现生命周期扩展
2.1.1 Starting 阶段, 设置为starting为true,表示开始启动
2.1.2 environmentPrepared阶段
LoggingApplicationListener
· 加载logging.config
StartingApplicationListener
· 创建工作目录
· 注入环境变量,主要是系统属性和运行时参数
· 从application.properties加载配置,并注册监听文件
· 系统属性初始化,确定启动模式和本地IP
2.1.3 在contextPrepared阶段之前,applyInitializers初始化,在PropertyUtil初始化Nacos的设置
比较重要的是确定存储方式:内置derby还是外置数据库。
private void prepareContext(DefaultBootstrapContext bootstrapContext, ConfigurableApplicationContext context, ConfigurableEnvironment environment, SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) {
context.setEnvironment(environment);
this.postProcessApplicationContext(context);
this.applyInitializers(context);
listeners.contextPrepared(context);
bootstrapContext.close(context);
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
if (printedBanner != null) {
beanFactory.registerSingleton("springBootBanner", printedBanner);
}
if (beanFactory instanceof AbstractAutowireCapableBeanFactory) {
((AbstractAutowireCapableBeanFactory)beanFactory).setAllowCircularReferences(this.allowCircularReferences);
if (beanFactory instanceof DefaultListableBeanFactory) {
((DefaultListableBeanFactory)beanFactory).setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
}
}
if (this.lazyInitialization) {
context.addBeanFactoryPostProcessor(new LazyInitializationBeanFactoryPostProcessor());
}
Set<Object> sources = this.getAllSources();
Assert.notEmpty(sources, "Sources must not be empty");
this.load(context, sources.toArray(new Object[0]));
listeners.contextLoaded(context);
}
2.1.4 contextPrepared阶段,StartingApplicationListener 从cluster.conf加载节点信息, 开始打印Starting信息。然后加载LazyInitializationBeanFactory
2.1.5 refreshContext 刷新上下文,主要代码如下,其主要功能是加载bean
2.1.5.1 auth 模块
AuthConfigs 类 构造器初始化AuthConfigs对象,获取nacos.core.auth.plugin 相关配置
2.1.5.2 console 模块
ConsoleConfig 配置类初始化,设置加载controller类的包路径,顺序依次是core 、naming、config、console, 实际上还有auth插件的controller, 应该是懒加载了。
@PropertySource("/application.properties")
public class ConsoleConfig {
/**
* Init.
*/
@PostConstruct
public void init() {
methodsCache.initClassMethod("com.alibaba.nacos.core.controller");
methodsCache.initClassMethod("com.alibaba.nacos.naming.controllers");
methodsCache.initClassMethod("com.alibaba.nacos.config.server.controller");
methodsCache.initClassMethod("com.alibaba.nacos.console.controller");
}
2.1.5.3 core模块
· 初始化ServerMemberManager 构造初始化设置ContentPath为nacos,设置当前所在节点的信息,将自己加入集群并发布一个MemberChangeEvent事件广播节点发生了变化,然后注册监听本地IP变更事件,如果发生变更,则将旧的地址移除,加入新的地址。
initAndStartLookup是采用工厂模式创建成员节点地址的监测,分为三种模式: address-server即从远程读取和更新
,FileConfig 是读取和监听cluster.conf文件,standalone模式这是直接获取本地地址比较然后更新。
DistroMapper
ProtocolManager 注册监听 ServerMemberManager变化
2.1.5.4 naming 模块
RaftCore 构造,基本信息的初始化
ConsistencyService 一致性服务实现了的初始化。
ServiceManager 管理服务的核心类,有一个全局的执行器监听服务meta,以保证服务meta数据的一致性。
DistroProtocol初始化,如果是standalone模式启动,则跳过。如果是集群模式启动,则使用线程池校验个节点时间和数据。
UpgradeJudgement 判断是否正在进行升级流程,如果是则初始化升级检查订阅检查升级。
ServerStatusManager 构造对象并注册Server状态更新对象,当本地Server发生变化时,更新server状态
SwitchManager 初始化并注册监听SwitchDomain 变更
ServerListManager 节点列表管理器初始化,订阅节点信息,订阅成员信息上报和更新。
2.1.5.5 config模块
CapacityService 初始化容量管理服务,本意是要限制创建的命名空间数量和配置数量,实际上目前并没有启用限制。
ConnectionManager 连接管理器,构造函数中注册监听连接限制规则变更事件。初始化先从本地加载 data/loader/limitRule ,如果目录不存在则创建。并注册监听 data/loader目录,如果 data/loader/limitRule文件发生变化,则重新载入文件内容。
DumpService 构造函数,这里依赖persistService 来持久化到数据库,依赖ServerMemberManager来获取节点变更情况,将构造的对象作为参数,创建了4个Processor。这些DumpProcessor的主要作用是将更新配置、灰度、tag等数据持久化到磁盘。然后创建两个通用的TaskManager, 将前面创建的Processor作为参数传入,由Task管理Processor的执行。最后获取数据源,根据Condition条件,初始化Jdbc,将初始化状态设为true.
EmbeddedDumpService 构造函数创建 EmbeddedDumpService对象,初始化函数执行流程如下:
在getCpProtocol中初始化Jraft Executer,其中各个Executor的线程数默认依次为 raftCoreExecutor 4, raftCliServiceExecutor 4,raftCommonExecutor 8,raftSnapshotExecutor 4。
protocolManager.getCpProtocol(),默认是CP模式启动,这里会初始化协议,通过MemberManager将节点信息注入到协议中,构造JRaftServer和JRaftProtocol对象并初始化,其中包括创建raft executer,加载raft相关配置,创建raft节点和group,详细内容见Raft初始化章节,最后启动rpcServer
raftServer启动成功,但此时还需要等待创建group和选举成功后才能执行dump操作。
@PostConstruct
@Override
protected void init() throws Throwable {
//如果是standalone模式启动,直接执行dump操作然后返回
if (EnvUtil.getStandaloneMode()) {
dumpOperate(processor, dumpAllProcessor, dumpAllBetaProcessor, dumpAllTagProcessor);
return;
}
//默认是CP模式启动,这里会初始化协议,通过MemberManager将节点信息注入到协议中,构造JRaftServer和JRaftProtocol对象并初始化,其中包括创建raft executer,加载raft相关配置,创建raft节点和group,详细内容见Raft初始化章节,最后启动rpcServer
CPProtocol protocol = protocolManager.getCpProtocol();
AtomicReference<Throwable> errorReference = new AtomicReference<>(null);
CountDownLatch waitDumpFinish = new CountDownLatch(1);
// watch path => /nacos_config/leader/ has value ?
Observer observer = new Observer() {
@Override
public void update(Observable o) {
if (!(o instanceof ProtocolMetaData.ValueItem)) {
return;
}
final Object arg = ((ProtocolMetaData.ValueItem) o).getData();
GlobalExecutor.executeByCommon(() -> {
// must make sure that there is a value here to perform the correct operation that follows
if (Objects.isNull(arg)) {
return;
}
// Identify without a timeout mechanism
EmbeddedStorageContextUtils.putExtendInfo(Constants.EXTEND_NEED_READ_UNTIL_HAVE_DATA, "true");
// Remove your own listening to avoid task accumulation
boolean canEnd = false;
for (; ; ) {
try {
// 从leaderdump数据到本地
dumpOperate(processor, dumpAllProcessor, dumpAllBetaProcessor, dumpAllTagProcessor);
//
protocol.protocolMetaData()
.unSubscribe(Constants.CONFIG_MODEL_RAFT_GROUP, MetadataKey.LEADER_META_DATA, this);
canEnd = true;
} catch (Throwable ex) {
if (!shouldRetry(ex)) {
errorReference.set(ex);
canEnd = true;
}
}
if (canEnd) {
ThreadUtils.countDown(waitDumpFinish);
break;
}
ThreadUtils.sleep(500L);
}
EmbeddedStorageContextUtils.cleanAllContext();
});
}
};
//observer订阅nacos_config leader metadata
protocol.protocolMetaData()
.subscribe(Constants.CONFIG_MODEL_RAFT_GROUP, MetadataKey.LEADER_META_DATA, observer);
//这里必须要等待dump任务完成回调,才能继续进行初始化
ThreadUtils.latchAwait(waitDumpFinish);
// If an exception occurs during the execution of the dump task, the exception
// needs to be thrown, triggering the node to start the failed process
final Throwable ex = errorReference.get();
if (Objects.nonNull(ex)) {
throw ex;
}
}
PersistentClientOperationServiceImpl 初始化,调用 raftServer 的createMultiRaftGroup方法创建naming_persistent_service_v2 ,创建group的详细流程见raft初始化。
InstanceMetadataProcessor 初始话,,调用 raftServer 的createMultiRaftGroup方法创建naming_instance_metadata,创建group的详细流程见raft初始化。
ServiceMetadataProcessor 构造,调用 raftServer 的createMultiRaftGroup方法创建naming_service_metadata,创建group的详细流程见3.3节。
2.1.5.6 plugin-default-impl模块
JwtTokenManager 初始化,确定认证方式和认证相关配置信息。
2.1.6 afterRefresh
说明ApplicationContext初始化Bean已完成,启动Tomcat, 绑定端口启动成功
2.1.7 started 阶段
关闭启动过程中的定时任务。
设置启动状态为true,表示已启动。
打印启动模式和存储模式。
2.1.7 ready阶段
启动已完成。
本篇主要记录了Nacos 启动执行初始化的主要流程,下一篇介绍Nacos启动过程中非常重要的raft协议的初始化过程。