• Springboot——如何保证服务启动后不自动停止?


    前言

    观众大姥爷们是否发现一个很有意思的现象:

    一般项目执行后,当程序结束会自动关闭程序。
    但Springboot项目,启动后,只要不发生error错误,一般不会自动停止。

    这是为什么呢?

    简单Java阻止停止

    为了保证一个服务能够持续有效地对外提供服务,一般会有相应的处理方式,比如:

    服务器上的守护进程脚本。

    但是,在Java代码层面,除了shell脚本之外,还有一种很特别的方式,保证服务不会执行后停止。

    死循环!
    文雅点叫自旋锁

    比如下面的例子:

    public class ThreadDemo2 {
        public static volatile boolean isDead = false;
        public static void main(String[] args) throws InterruptedException {
            while (!isDead){
                Thread.sleep(4000);
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    程序执行后,会进入自旋锁的代码逻辑中,每隔4s,检查一下isDead值。

    如果isDead一直为false,那么程序将不会自动停止。
    在这里插入图片描述
    但是,设置在主线程中,此处自旋锁触发后,导致后续代码并不会继续执行,影响到后面的逻辑处理,显然实不可取的。

    如果,单独开辟一个新的线程,去处理这个活,主线程依旧去执行别的逻辑呢?

    public class TestThread {
        public static volatile boolean isDead = false;
        public static void main(String[] args) throws InterruptedException {
    //        Thread thread = new Thread(new Runnable() {
    //            @Override
    //            public void run() {
    //                System.out.println("---------");
    //                try {
    //                    while (!isDead){
    //                        Thread.sleep(10000L);
    //                    }
    //                } catch (InterruptedException e) {
    //                    e.printStackTrace();
    //                }
    //            }
    //        });
    
            Thread thread = new Thread("container-1" ) {
    
                @Override
                public void run() {
                    System.out.println("---------");
                    try {
                        while (!isDead){
                            Thread.sleep(4000L);
                        }
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
    
            };
            thread.setDaemon(false);
    
            thread.start();
    
            // 主线程暂停40s
            Thread.sleep(40000L);
            // 变更状态
            isDead = true;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42

    单独设定一个非守护进程的线程,去干这个活,主线程依旧可以继续执行其他的事。

    基于这个思想,接下来看看源码中Springboot底层是如何实现的。

    Springboot 底层实现

    以Springboot默认整合Tomcat为例。

    1、查看SpringApplication.run

    Springboot的项目执行,依据的是run方法,其中的实现方式如下:

    public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
    	return run(new Class<?>[] { primarySource }, args);
    }
    public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
    	return new SpringApplication(primarySources).run(args);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    继续查看run方法。

    public ConfigurableApplicationContext run(String... args) {
    		StopWatch stopWatch = new StopWatch();
    		stopWatch.start();
    		ConfigurableApplicationContext context = null;
    		Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
    		configureHeadlessProperty();
    		SpringApplicationRunListeners listeners = getRunListeners(args);
    		listeners.starting();
    		try {
    			ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
    			ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
    			configureIgnoreBeanInfo(environment);
    			Banner printedBanner = printBanner(environment);
    			context = createApplicationContext();
    			exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
    					new Class[] { ConfigurableApplicationContext.class }, context);
    			prepareContext(context, environment, listeners, applicationArguments, printedBanner);
    			// 暂时不看别的逻辑,就看 refreshContext
    			refreshContext(context);
    			afterRefresh(context, applicationArguments);
    			stopWatch.stop();
    			if (this.logStartupInfo) {
    				new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
    			}
    			listeners.started(context);
    			callRunners(context, applicationArguments);
    		}
    		catch (Throwable ex) {
    			handleRunFailure(context, ex, exceptionReporters, listeners);
    			throw new IllegalStateException(ex);
    		}
    
    		try {
    			listeners.running(context);
    		}
    		catch (Throwable ex) {
    			handleRunFailure(context, ex, exceptionReporters, null);
    			throw new IllegalStateException(ex);
    		}
    		return context;
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41

    其他逻辑暂时不看,就看其中的refreshContext(context);

    2、refreshContext(context)

    private void refreshContext(ConfigurableApplicationContext context) {
    	refresh(context);
    	if (this.registerShutdownHook) {
    		try {
    			context.registerShutdownHook();
    		}
    		catch (AccessControlException ex) {
    			// Not allowed in some environments.
    		}
    	}
    }
    
    protected void refresh(ApplicationContext applicationContext) {
    	Assert.isInstanceOf(AbstractApplicationContext.class, applicationContext);
    	((AbstractApplicationContext) applicationContext).refresh();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    3、org.springframework.context.support.AbstractApplicationContext#refresh

    在其中,定义了synchronized保证了启动加载时的线程安全性问题:

    @Override
    public void refresh() throws BeansException, IllegalStateException {
    	synchronized (this.startupShutdownMonitor) {
    		// Prepare this context for refreshing.
    		prepareRefresh();
    
    		// Tell the subclass to refresh the internal bean factory.
    		ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
    
    		// Prepare the bean factory for use in this context.
    		prepareBeanFactory(beanFactory);
    
    		try {
    			// Allows post-processing of the bean factory in context subclasses.
    			postProcessBeanFactory(beanFactory);
    
    			// Invoke factory processors registered as beans in the context.
    			invokeBeanFactoryPostProcessors(beanFactory);
    
    			// Register bean processors that intercept bean creation.
    			registerBeanPostProcessors(beanFactory);
    
    			// Initialize message source for this context.
    			initMessageSource();
    
    			// Initialize event multicaster for this context.
    			initApplicationEventMulticaster();
    
    			// Initialize other special beans in specific context subclasses.
    			onRefresh();
    
    			// Check for listener beans and register them.
    			registerListeners();
    
    			// Instantiate all remaining (non-lazy-init) singletons.
    			finishBeanFactoryInitialization(beanFactory);
    
    			// Last step: publish corresponding event.
    			finishRefresh();
    		}
    
    		catch (BeansException ex) {
    			if (logger.isWarnEnabled()) {
    				logger.warn("Exception encountered during context initialization - " +
    						"cancelling refresh attempt: " + ex);
    			}
    
    			// Destroy already created singletons to avoid dangling resources.
    			destroyBeans();
    
    			// Reset 'active' flag.
    			cancelRefresh(ex);
    
    			// Propagate exception to caller.
    			throw ex;
    		}
    
    		finally {
    			// Reset common introspection caches in Spring's core, since we
    			// might not ever need metadata for singleton beans anymore...
    			resetCommonCaches();
    		}
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64

    其中定义程序是否保活的逻辑,在onRefresh()中。

    4、org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext#onRefresh

    @Override
    protected void onRefresh() {
    	super.onRefresh();
    	try {
    		createWebServer();
    	}
    	catch (Throwable ex) {
    		throw new ApplicationContextException("Unable to start web server", ex);
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    createWebServer()主要是构建服务容器,这里以tomcat为例。点击进去查看其实现:

    private void createWebServer() {
    	WebServer webServer = this.webServer;
    	ServletContext servletContext = getServletContext();
    	if (webServer == null && servletContext == null) {
    		ServletWebServerFactory factory = getWebServerFactory();
    		this.webServer = factory.getWebServer(getSelfInitializer());
    	}
    	else if (servletContext != null) {
    		try {
    			getSelfInitializer().onStartup(servletContext);
    		}
    		catch (ServletException ex) {
    			throw new ApplicationContextException("Cannot initialize servlet context", ex);
    		}
    	}
    	initPropertySources();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    在服务容器创建时,就会定义其保活线程,查看getWebServer处理逻辑:

    @Override
    public WebServer getWebServer(ServletContextInitializer... initializers) {
    	if (this.disableMBeanRegistry) {
    		Registry.disableRegistry();
    	}
    	Tomcat tomcat = new Tomcat();
    	File baseDir = (this.baseDirectory != null) ? this.baseDirectory : createTempDir("tomcat");
    	tomcat.setBaseDir(baseDir.getAbsolutePath());
    	Connector connector = new Connector(this.protocol);
    	connector.setThrowOnFailure(true);
    	tomcat.getService().addConnector(connector);
    	customizeConnector(connector);
    	tomcat.setConnector(connector);
    	tomcat.getHost().setAutoDeploy(false);
    	configureEngine(tomcat.getEngine());
    	for (Connector additionalConnector : this.additionalTomcatConnectors) {
    		tomcat.getService().addConnector(additionalConnector);
    	}
    	prepareContext(tomcat.getHost(), initializers);
    	return getTomcatWebServer(tomcat);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    查看tomcatweb服务生成方式逻辑:

    public TomcatWebServer(Tomcat tomcat, boolean autoStart) {
    	Assert.notNull(tomcat, "Tomcat Server must not be null");
    	this.tomcat = tomcat;
    	this.autoStart = autoStart;
    	initialize();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    查看init逻辑:

    private void initialize() throws WebServerException {
    	logger.info("Tomcat initialized with port(s): " + getPortsDescription(false));
    	synchronized (this.monitor) {
    		try {
    			addInstanceIdToEngineName();
    
    			Context context = findContext();
    			context.addLifecycleListener((event) -> {
    				if (context.equals(event.getSource()) && Lifecycle.START_EVENT.equals(event.getType())) {
    					// Remove service connectors so that protocol binding doesn't
    					// happen when the service is started.
    					removeServiceConnectors();
    				}
    			});
    
    			// Start the server to trigger initialization listeners
    			this.tomcat.start();
    
    			// We can re-throw failure exception directly in the main thread
    			rethrowDeferredStartupExceptions();
    
    			try {
    				ContextBindings.bindClassLoader(context, context.getNamingToken(), getClass().getClassLoader());
    			}
    			catch (NamingException ex) {
    				// Naming is not enabled. Continue
    			}
    
    			// Unlike Jetty, all Tomcat threads are daemon threads. We create a
    			// blocking non-daemon to stop immediate shutdown
    			startDaemonAwaitThread();
    		}
    		catch (Exception ex) {
    			stopSilently();
    			destroySilently();
    			throw new WebServerException("Unable to start embedded Tomcat", ex);
    		}
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39

    可以看到,在tomcat的服务容器构建完成后,其中调用了一个startDaemonAwaitThread();开启了一个守护线程

    查看其中的逻辑:

    private void startDaemonAwaitThread() {
    	Thread awaitThread = new Thread("container-" + (containerCounter.get())) {
    
    		@Override
    		public void run() {
    			TomcatWebServer.this.tomcat.getServer().await();
    		}
    
    	};
    	awaitThread.setContextClassLoader(getClass().getClassLoader());
    	awaitThread.setDaemon(false);
    	awaitThread.start();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    开启了一个新的非守护类型的线程,去执行了await()

    查看await()的实现逻辑:
    在这里插入图片描述
    红框中的代码逻辑,是否似曾相识!!!!

    整体思维逻辑

    SpringApplication.run
     --> 
    refreshContext
     -- >
    org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext#refresh 
    -- >
    org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext#createWebServer 
    -- >
    org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory#getWebServer
     ->
    org.springframework.boot.web.embedded.tomcat.TomcatWebServer#TomcatWebServer(org.apache.catalina.startup.Tomcat, boolean)
     -->
     org.springframework.boot.web.embedded.tomcat.TomcatWebServer#initialize
      -->
      org.springframework.boot.web.embedded.tomcat.TomcatWebServer#startDaemonAwaitThread
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
  • 相关阅读:
    Unity Shader 透明度效果
    游戏设计模式杂谈(一)
    浅谈OAuth 2.0与JWT
    OSG第三方库编译之三十五:libzip编译(Windows、Linux、Macos环境下编译)
    C++ map和set(补充)
    Nacos配置中心集群原理及源码分析
    三. 操作系统 (6分) [理解|计算]
    Linux系统卡顿处理记录(Debian)
    1965. 丢失信息的雇员
    10/16作业
  • 原文地址:https://blog.csdn.net/qq_38322527/article/details/125885714