SpringBoot源码学习——SpringApplication生命周期

从Spring应用进程来看,整体的生命周期大体如下

  • SpringApplication初始化阶段
  • SpringApplication运行阶段
  • SpringApplication结束阶段
  • SpringBoot应用退出
    附带一张SpringApplication的生命周期图
    在这里插入图片描述

1.SpringApplication初始化阶段

该阶段属于运行前的准备阶段,为Spring应用的运行进行一些服务组件的构造和初始化

该阶段分为

  • 构造阶段
  • 配置阶段

1.1 构造阶段

职责:

  • 根据当前环境选择合适的Web应用类的类名(Servlet/Reactive)
  • 从META-INF/spring.factories资源文件中获取所有的初始化器接口的实现类,创建并关联到应用上下文中
  • 根据当前线程执行栈获取main主类的类名,关联到上下文

下面是结合源码对职责进行分析

我们运行一个SpringBoot应用都是通过SpringApplication.run(Demo01Application.class, args)语句来引导创建上下文应用,

//main方法中的引导语句
SpringApplication.run(Demo01Application.class, args);

分析:上面的run放法将Demo01Application的类对象作为首参传入,该Demo01Application是被标注了@SpringBootApplication

我们追溯该方法会发现它会创建一个SpringApplication并将Demo01Application.class作为构造参数进行实例化,然后调用我们实例化好的SpringApplication对象的run方法开始进行Spring上下文的初始化工作,此时,我们的SpringBoot应用上下文已经完成的创建

public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
        return (new SpringApplication(primarySources)).run(args);
    }

那么,在创建时,我们构上下文造器具体完成了什么工作

public SpringApplication(Class<?>... primarySources) {
        this((ResourceLoader)null, primarySources);
    }

    public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
        this.sources = new LinkedHashSet();
        this.bannerMode = Mode.CONSOLE;
        this.logStartupInfo = true;
        this.addCommandLineProperties = true;
        this.addConversionService = true;
        this.headless = true;
        this.registerShutdownHook = true;
        this.additionalProfiles = new HashSet();
        this.isCustomEnvironment = false;
        this.lazyInitialization = false;
        this.resourceLoader = resourceLoader;
        Assert.notNull(primarySources, "PrimarySources must not be null");

		//前面不重要
		//----------------------华丽的分割线-----------------------------
        this.primarySources = new LinkedHashSet(Arrays.asList(primarySources));
        this.webApplicationType = WebApplicationType.deduceFromClasspath();
        this.setInitializers(this.getSpringFactoriesInstances(ApplicationContextInitializer.class));
        this.setListeners(this.getSpringFactoriesInstances(ApplicationListener.class));
        this.mainApplicationClass = this.deduceMainApplicationClass();
    }

我们从分割线开始分析构造工作

this.primarySources = new LinkedHashSet(Arrays.asList(primarySources));

我们以前的Spring项目都是通过配置文件的形式以及配置类的形式来实现配置,到了Spring4开始我们差不多完全放弃了配置文件的形式,当然,并不意味着你不能用,只是一般不用。这里的primarySources就是我们一开始传入的引导类。而引导类中的@SpringBootApplication注解本质上就是一个配置类,关于这部分可以看我的相关文章。所以这里的工作就是将配置类关联到SpringApplication中

this.webApplicationType = WebApplicationType.deduceFromClasspath();

该方法的职责就是我们前面提到的根据环境换获取合适的web容器,只要我们库里有Servlet容器就用Servlet

 static WebApplicationType deduceFromClasspath() {
        if (ClassUtils.isPresent("org.springframework.web.reactive.DispatcherHandler", (ClassLoader)null) && !ClassUtils.isPresent("org.springframework.web.servlet.DispatcherServlet", (ClassLoader)null) && !ClassUtils.isPresent("org.glassfish.jersey.servlet.ServletContainer", (ClassLoader)null)) {
            return REACTIVE;
        } else {
            String[] var0 = SERVLET_INDICATOR_CLASSES;
            int var1 = var0.length;

            for(int var2 = 0; var2 < var1; ++var2) {
                String className = var0[var2];
                if (!ClassUtils.isPresent(className, (ClassLoader)null)) {
                    return NONE;
                }
            }

            return SERVLET;
        }
    }

this.setInitializers(this.getSpringFactoriesInstances(ApplicationContextInitializer.class));

这个部分从名字就能看出来,是设置我们的初始化器,关于getSpringFactoriesInstances,这个方法将我们传入的接口从从META-INF/spring.factories资源文件中获取所有的初始化器接口的实现类,并进行创建,然后通过set方法关联到应用上下文中,该方法在SpringBoot中被大量使用,这里不做描述,我的相关文章中有对这类功能性方法的详述

this.setListeners(this.getSpringFactoriesInstances(ApplicationListener.class));

这部分同上,将我们传入的接口从从META-INF/spring.factories资源文件中获取所有的初始化器接口的实现类,并进行创建,然后通过set方法关联到应用上下文中,之部分这里创建的是我们的上下文监听器

this.mainApplicationClass = this.deduceMainApplicationClass();

该方法结合源码分析,其实整体逻辑也很清晰,根据当前线程执行栈获取main主类的类名,关联到上下文
至于这个类拿来有什么用,暂时还没发现

private Class<?> deduceMainApplicationClass() {
        try {
            StackTraceElement[] stackTrace = (new RuntimeException()).getStackTrace();
            StackTraceElement[] var2 = stackTrace;
            int var3 = stackTrace.length;

            for(int var4 = 0; var4 < var3; ++var4) {
                StackTraceElement stackTraceElement = var2[var4];
                if ("main".equals(stackTraceElement.getMethodName())) {
                    return Class.forName(stackTraceElement.getClassName());
                }
            }
        } catch (ClassNotFoundException var6) {
        }

        return null;
    }

总结: 关于构造阶段其主要过程都是在SpringApplication中的构造函数中,也就是说在构造函数中,完成了Web应用类的类名选择、从META-INF/spring.factories资源文件中获取所有的初始化器接口的实现类,创建并关联到应用上下文中以及根据当前线程执行栈获取main主类的类名,关联到上下文。这个过程其实就是为后面整个上下文的初始化工作加载一些必要的工具

关于构造器阶段的工作就到这里结束了,下面是配置阶段

1.2 配置阶段

回顾静态的run方法,我们可以发现在前面构造阶段的构造完成后,马上就会调用run方法。但事实上我们的run方法就对应我们的SpringApplication的运行阶段。
那我们的配置阶段在哪呢。其实这个阶段是可选的。我们如果需要对SpringApplication做一些额外的配置工作,我们就不能通过静态run方法执行,因为它会跳过配置阶段

public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
        return (new SpringApplication(primarySources)).run(args);
    }

该阶段用于调整或补充构造阶段的状态、左右运行时行为。
我们可以手动new SpringApplication,然后进行配置工作,当完成配置后,我们再执行对应运行阶段的run方法

配置阶段能做什么

这个阶段能做许多的配置调整工作,类似于设置banner、添加SpringProfile,添加初始化器等等工作。那么具体怎么做。
SpringApplication的setter方法对应调整行为,add方法对应补充行为
但这种方式过于繁琐,所以SpringBoot引入了SpringApplicationBuilder API来简化前者的繁琐操作(建造者模式的实现)相关还有很多方法,有需要可以在官方文档上找或者直接看源码

SpringApplicationBuilder不是SpringApplication,而是组合了SpringApplication,所以如果需要配置阶段,我们可以使用SpringApplicationBuilder

setter方式

SpringApplication springApplication = new SpringApplication(Demo01Application.class);
        springApplication.setBannerMode(Banner.Mode.CONSOLE);
        springApplication.setWebApplicationType(WebApplicationType.SERVLET);
        springApplication.run(args);

SpringApplicationBuilder方式

new SpringApplicationBuilder(Demo01Application.class)
                .bannerMode(Banner.Mode.CONSOLE)
                .web(WebApplicationType.SERVLET)
                .run(args);

两者都返回ConfigurableApplicationContext对象



2.SpringApplication运行阶段

该阶段属于核心过程,围绕run(String…)方法展开

该阶段结合初始化阶段完成的状态,进一步完善了运行时所需要准备的资源,随后启动Spring应用上下文。此期间伴随Spring和SpringBoot事件的触发,形成完整的SpringApplication声明周期

该阶段分为以下几个子阶段

  • SpringApplication准备阶段
  • SpringApplication启动阶段
  • SpringApplication启动后阶段

下面就是整个运行阶段对应的源码,我会在源码中对其进行阶段的分割。但是只是分割一下,具体说明在后面小章节中进行解释

public ConfigurableApplicationContext run(String... args) {
		//---------------------------准备阶段------------------------------------
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        ConfigurableApplicationContext context = null;
        Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList();
        this.configureHeadlessProperty();
        SpringApplicationRunListeners listeners = this.getRunListeners(args);
        listeners.starting();

        Collection exceptionReporters;
        try {
            ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
            ConfigurableEnvironment environment = this.prepareEnvironment(listeners, applicationArguments);
            this.configureIgnoreBeanInfo(environment);
            Banner printedBanner = this.printBanner(environment);
            context = this.createApplicationContext();
            exceptionReporters = this.getSpringFactoriesInstances(SpringBootExceptionReporter.class, new Class[]{ConfigurableApplicationContext.class}, context);
            this.prepareContext(context, environment, listeners, applicationArguments, printedBanner);
            //---------------------------启动阶段------------------------------------
            this.refreshContext(context);
            //---------------------------启动后阶段------------------------------------
            this.afterRefresh(context, applicationArguments);
            stopWatch.stop();
            if (this.logStartupInfo) {
                (new StartupInfoLogger(this.mainApplicationClass)).logStarted(this.getApplicationLog(), stopWatch);
            }

            listeners.started(context);
            this.callRunners(context, applicationArguments);
        } catch (Throwable var10) {
            this.handleRunFailure(context, var10, exceptionReporters, listeners);
            throw new IllegalStateException(var10);
        }

        try {
            listeners.running(context);
            return context;
        } catch (Throwable var9) {
            this.handleRunFailure(context, var9, exceptionReporters, (SpringApplicationRunListeners)null);
            throw new IllegalStateException(var9);
        }
    }

2.1 SpringApplication准备阶段

前面省去一些非核心的代码

public ConfigurableApplicationContext run(String... args) {
		//---------------------------准备阶段------------------------------------
        //前面不重要
        ...
        SpringApplicationRunListeners listeners = this.getRunListeners(args);
        listeners.starting();

        Collection exceptionReporters;
        try {
            ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
            ConfigurableEnvironment environment = this.prepareEnvironment(listeners, applicationArguments);
            this.configureIgnoreBeanInfo(environment);
            Banner printedBanner = this.printBanner(environment);
            context = this.createApplicationContext();
            exceptionReporters = this.getSpringFactoriesInstances(SpringBootExceptionReporter.class, new Class[]{ConfigurableApplicationContext.class}, context);
            this.prepareContext(context, environment, listeners, applicationArguments, printedBanner);
            //---------------------------启动阶段------------------------------------
            ...
    }

SpringApplicationRunListeners listeners = this.getRunListeners(args);
listeners.starting();

这部分就是获取SpringApplicationRunListeners来向SpringApplicationRunListener发布starting事件。整个应用上下文的事件发布监听系统从这里开始,
由于SpringBoot中监听系统的篇幅需要不小的描述,所以我另外出一章进行解释

ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);

该阶段用于装配ApplicationArguments。ApplicationArguments用于就简化SpringBoot应用启动参数的封装接口。其底层基于Spring中的命令行配置源SimpleCommandLinePropertySource
也就说,DefaultApplicationArguments将我们传入的args参数用于SimpleCommandLinePropertySource的构造。而SimpleCommandLinePropertySource可以将我们的参数解析成键值属性,然后我们上下文需要使用到参数时都可以在这个类中获得

ConfigurableEnvironment environment = this.prepareEnvironment(listeners, applicationArguments);
this.configureIgnoreBeanInfo(environment);
Banner printedBanner = this.printBanner(environment);

这个阶段是准备ConfigurableEnvironment
这部分也会另外出一章进行解释,我们现在姑且认为它就是我们根据applicationArguments构建的配置环境的抽象吧

context = this.createApplicationContext();

该阶段正式创建了Spring容器对象.
从下面源码中看出,createApplicationContext()方法是根据我们在初始化阶段判断或是配置的webApplicationType来选择合适的上下文容器。

protected ConfigurableApplicationContext createApplicationContext() {
		Class<?> contextClass = this.applicationContextClass;
		if (contextClass == null) {
			try {
				switch (this.webApplicationType) {
				case SERVLET:
					contextClass = Class.forName(DEFAULT_SERVLET_WEB_CONTEXT_CLASS);
					break;
				case REACTIVE:
					contextClass = Class.forName(DEFAULT_REACTIVE_WEB_CONTEXT_CLASS);
					break;
				default:
					contextClass = Class.forName(DEFAULT_CONTEXT_CLASS);
				}
			}
			catch (ClassNotFoundException ex) {
				throw new IllegalStateException(
						"Unable create a default ApplicationContext, please specify an ApplicationContextClass", ex);
			}
		}
		return (ConfigurableApplicationContext) BeanUtils.instantiateClass(contextClass);
	}

注:webApplicationType一开始是在构造方法中被推出来的,但是在我们的配置阶段也可以通过方法直接修改该参数。这里会根据webApplicationType创建容器,但是我们如果在配置阶段直接设置的容器,那么这个方法被跳过

this.prepareContext(context, environment, listeners, applicationArguments, printedBanner);

private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment,
			SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) {
		context.setEnvironment(environment);
		postProcessApplicationContext(context);
		applyInitializers(context);
		listeners.contextPrepared(context);
		if (this.logStartupInfo) {
			logStartupInfo(context.getParent() == null);
			logStartupProfileInfo(context);
		}
		// Add boot specific singleton beans
		ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
		beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
		if (printedBanner != null) {
			beanFactory.registerSingleton("springBootBanner", printedBanner);
		}
		if (beanFactory instanceof DefaultListableBeanFactory) {
			((DefaultListableBeanFactory) beanFactory)
					.setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
		}
		if (this.lazyInitialization) {
			context.addBeanFactoryPostProcessor(new LazyInitializationBeanFactoryPostProcessor());
		}
		// Load the sources
		Set<Object> sources = getAllSources();
		Assert.notEmpty(sources, "Sources must not be empty");
		load(context, sources.toArray(new Object[0]));
		listeners.contextLoaded(context);
	}

该方法分为两个阶段

  • 容器准备阶段
  • 容器装载阶段

容器准备阶段

context.setEnvironment(environment);

为创建好的容器设置环境

postProcessApplicationContext(context);

后置处理容器,这里的后置处理并不是调用后置处理器
根据源码,我们看出postProcessApplicationContext内容就是覆盖容器默认的BeanName生成器,同时如果容器继承自GenericApplicationContext,该方法也会覆盖容器的类加载器和资源加载器

protected void postProcessApplicationContext(ConfigurableApplicationContext context) {
		if (this.beanNameGenerator != null) {
			context.getBeanFactory().registerSingleton(AnnotationConfigUtils.CONFIGURATION_BEAN_NAME_GENERATOR,
					this.beanNameGenerator);
		}
		if (this.resourceLoader != null) {
			if (context instanceof GenericApplicationContext) {
				((GenericApplicationContext) context).setResourceLoader(this.resourceLoader);
			}
			if (context instanceof DefaultResourceLoader) {
				((DefaultResourceLoader) context).setClassLoader(this.resourceLoader.getClassLoader());
			}
		}
		if (this.addConversionService) {
			context.getBeanFactory().setConversionService(ApplicationConversionService.getSharedInstance());
		}
	}

GenericApplicationContext是AnnotationConfigApplicationContext和AnnotationConfigServletWebServerApplicationContext的子类,也就是说,它不会覆盖Reactive的容器的类加载器和资源加载器

applyInitializers(context);

在构造阶段我们从资源文件中获取并加载到了所有的内置初始化器。
这里就开始通过初始化器对容器进行初始化工作
关于内置的初始化器到底做了什么工作我们另外研究

listeners.contextPrepared(context);

发布容器准备完成的事件


装载阶段

下面进入Spring应用上下文的装载阶段

private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment,
			SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) {
		....
		if (this.logStartupInfo) {
			logStartupInfo(context.getParent() == null);
			logStartupProfileInfo(context);
		}
		// Add boot specific singleton beans
		ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
		beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
		if (printedBanner != null) {
			beanFactory.registerSingleton("springBootBanner", printedBanner);
		}
		if (beanFactory instanceof DefaultListableBeanFactory) {
			((DefaultListableBeanFactory) beanFactory)
					.setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
		}
		if (this.lazyInitialization) {
			context.addBeanFactoryPostProcessor(new LazyInitializationBeanFactoryPostProcessor());
		}
		// Load the sources
		Set<Object> sources = getAllSources();
		Assert.notEmpty(sources, "Sources must not be empty");
		load(context, sources.toArray(new Object[0]));
		listeners.contextLoaded(context);
	}

本阶段可以划分为四个过程

  • 注册SpringBean
  • 合并Spring上下文配置源
  • 加载Spring上下文配置源
  • 发布contextLoaded事件
注册SpringBean
		//获取容器实例
		ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
		//将前面创建的ApplicationArguments注册到容器中
		beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
		//
		if (printedBanner != null) {
			beanFactory.registerSingleton("springBootBanner", printedBanner);
		}
		if (beanFactory instanceof DefaultListableBeanFactory) {
			((DefaultListableBeanFactory) beanFactory)
					.setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
		}
		//将延迟实例化的BeanFactoryPostProcessor注册到容器中
		if (this.lazyInitialization) {
			context.addBeanFactoryPostProcessor(new LazyInitializationBeanFactoryPostProcessor());
		}
合并Spring上下文配置源

Set sources = getAllSources();

public Set<Object> getAllSources() {
		Set<Object> allSources = new LinkedHashSet<>();
		if (!CollectionUtils.isEmpty(this.primarySources)) {
			allSources.addAll(this.primarySources);
		}
		if (!CollectionUtils.isEmpty(this.sources)) {
			allSources.addAll(this.sources);
		}
		return Collections.unmodifiableSet(allSources);
	}

在开始的构造阶段,主类被当做primarySources被传入,除了这里可以传入配置之外,我们还可以在配置阶段通过add方法进行配置文件的添加。
所以,这里souce可能是包名、类名、或者XML配置资源路径。而该阶段所作的事情,就是将SpringApplication中所有异形配置源进行整合

加载Spring上下文配置源

load(context, sources.toArray(new Object[0]));

protected void load(ApplicationContext context, Object[] sources) {
		if (logger.isDebugEnabled()) {
			logger.debug("Loading source " + StringUtils.arrayToCommaDelimitedString(sources));
		}
		BeanDefinitionLoader loader = createBeanDefinitionLoader(getBeanDefinitionRegistry(context), sources);
		if (this.beanNameGenerator != null) {
			loader.setBeanNameGenerator(this.beanNameGenerator);
		}
		if (this.resourceLoader != null) {
			loader.setResourceLoader(this.resourceLoader);
		}
		if (this.environment != null) {
			loader.setEnvironment(this.environment);
		}
		loader.load();
	}

这里一开始创建了一个BeanDefinitionLoader
该类继承对各种数据源的读取方式,这个类可读取配置类、XML配置文件以及BeanDefinition扫描器等
也就是说通过BeanDefinitionLoader,前面所有的source都被解析成BeanDefinition

class BeanDefinitionLoader {

	private final Object[] sources;

	private final AnnotatedBeanDefinitionReader annotatedReader;

	private final XmlBeanDefinitionReader xmlReader;

	private BeanDefinitionReader groovyReader;

	private final ClassPathBeanDefinitionScanner scanner;

	private ResourceLoader resourceLoader;
...
}

后面将SpringApplciation中的类加载器、环境等参数覆盖掉BeanDefinitionLoader

loader.load();

启动加载,由于在实例化BeanDefinitionLoader时传入了我们的容器,所以BeanDefinitionLoader可以直接通过容器把加载好的BeanDefinition注册到容器中

发布contextLoaded事件

这里除了触发事件,同时将SpringApplication中的ApplicationListener全部添加到容器中
同时,由于此时容器尚未启动,相关的监听器依旧可以对容器进行修改

public void contextLoaded(ConfigurableApplicationContext context) {
		for (ApplicationListener<?> listener : this.application.getListeners()) {
			if (listener instanceof ApplicationContextAware) {
				((ApplicationContextAware) listener).setApplicationContext(context);
			}
			context.addApplicationListener(listener);
		}
		this.initialMulticaster.multicastEvent(new ApplicationPreparedEvent(this.application, this.args, context));
	}

2.2 SpringApplication启动阶段

在容器启动前,前面已经完成了以下任务:

  • Bean的解析,并将BeanDefinition注册到容器中了
  • 同步SpringApplication中的ApplicationListener到容器中
  • 参数的封装
  • ConfigurableEnvironment的创建

现在就是正式的启动阶段

public ConfigurableApplicationContext run(String... args) {
		//---------------------------准备阶段------------------------------------
        ...
            //---------------------------启动阶段------------------------------------
            this.refreshContext(context);
            //---------------------------启动后阶段------------------------------------
           ...
        }
    }

从源码中看出,refreshContext先调用了容器的refresh方法,然后又通过容器创建了一条shutDownHook线程(销毁生命周期回调)关于shutDownHook线程的作用,我们大致能猜到与销毁有关,但具体到后面再另外进行补充
关于容器的refresh方法,我们也独立写一篇进行说明

private void refreshContext(ConfigurableApplicationContext context) {
		refresh((ApplicationContext) context);
		if (this.registerShutdownHook) {
			try {
				context.registerShutdownHook();
			}
			catch (AccessControlException ex) {
				// Not allowed in some environments.
			}
		}
	}
protected void refresh(ApplicationContext applicationContext) {
		Assert.isInstanceOf(ConfigurableApplicationContext.class, applicationContext);
		refresh((ConfigurableApplicationContext) applicationContext);
	}
protected void refresh(ConfigurableApplicationContext applicationContext) {
		applicationContext.refresh();
	}
public void registerShutdownHook() {
        if (this.shutdownHook == null) {
            this.shutdownHook = new Thread("SpringContextShutdownHook") {
                public void run() {
                    synchronized(AbstractApplicationContext.this.startupShutdownMonitor) {
                        AbstractApplicationContext.this.doClose();
                    }
                }
            };
            Runtime.getRuntime().addShutdownHook(this.shutdownHook);
        }

    }

2.3 SpringApplication启动后阶段

protected void afterRefresh(ConfigurableApplicationContext context, ApplicationArguments args) {
	}

这里是一个空实现,留给开发人员进行拓展

另外在run方法的最后除了时间发布外,还有一个方法的的执行

private void callRunners(ApplicationContext context, ApplicationArguments args) {
		List<Object> runners = new ArrayList<>();
		runners.addAll(context.getBeansOfType(ApplicationRunner.class).values());
		runners.addAll(context.getBeansOfType(CommandLineRunner.class).values());
		AnnotationAwareOrderComparator.sort(runners);
		for (Object runner : new LinkedHashSet<>(runners)) {
			if (runner instanceof ApplicationRunner) {
				callRunner((ApplicationRunner) runner, args);
			}
			if (runner instanceof CommandLineRunner) {
				callRunner((CommandLineRunner) runner, args);
			}
		}
	}

根据官方文档,如果我们有什么命令或逻辑需要在容器启动完毕后执行,我们可以实现ApplicationRunner或者CommandLineRunner接口callRunner

如果说我们需要在容器启动完毕后执行自定义的逻辑,那么为什么需要另外两者,直接写在监听器里不好吗。首先,ApplciationListner无法获得SpringApplication的变量。第二在后两个由于注册在容器中,享有一些类似于Aware的容器福利

3.SpringApplication结束阶段

此处后面补上

4.SpringBoot应用退出

此处后面补上

  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

原来是肖某人

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

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

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

打赏作者

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

抵扣说明:

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

余额充值