SpringBoot关闭时都做了哪些事?

本文详细剖析了SpringBoot服务关闭时的核心流程,包括从LiveBeansView移除上下文、广播ContextClosedEvent事件、触发生命周期处理器的onClose方法、销毁BeanFactory中的DisposableBean等关键步骤。
部署运行你感兴趣的模型镜像

本文我们开始分析SpringBoot关闭的时候都做了哪些事。核心流程梳理如下:

  • 从LiveBeansView移除掉维护的上下文
  • 广播ContextClosedEvent事件
  • 触发生命周期处理器的onClose方法,这里会stopBean,也就是触发那些Lifecycle实例的stop方法
  • 销毁BeanFactory中的所有DisposableBean并清空一些缓存
  • 关闭BeanFactory
  • 停止服务,这里会stop Tomcat
  • 将earlyApplicationListeners 数据拷贝给applicationListeners
  • 设置active状态为false

如下所示触发ConfigurableApplicationContext的close方法时就会触发服务的关闭。

public static void main(String[] args) {
    ConfigurableApplicationContext applicationContext = SpringApplication.run(RecommendApplication.class, args);
    applicationContext.close();
}

AbstractApplicationContext的close方法

如下所示,其会将具体动作委派给doClose方法,然后尝试移除掉JVM shutdown hook。

@Override
public void close() {
	synchronized (this.startupShutdownMonitor) {
		doClose();
		// If we registered a JVM shutdown hook, we don't need it anymore now:
		// We've already explicitly closed the context.
		if (this.shutdownHook != null) {
			try {
			// 移除钩子
				Runtime.getRuntime().removeShutdownHook(this.shutdownHook);
			}
			catch (IllegalStateException ex) {
				// ignore - VM is already shutting down
			}
		}
	}
}

【1】核心关闭方法

AbstractApplicationContext的doClose

protected void doClose() {
	// Check whether an actual close attempt is necessary...
	// 如果当前应用是激活状态且没有关闭,才执行if方法体
	if (this.active.get() && this.closed.compareAndSet(false, true)) {
		if (logger.isDebugEnabled()) {
			logger.debug("Closing " + this);
		}
	// 从LiveBeansView移除掉维护的上下文
		LiveBeansView.unregisterApplicationContext(this);

		try {
			// Publish shutdown event.
			// 广播ContextClosedEvent事件
			publishEvent(new ContextClosedEvent(this));
		}
		catch (Throwable ex) {
			logger.warn("Exception thrown from ApplicationListener handling ContextClosedEvent", ex);
		}

		// Stop all Lifecycle beans, to avoid delays during individual destruction.
		if (this.lifecycleProcessor != null) {
			try {
// 触发生命周期处理器的onClose方法,这里会stopBean,也就是触发那些Lifecycle实例的stop方法
				this.lifecycleProcessor.onClose();
			}
			catch (Throwable ex) {
				logger.warn("Exception thrown from LifecycleProcessor on context close", ex);
			}
		}

		// Destroy all cached singletons in the context's BeanFactory.
		// 销毁BeanFactory中的所有DisposableBean并清空一些缓存
		destroyBeans();

		// Close the state of this context itself.
		//关闭BeanFactory
		closeBeanFactory();

		// Let subclasses do some final clean-up if they wish...
		//停止服务,这里会stop Tomcat
		onClose();

		// Reset local application listeners to pre-refresh state.
		if (this.earlyApplicationListeners != null) {
			this.applicationListeners.clear();
			this.applicationListeners.addAll(this.earlyApplicationListeners);
		}

		// Switch to inactive.
		// 设置active状态为false
		this.active.set(false);
	}
}

① unregisterApplicationContext

关于LiveBeansView.unregisterApplicationContext(this),如下所示在SpringBoot启动流程中AbstractApplicationContextfinishRefresh方法中会将应用上下文注册到LiveBeansView中,在关闭过程中就会触发unregisterApplicationContext移除应用上下文。具体来讲就是

  • 从LiveBeansView的成员applicationContexts移除应用上下文
  • 触发MBeanServer的unregisterMBean方法
  • 将LiveBeansView的成员applicationName置为null

在这里插入图片描述

② publishEvent

这里会发布ContextClosedEvent事件,有如下监听器对其感兴趣:

0 = {RestartApplicationListener@10934} 
1 = {LoggingApplicationListener@10935} 
2 = {DelegatingApplicationListener@2936} 
3 = {DelegatingApplicationListener@10936} 

LoggingApplicationListener会触发this.loggingSystem.cleanUp();。本文这里并没有配置context.listener.classes属性,所以其他监听器对该事件并无作用。

③ lifecycleProcessor.onClose()

触发生命周期处理器的关闭方法,默认实例是DefaultLifecycleProcessor,其方法如下所示:

@Override
public void onClose() {
//触发Lifecycle类型的bean的stop方法
	stopBeans();
	//修改运行状态为false
	this.running = false;
}

stopBeans方法如下所示:

private void stopBeans() {
//获取生命周期bean,本文这里只有一个documentationPluginsBootstrapper
// key = "documentationPluginsBootstrapper" --DocumentationPluginsBootstrapper
	Map<String, Lifecycle> lifecycleBeans = getLifecycleBeans();
	Map<Integer, LifecycleGroup> phases = new HashMap<>();
	lifecycleBeans.forEach((beanName, bean) -> {
		int shutdownPhase = getPhase(bean);
		LifecycleGroup group = phases.get(shutdownPhase);
		if (group == null) {
			group = new LifecycleGroup(shutdownPhase, this.timeoutPerShutdownPhase, lifecycleBeans, false);
			phases.put(shutdownPhase, group);
		}
		group.add(beanName, bean);
	});
	if (!phases.isEmpty()) {
		List<Integer> keys = new ArrayList<>(phases.keySet());
		keys.sort(Collections.reverseOrder());
		for (Integer key : keys) {
		// 触发LifecycleGroup的stop,最终会触发生命周期bean的stop方法
			phases.get(key).stop();
		}
	}
}

如上所示首先获取所有的Lifecycle实例,然后安装shutdownPhase 进行分组,按组进行比遍历触发bean实例的stop方法。这里需要说明的时,如果bean实例有依赖bean(dependentBeans),那么会先触发这些依赖bean的销毁。

【2】 destroyBeans

AbstractApplicationContextdestroyBeans方法最终会触发DefaultListableBeanFactorydestroySingletons方法。

protected void destroyBeans() {
	getBeanFactory().destroySingletons();
}

在这里插入图片描述

DefaultListableBeanFactory的destroySingletons方法如下所示:

// DefaultListableBeanFactory
@Override
public void destroySingletons() {
	//触发父类DefaultSingletonBeanRegistry的销毁方法
	super.destroySingletons();

	//更新Set<String> manualSingletonNames
	updateManualSingletonNames(Set::clear, set -> !set.isEmpty());
	

	//这里会清空两个ConcurrentHashMap:
	//allBeanNamesByType和singletonBeanNamesByType
	clearByTypeCache();
}

① DefaultSingletonBeanRegistry的destroySingletons

父类DefaultSingletonBeanRegistry的destroySingletons方法如下所示,首先遍历每一个disposableBean进行销毁,然后清空一些缓存。

// DefaultSingletonBeanRegistry
public void destroySingletons() {
	if (logger.isTraceEnabled()) {
		logger.trace("Destroying singletons in " + this);
	}
	// 对一级缓存加锁,修改单例正在销毁标识为true
	synchronized (this.singletonObjects) {
		this.singletonsCurrentlyInDestruction = true;
	}

	String[] disposableBeanNames;
	synchronized (this.disposableBeans) {
		disposableBeanNames = StringUtils.toStringArray(this.disposableBeans.keySet());
	}
	// 销毁每一个实现了disposableBean接口的单例bean
	for (int i = disposableBeanNames.length - 1; i >= 0; i--) {
		destroySingleton(disposableBeanNames[i]);
	}
// 清空三个ConcurrentHashMap
// 本文这里为空
	this.containedBeanMap.clear();
	// 存储的是  bean---被哪些bean所需要
	this.dependentBeanMap.clear();
	//存储的是
	this.dependenciesForBeanMap.clear();

	//清空单例缓存
	clearSingletonCache();
}

如上方法所示,其获取disposableBeanNames 遍历循环触发每一个bean的销毁流程,然后清空三个ConcurrentHashMap,最后清空单例缓存。

销毁单个bean

我们先看一下disposableBean的销毁,这里首先触发的DefaultListableBeanFactorydestroySingleton方法。

// DefaultListableBeanFactory
@Override
public void destroySingleton(String beanName) {
// 触发父类DefaultSingletonBeanRegistry的销毁方法
	super.destroySingleton(beanName);
	
	// 从manualSingletonNames这个Set中移除beanName
	removeManualSingletonName(beanName);
	
	//清理缓存--又看到了,是不是多余?
	clearByTypeCache();
}

父类DefaultSingletonBeanRegistry的destroySingleton方法

也就是说DisposableBean 的销毁方法其实是被父类DefaultSingletonBeanRegistry来执行的。

public void destroySingleton(String beanName) {

// 从singletonObjects、singletonFactories、earlySingletonObjects及registeredSingletons移除
	removeSingleton(beanName);

	// Destroy the corresponding DisposableBean instance.
	DisposableBean disposableBean;
//从Map<String, Object> disposableBeans移除
	synchronized (this.disposableBeans) {
		disposableBean = (DisposableBean) this.disposableBeans.remove(beanName);
	}

	//销毁bean
	destroyBean(beanName, disposableBean);
}

关于destroyBean(beanName, disposableBean);我们本文不做进一步分析,详情参考SpringBoot关闭过程中是如何销毁一个DisposableBean的?


清空单例缓存

clearSingletonCache方法如下所示,清空了一级、三级、二级缓存以及Set<String> registeredSingletons

protected void clearSingletonCache() {
	synchronized (this.singletonObjects) {
	// 清空一级缓存
		this.singletonObjects.clear();
		//清空三级缓存
		this.singletonFactories.clear();
		//清空二级缓存
		this.earlySingletonObjects.clear();
		//清空单例bean  Set
		this.registeredSingletons.clear();
		this.singletonsCurrentlyInDestruction = false;
	}
}

② updateManualSingletonNames

这里应用了jdk1.8的新特性,主要目的就是如果manualSingletonNames不为空,那么就clear见清空。

updateManualSingletonNames(Set::clear, set -> !set.isEmpty());

//DefaultListableBeanFactory
private void updateManualSingletonNames(Consumer<Set<String>> action, Predicate<Set<String>> condition) {
		if (hasBeanCreationStarted()) {
			// Cannot modify startup-time collection elements anymore (for stable iteration)
			synchronized (this.beanDefinitionMap) {
				if (condition.test(this.manualSingletonNames)) {
					Set<String> updatedSingletons = new LinkedHashSet<>(this.manualSingletonNames);
					action.accept(updatedSingletons);
					this.manualSingletonNames = updatedSingletons;
				}
			}
		}
		else {
			// Still in startup registration phase
			if (condition.test(this.manualSingletonNames)) {
				action.accept(this.manualSingletonNames);
			}
		}
	}

③ clearByTypeCache

这里清理的是allBeanNamesByType和singletonBeanNamesByType。

private void clearByTypeCache() {
	this.allBeanNamesByType.clear();
	// 本文这里为空
	this.singletonBeanNamesByType.clear();
}

/** Map of singleton and non-singleton bean names, keyed by dependency type. */
	private final Map<Class<?>, String[]> allBeanNamesByType = new ConcurrentHashMap<>(64);

/** Map of singleton-only bean names, keyed by dependency type. */
	private final Map<Class<?>, String[]> singletonBeanNamesByType = new ConcurrentHashMap<>(64);

在这里插入图片描述

【3】其余方法

① closeBeanFactory

AbstractRefreshableApplicationContextcloseBeanFactory方法如下所示,设置当前应用上下文维护的beanFactory的SerializationId为null,然后beanFactory指向null。

@Override
protected final void closeBeanFactory() {
	synchronized (this.beanFactoryMonitor) {
		if (this.beanFactory != null) {
			this.beanFactory.setSerializationId(null);
			this.beanFactory = null;
		}
	}
}

② onClose

这里会触发ServletWebServerApplicationContext的onClose方法,这里会stop tomcat。

@Override
protected void onClose() {
// super是AbstractApplicationContext,其是一个空方法
	super.onClose();

//触发webServer的stop方法
	stopAndReleaseWebServer();
}

ServletWebServerApplicationContext的stopAndReleaseWebServer方法

private void stopAndReleaseWebServer() {
	WebServer webServer = this.webServer;
	if (webServer != null) {
		try {
		// 触发stop方法
			webServer.stop();
			// 引用指向null
			this.webServer = null;
		}
		catch (Exception ex) {
			throw new IllegalStateException(ex);
		}
	}
}

继续往下看,这里是TomcatWebServer的stop方法。

@Override
public void stop() throws WebServerException {
	synchronized (this.monitor) {
		boolean wasStarted = this.started;
		try {
		// 修改启动标志为false
			this.started = false;
			try {
			// 停止tomcat
				stopTomcat();
				
				// 销毁tomcat实例
				this.tomcat.destroy();
			}
			catch (LifecycleException ex) {
				// swallow and continue
			}
		}
		catch (Exception ex) {
			throw new WebServerException("Unable to stop embedded Tomcat", ex);
		}
		finally {
			if (wasStarted) {
				containerCounter.decrementAndGet();
			}
		}
	}
}

stopTomcat方法会触发tomcat实例的stop方法进而触发server的stop方法,tomcat实例的destroy方法也会触发server的destroy方法。

public void stop() throws LifecycleException {
    getServer();
    server.stop();
}

public void destroy() throws LifecycleException {
    getServer();
    server.destroy();
    // Could null out objects here
}

③ doClose的末尾方法

如下所示,这里会将earlyApplicationListeners 的数据复制到applicationListeners中,然后将active状态设置为false。

if (this.earlyApplicationListeners != null) {
	this.applicationListeners.clear();
	this.applicationListeners.addAll(this.earlyApplicationListeners);
}

// Switch to inactive.
this.active.set(false);

④ 移除钩子

当doClose方法执行完后,如下所示最终会移除JVM shutdown hook。
在这里插入图片描述

ShutdownHooks的remove方法如下所示,会移除当前应用上下文的销毁钩子,这里移除的是Thread[SpringContextShutdownHook,5,main]

static synchronized boolean remove(Thread hook) {
      if(hooks == null)
          throw new IllegalStateException("Shutdown in progress");

      if (hook == null)
          throw new NullPointerException();

      return hooks.remove(hook) != null;
  }

在这里插入图片描述

您可能感兴趣的与本文相关的镜像

Llama Factory

Llama Factory

模型微调
LLama-Factory

LLaMA Factory 是一个简单易用且高效的大型语言模型(Large Language Model)训练与微调平台。通过 LLaMA Factory,可以在无需编写任何代码的前提下,在本地完成上百种预训练模型的微调

<think>好的,我需要解决用户的问题:Spring Boot是否默认开启Actuator。首先,我得回忆一下Spring Boot Actuator的相关知识。根据引用内容,用户提供的三个引用都提到了Actuator的使用,但并没有直接说明默认是否启用。不过,通常Spring Boot的很多功能需要添加依赖后手动配置。比如引用[1]提到需要添加依赖,所以可能默认不开启。但有些端点可能默认开启,比如health。 接下来,我需要确认默认情况下,添加了Actuator依赖后,哪些端点是启用的。根据Spring Boot的文档,默认情况下,只有一些关键端点如health是启用的,其他如info、metrics可能需要通过配置开启。此外,引用[3]提到Actuator端点可以关闭,说明默认可能是部分开启,但需要用户配置。因此,结论应该是:Spring Boot在添加Actuator依赖后,默认开启部分端点,但大部分端点需要显式配置才能访问。用户需要检查application.properties或yml中的management.endpoints.web.exposure.include属性来确认。最后,确保回答引用正确的引用来源,比如引用[1]和[3]。</think>Spring Boot 默认情况下不会自动启用所有 Actuator 端点,但**在添加 Actuator 依赖后,部分基础端点(如 `/health`)是默认开启的**,而其他端点需要显式配置才能暴露[^1][^3]。 以下是具体说明: 1. **依赖添加**:必须先在 `pom.xml` 中添加 Actuator 依赖(如引用[1]所示),否则 Actuator 功能完全不可用。 2. **默认暴露的端点**:Spring Boot 2.x 及以上版本默认仅暴露 `/health` 端点。需通过配置文件(如 `application.properties`)设置 `management.endpoints.web.exposure.include=*` 来开启所有端点。 3. **安全性**:生产环境中,建议通过 `management.endpoints.web.exposure.include` 和 `exclude` 精确控制端点的开放,避免敏感信息泄露。 示例配置: ```properties # 开启所有端点 management.endpoints.web.exposure.include=* # 关闭特定端点(如env) management.endpoints.web.exposure.exclude=env ```
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

流烟默

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值