SpringBoot启动流程分析原理

· 进阶提升:SpringBoot启动流程原理解析(一)

我们都知道SpringBoot自问世以来,一直有一个响亮的口号"约定优于配置",其实一种按约定编程的软件设计范式,目的在于减少软件开发人员在工作中的各种繁琐的配置,我们都知道传统的SSM框架的组合,会伴随着大量的繁琐的配置;稍有不慎,就可能各种bug,被人发现还以为我们技术很菜。而SpringBoot的出现不仅大大提高的开发人员的效率,还能避免由于"手抖"带来的配置错误。

很多程序员都感慨SpringBoot的到来大大解放了生产力,但是也有聪明的程序猿会多思考一下下,SpringBoot是怎么做到的约定的配置?它配置在了哪里?又是怎么启动的作用等等一系列的问号在跟女朋友花前月下的时候,依然会时不时冒出来。这严重影响了程序猿们的"幸"福生活,为了能广大"程序猿"同胞过上幸福美满的生活,今天咱么就来一起跟随源码探究下SpringBoot到底是如何做到"约定优于配置"的。

首先,我们先介绍下我们的演示的项目环境,我们先试用Spring Initializr来创建一个SpirngBoot工程。我们使用的版本是SpringBoot 2.1.5.RELEASE
SpringBoot启动流程分析原理_第1张图片

接下来就只在pom.xml文件中添加一个web工程的依赖,是为了观察后面容器类型的源码。

		
            org.springframework.boot
            spring-boot-starter-web
        

这样我们的环境就准备好了。

我们跟着SpringBoot的源码来探究它的启动流程,首先,先找到这个应用程序的入口主方法,在上面打一个断点

SpringBoot启动流程分析原理_第2张图片

启动之后,F5进入到run()方法

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

到这里会执行new SpringApplication(primarySources)创建spring应用对象,继续F5往下跟会执行SpringApplication构造器

// SpringApplication构造器
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
    // 资源加载器
		this.resourceLoader = resourceLoader;
		Assert.notNull(primarySources, "PrimarySources must not be null");
		this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
    // 1. 可能的web应用程序类型的类型。
		this.webApplicationType = WebApplicationType.deduceFromClasspath();
    // 2. 设置初始化应用context
		setInitializers((Collection) getSpringFactoriesInstances(
				ApplicationContextInitializer.class));
	// 3.设置初始化监听	
    setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
	// 4. 推演主程序类	
    this.mainApplicationClass = deduceMainApplicationClass();
	}

很多不为人知的事情都是发生在这个对象初始化的时候,这里我们都来一一解密

static WebApplicationType deduceFromClasspath() {
		if (ClassUtils.isPresent(WEBFLUX_INDICATOR_CLASS, null)
				&& !ClassUtils.isPresent(WEBMVC_INDICATOR_CLASS, null)
				&& !ClassUtils.isPresent(JERSEY_INDICATOR_CLASS, null)) {
			return WebApplicationType.REACTIVE;
		}
		for (String className : SERVLET_INDICATOR_CLASSES) {
			if (!ClassUtils.isPresent(className, null)) {
				return WebApplicationType.NONE;
			}
		}
    // 这里是我们测试web容器
		return WebApplicationType.SERVLET;
	}
一、推断web应用类型

这段代码是来推断我们的应用是哪种web应用程序

public enum WebApplicationType {

	/**
	 * The application should not run as a web application and should not start an
	 * embedded web server.
	 */
	NONE, // 不是web应用

	/**
	 * The application should run as a servlet-based web application and should start an
	 * embedded servlet web server.
	 */
	SERVLET, // servlet容器

	/**
	 * The application should run as a reactive web application and should start an
	 * embedded reactive web server.
	 */
	REACTIVE;  // 反应型web应用(webflux)

当然一开始我们加入了web的依赖,所以我们是servlet容器,

二、初始化应用上下文

在设置初始化应用context的时候 ,是先执行了`getSpringFactoriesInstances(ApplicationContextInitializer.class)方法,参数是ApplicationContextInitializer.class字节码对象

private <T> Collection<T> getSpringFactoriesInstances(Class<T> type,
		Class<?>[] parameterTypes, Object... args) {
		ClassLoader classLoader = getClassLoader();
		// Use names and ensure unique to protect against duplicates
		Set<String> names = new LinkedHashSet<>(
      // 加载ApplicationContextInitializer.class类型的类
       // 这里传入就是参数 ApplicationContextInitializer.clas
	 	SpringFactoriesLoader.loadFactoryNames(type, classLoader));
    // 实例化加载到的类
		List<T> instances = createSpringFactoriesInstances(type, parameterTypes,
				classLoader, args, names);
		AnnotationAwareOrderComparator.sort(instances);
	// 返回
    return instances;
}
public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {
		String factoryClassName = factoryClass.getName();
		return loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());
}

我们先来看看他是如何加载到这些类

private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
    // 先从缓存中拿
		MultiValueMap<String, String> result = cache.get(classLoader);
		if (result != null) {
			return result;
		}
		try {
// 去资源路径下加载
public static final String ACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories"; 
public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories"; 
	Enumeration<URL> urls = (classLoader != null ?	
 classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :     ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION);			result = new LinkedMultiValueMap<>();
			while (urls.hasMoreElements()) {
				URL url = urls.nextElement();
				UrlResource resource = new UrlResource(url);
				Properties properties = PropertiesLoaderUtils.loadProperties(resource);
				for (Map.Entry<?, ?> entry : properties.entrySet()) {
					String factoryClassName = ((String) entry.getKey()).trim();
					for (String factoryName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
			result.add(factoryClassName, factoryName.trim());
					}
				}
			}
			cache.put(classLoader, result);
             // 返回所有的加载的类
			return result;
		}
		catch (IOException ex) {
			throw new IllegalArgumentException("Unable to load factories from location [" +
					FACTORIES_RESOURCE_LOCATION + "]", ex);
		}
	}

这里有两个加载配置类的地方其实都指向了META-INF/spring.factories,通过断点我们可以看到应用程序是加载了以下几个jar下的spring.factores文件。

双击Shift搜索spring.factories可以看到它存在于以下工程中

SpringBoot启动流程分析原理_第3张图片

spring-boot-2.1.5.RELEASE.jar下的spring.factores(截图未完整截取)

SpringBoot启动流程分析原理_第4张图片

spring-boot-autoconfigure-2.1.5.RELEASE.jar`下的`spring.factores

SpringBoot启动流程分析原理_第5张图片

spring-beans-5.1.7.RELEASE.jar`下的`spring.factores

图片描述

从Map中根据org.springframework.context.ApplicationContextInitializer的类型拿到需要的类初始化类,断点进入getOrDefault(factoryClassName, Collections.emptyList());方法

图片描述

之后就是把加载到的需要初始化的类进行实例化添加到一个集合中等待备用

public void setInitializers(
			Collection<? extends ApplicationContextInitializer<?>> initializers) {
		this.initializers = new ArrayList<>();
		this.initializers.addAll(initializers);
	}
三、初始化监听器类

最关键的的还是这句

SpringBoot启动流程分析原理_第6张图片

当我们跟进去之后,会发现在初始化监听类的时候和上面初始化应用上下文是一样的代码。唯一不同的是getSpringFactoriesInstances(ApplicationListener.class))传进去的是·ApplicationListener.class所以这里就不再赘述。

四、推演主程序类

也就是这个最关键的代码了

this.mainApplicationClass = deduceMainApplicationClass();

SpringBoot启动流程分析原理_第7张图片

SpringBoot启动流程分析原理_第8张图片

到这里就完成了SpringBoot启动过程中初始化SpringApplication的过程。

小结

这篇文章主要是给大家说了下SpringBoot启动过程中初始化SpringApplication的流程,大致可以分为四个步骤:

  1. 推演web应用的类型(如果没有加web依赖类型NONE)
  2. 初始化ApplicationContextInitializer
  3. 初始化ApplicationListener
  4. 推演出主程序类

通过这样四个步骤就完成了第一步SpringApplication的初始化过程。

· 进阶提升:SpringBoot启动流程原理解析(二)

在上一节我们分析了SpringBoot启动流程中实例化SpringApplication的过程。

return new SpringApplication(primarySources).run(args)

这篇文章咱么说下run()方法开始之后都做了那些事情

继续往下跟着源码进入到run()这个是比较核心的一个方法了

public ConfigurableApplicationContext run(String... args) {
		StopWatch stopWatch = new StopWatch();
	// //计时器开始	
    stopWatch.start();
		ConfigurableApplicationContext context = null;
		Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
    // 配置Headless模式,是在缺少显示屏、键盘或者鼠标时的系统配置
    // 默认为true
		configureHeadlessProperty();
    // 获取所有的监听器
		SpringApplicationRunListeners listeners = getRunListeners(args);
    // 启动监听器
		listeners.starting();
		try {
			ApplicationArguments applicationArguments = new DefaultApplicationArguments(
					args);
            // 准备环境
			ConfigurableEnvironment environment = prepareEnvironment(listeners,
					applicationArguments);
            // 配置忽略的bean
			configureIgnoreBeanInfo(environment);
            // 打印banner
			Banner printedBanner = printBanner(environment);
			// 创建容器
            context = createApplicationContext();
			// 异常处理相关
            exceptionReporters = getSpringFactoriesInstances(
					SpringBootExceptionReporter.class,
					new Class[] { ConfigurableApplicationContext.class }, context);
			// 准本应用上下文
            prepareContext(context, environment, listeners, applicationArguments,
					printedBanner);
			// 刷新容器
            refreshContext(context);
            // 刷新容器后的扩展接口
			afterRefresh(context, applicationArguments);
			stopWatch.stop();
			if (this.logStartupInfo) {
				new StartupInfoLogger(this.mainApplicationClass)
						.logStarted(getApplicationLog(), stopWatch);
			}
            // 发布监听应用上下文启动完成
			listeners.started(context);
            // 执行runner
			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、获取所有的监听器
private SpringApplicationRunListeners getRunListeners(String[] args) {
		Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class };
		return new SpringApplicationRunListeners(logger, getSpringFactoriesInstances(    
				SpringApplicationRunListener.class, types, this, args));
}
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type,
			Class<?>[] parameterTypes, Object... args) {
		ClassLoader classLoader = getClassLoader();
		// Use names and ensure unique to protect against duplicates
		Set<String> names = new LinkedHashSet<>(
				SpringFactoriesLoader.loadFactoryNames(type, classLoader));
		List<T> instances = createSpringFactoriesInstances(type, parameterTypes,
				classLoader, args, names);
		AnnotationAwareOrderComparator.sort(instances);
		return instances;
	}

这段代码我们比较熟悉了,上一篇咱么详细介绍过,它的主要作用就是去META-INFO/spring.properties中加载配置SpringApplicationRunListener的监听器如下:

# Run Listeners
org.springframework.boot.SpringApplicationRunListener=
org.springframework.boot.context.event.EventPublishingRunListener

显然只有一个事件发布监听器类,拿到了EventPublishingRunListener启动事件发布监听器,下一步就是开始启动了listeners.starting();我们往下跟源码看

@Override
	public void starting() {
		this.initialMulticaster.multicastEvent(
				new ApplicationStartingEvent(this.application, this.args));
	}

启动的时候实际上是又创建了一个ApplicationStartingEvent对象,其实就是监听应用启动事件。

其中initialMulticaster是一个SimpleApplicationEventMulticaster

public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {
		ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
		for (final ApplicationListener<?> listener : 
             // 根据ApplicationStartingEvent事件类型找到对应的监				听器
             getApplicationListeners(event, type)) {
			 // 获取线程池,为每个监听事件创建一个线程
            Executor executor = getTaskExecutor();
			if (executor != null) {
				executor.execute(() -> invokeListener(listener, event));
			}
			else {
				invokeListener(listener, event);
			}
		}
	}

SpringBoot启动流程分析原理_第9张图片

2、准备环境
ConfigurableEnvironment environment = prepareEnvironment(listeners,
					applicationArguments);

继续往下跟看到

private ConfigurableEnvironment prepareEnvironment(
			SpringApplicationRunListeners listeners,
			ApplicationArguments applicationArguments) {
		// Create and configure the environment
    // 这里我们加了web的依赖所以是一个servlet容器
		ConfigurableEnvironment environment = getOrCreateEnvironment();
    // 配置环境
		configureEnvironment(environment, applicationArguments.getSourceArgs());
    //环境准备完成
		listeners.environmentPrepared(environment);
	// 	
    bindToSpringApplication(environment);
		if (!this.isCustomEnvironment) {
			environment = new EnvironmentConverter(getClassLoader())
					.convertEnvironmentIfNecessary(environment, deduceEnvironmentClass());
		}
		ConfigurationPropertySources.attach(environment);
		return environment;
	}

由于我们是添加了web的依赖getOrCreateEnvironment()返回的是一个StandardServletEnvironment标准的servlet环境,

2.1 配置环境
protected void configureEnvironment(ConfigurableEnvironment environment,
			String[] args) {
		if (this.addConversionService) {
            // 嵌入式的转换器
			ConversionService conversionService = ApplicationConversionService
					.getSharedInstance();
			environment.setConversionService(
					(ConfigurableConversionService) conversionService);
		}
    // 配置属性资源文件
		configurePropertySources(environment, args);
    //配置文件
		configureProfiles(environment, args);
	}

应用嵌入的转换器ApplicationConversionService

public static void configure(FormatterRegistry registry) {
		DefaultConversionService.addDefaultConverters(registry);
		DefaultFormattingConversionService.addDefaultFormatters(registry);
		addApplicationFormatters(registry);
		addApplicationConverters(registry);
	}
===================格式转换=============================
public static void addApplicationFormatters(FormatterRegistry registry) {
		registry.addFormatter(new CharArrayFormatter());
		registry.addFormatter(new InetAddressFormatter());
		registry.addFormatter(new IsoOffsetFormatter());
}
====================类型转换============================
public static void addApplicationConverters(ConverterRegistry registry) {
		addDelimitedStringConverters(registry);
		registry.addConverter(new StringToDurationConverter());
		registry.addConverter(new DurationToStringConverter());
		registry.addConverter(new NumberToDurationConverter());
		registry.addConverter(new DurationToNumberConverter());
		registry.addConverter(new StringToDataSizeConverter());
		registry.addConverter(new NumberToDataSizeConverter());
		registry.addConverterFactory(new StringToEnumIgnoringCaseConverterFactory());
	}
2.2 环境准备完成

同上面启动监听事件,这次的环境准备也是同样的代码

@Override
	public void environmentPrepared(ConfigurableEnvironment environment) {
		this.initialMulticaster.multicastEvent(
            // 创建了一个应用环境准备事件对象
            new ApplicationEnvironmentPreparedEvent(
				this.application, this.args, environment));
	}

debug进去之后代码跟ApplicationStartingEvent事件对象是一样的。不再赘述。

不过这里是7个监听器对象
SpringBoot启动流程分析原理_第10张图片

3、配置忽略的bean
configureIgnoreBeanInfo(environment);
4、打印banner

这是SpringBoot默认的启动时的图标

Banner printedBanner = printBanner(environment);

SpringBoot启动流程分析原理_第11张图片

这个是可以自定义的,也可以是图篇或是文本文件中的图形。

5、创建容器

紧接着上一篇,接下来就是创建容器

context = createApplicationContext();

SpringBoot启动流程分析原理_第12张图片

我们的环境是servletDEFAULT_SERVLET_WEB_CONTEXT_CLASS其实servlet通过反射的方式创建对象

public static final String DEFAULT_SERVLET_WEB_CONTEXT_CLASS = "org.springframework.boot."
			+ "web.servlet.context.AnnotationConfigServletWebServerApplicationContext";

并且实例化AnnotationConfigServletWebServerApplicationContext对象

6、异常错误处理

代码如下:

exceptionReporters = getSpringFactoriesInstances(
					SpringBootExceptionReporter.class,
					new Class[] { ConfigurableApplicationContext.class }, context);

其实还是去META-INFO/spring.factories配置文件中加载SpringBootExceptionReporter

# Error Reporters
org.springframework.boot.SpringBootExceptionReporter=
org.springframework.boot.diagnostics.FailureAnalyzers
7、准备应用上下文

这里就会根据之前创建的上下文、准备的环境、以及监听等准备应用上下文

rivate void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment, 
    SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) {
        //设置环境参数
        context.setEnvironment(environment);
        //设置后处理应用上下文
        this.postProcessApplicationContext(context);
        //把从spring.properties中加载的org.springframework.boot.context.ConfigurationWarningsApplicationContextInitializer,进行初始化操作
        this.applyInitializers(context);
        //EventPublishingRunLIstener发布应用上下文事件
        listeners.contextPrepared(context);
        //打印启动日志
        if (this.logStartupInfo) {
            this.logStartupInfo(context.getParent() == null);
            this.logStartupProfileInfo(context);
        }
        //注册一个字是springApplicationArguments单例的bean,
  context.getBeanFactory().registerSingleton("springApplicationArguments", applicationArguments);

   if (printedBanner != null) {
        //注册一个字是springBootBanner单例的bean,
            context.getBeanFactory().registerSingleton("springBootBanner", printedBanner);
        }
        //Load the sources 获取所有资源
        Set<Object> sources = this.getAllSources();
        Assert.notEmpty(sources, "Sources must not be empty");
        //创建BeanDefinitionLoader加载器加载注册所有的资源
        this.load(context, sources.toArray(new Object[0]));
        //同之前,发布应用上下文加载事件
        listeners.contextLoaded(context);
    }
8、刷新应用上下文

刷新应用上下文就进入了spring的源码了

@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.
 // 准备Bean工厂
prepareBeanFactory(beanFactory);

			try {
// Allows post-processing of the bean factory in context subclasses.	
  // 允许在上下文子类中对bean工厂进行后处理。              
     postProcessBeanFactory(beanFactory);

// Invoke factory processors registered as beans in the context.
//调用上下文中注册为bean的工厂处理器				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.
	// 初始化其他自定义bean
     onRefresh();

// Check for listener beans and register them.
         // 注册监听器
          registerListeners();

// Instantiate all remaining (non-lazy-init) singletons.
	// 完成bean工厂初始化			finishBeanFactoryInitialization(beanFactory);

		// Last step: publish corresponding event.
          // 完成刷新,清缓存,初始化生命周期,事件发布等      
				finishRefresh();
			}

			catch (BeansException ex) {
				if (logger.isWarnEnabled()) {
					logger.warn("Exception encountered during context init ialization - " +
							"cancelling refresh attempt: " + ex);
				}

	// Destroy already created singletons to avoid dangling resources.
		      // 销毁bean		
                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();
			}
		}
	}

刷新的代码有点深,也是在这时创建了Tomcat对象,这也是SpringBoot一键启动web工程的关键

@Override
	protected void onRefresh() {
		super.onRefresh();
		try {
            // 创建web服务
			createWebServer();
		}
		catch (Throwable ex) {
			throw new ApplicationContextException("Unable to start web server", ex);
		}
	}
private void createWebServer() {
		WebServer webServer = this.webServer;
		ServletContext servletContext = getServletContext();
		if (webServer == null && servletContext == null) {
			ServletWebServerFactory factory = getWebServerFactory();
            // 获取到Tomcat
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();
	}

创建了Tomcat对象,并设置参数

public WebServer getWebServer(ServletContextInitializer... initializers) {
		Tomcat tomcat = new Tomcat();
		File baseDir = (this.baseDirectory != null) ? this.baseDirectory
				: createTempDir("tomcat");
		tomcat.setBaseDir(baseDir.getAbsolutePath());
		Connector connector = new Connector(this.protocol);
		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);
    // 返回TomcatWebServer服务
		return getTomcatWebServer(tomcat);
	}

启动Tomcat服务器

SpringBoot启动流程分析原理_第13张图片

9、刷新后处理

afterRefresh()是个一空实现,留着后期扩展

protected void afterRefresh(ConfigurableApplicationContext context,	ApplicationArguments args) {
	// TODO
}
10、发布监听应用启动事件
public void started(ConfigurableApplicationContext context) {
		context.publishEvent(
new ApplicationStartedEvent(this.application, this.args, context));
	}

这里是调用context.publishEvent()方法,发布应用启动事件ApplicationStartedEvent.

11、执行Runner

获取所有的ApplicationRunner和CommandLineRunner来初始化一些参数

callRunner()是一个回调函数

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);
			}
		}
	}
12、发布上下文准备完成的事件
listeners.running(context);
public void running(ConfigurableApplicationContext context) {
	context.publishEvent(
				new ApplicationReadyEvent(this.application, this.args, context));
}

这段代码看上去似成相识,前面有很多类似的代码,不同的是这里上下文准备完成之后发布了一个ApplicationReadyEvent事件,声明一下应用上下文准备完成

小结

这篇主要是介绍了SpringBoot启动过程中run()的这个过程。从中我们也可以发现一些非常好的编码习惯,大家可以在日常的工作中从模仿到内化,慢慢变成自己的东西。

本来来自慕课网 java架构师课程 笔记

你可能感兴趣的:(java架构,spring,boot,java,spring)