SpringBoot启动流程总结
一直很好奇SpringBoot这么一个大怪物,启动的时候做了哪些事情,然后看了很多老师讲的教学视频,然后自己想好好整理一下,做下学习笔记下次也方便自己阅读
1.执行main方法
public static void main(String[] args) {//代码很简单SpringApplication.run();SpringApplication.run(ConsumerApp.class, args);}
public static ConfigurableApplicationContext run(Class<?> primarySource,String... args) {//这个里面调用了run() 方法,我们转到定义return run(new Class<?>[] { primarySource }, args);}//这个run方法代码也很简单,就做了两件事情//1、new了一个SpringApplication() 这么一个对象//2、执行new出来的SpringApplication()对象的run()方法public static ConfigurableApplicationContext run(Class<?>[] primarySources,String[] args) {return new SpringApplication(primarySources).run(args);}
上面代码主要做了两件事情。第一步new了一个SpringApplication对象 ,第二部调用了run()方法。接下来我们一起看下new SpringApplication() 主要做了什么事情。
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {this.resourceLoader = resourceLoader;Assert.notNull(primarySources, "PrimarySources must not be null");//1、先把主类保存起来this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));//2、判断运行项目的类型this.webApplicationType = WebApplicationType.deduceFromClasspath();//3、扫描当前路径下META-INF/spring.factories文件的,加载ApplicationContextInitializer接口实例setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));//4、扫描当前路径下META-INF/spring.factories文件的,加载ApplicationListener接口实例setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));this.mainApplicationClass = deduceMainApplicationClass();}
利用SPI机制扫描 META-INF/spring.factories 这个文件,并且加载 ApplicationContextInitializer、ApplicationListener 接口实例。
1、ApplicationContextInitializer 这个类当springboot上下文Context初始化完成后会调用
2、ApplicationListener 当springboot启动时事件change后都会触发
下面我们来自定义ApplicationContextInitializer、ApplicationListener 接口实现类,然后Debug来看下效果。
/*** Context初始化后调用类* @author ShiMinChen**/public class StarterApplicationContextInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {@Overridepublic void initialize(ConfigurableApplicationContext applicationContext) {System.out.println("applicationContext 初始化完成 ... ");}}
public class StarterApplicationListener implements ApplicationListener {@Overridepublic void onApplicationEvent(ApplicationEvent event) {System.out.println(event.toString());System.out.println("ApplicationListener .... " + System.currentTimeMillis());}}
我们需要把这两个类集成到springboot里面去,其实操作也挺简单的

然后在META-INF/spring.factories 文件配置那两个类
org.springframework.context.ApplicationContextInitializer=\org.admin.starter.test.listener.StarterApplicationContextInitializerorg.springframework.context.ApplicationListener=\org.admin.starter.test.listener.StarterApplicationListener
4、我们代码DEBUG一下,在loadSpringFactories() 方法打一个断点
private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {MultiValueMap<String, String> result = cache.get(classLoader);if (result != null)return result;try {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()) {List<String> factoryClassNames = Arrays.asList(StringUtils.commaDelimitedListToStringArray((String) entry.getValue()));result.addAll((String) entry.getKey(), factoryClassNames);}}cache.put(classLoader, result);// 端点打在这里就行了return result;}catch (IOException ex) {throw new IllegalArgumentException("Unable to load factories from location [" +FACTORIES_RESOURCE_LOCATION + "]", ex);}}

总结:上面就是SpringApplication初始化的代码,new SpringApplication()没做啥事情 ,利用SPI机制主要加载了META-INF/spring.factories 下面定义的事件监听器接口实现类
2.执行run() 方法
public ConfigurableApplicationContext run(String... args) {<!--1、这个是一个计时器,没什么好说的-->StopWatch stopWatch = new StopWatch();stopWatch.start();ConfigurableApplicationContext context = null;Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();<!--2、这个也不是重点,就是设置了一些环境变量-->configureHeadlessProperty();<!--3、获取事件监听器SpringApplicationRunListener类型,并且执行starting()方法-->SpringApplicationRunListeners listeners = getRunListeners(args);listeners.starting();try {<!--4、把参数args封装成DefaultApplicationArguments,这个了解一下就知道-->ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);<!--5、这个很重要准备环境了,并且把环境跟spring上下文绑定好,并且执行environmentPrepared()方法-->ConfigurableEnvironment environment = prepareEnvironment(listeners,applicationArguments);<!--6、判断一些环境的值,并设置一些环境的值-->configureIgnoreBeanInfo(environment);<!--7、打印banner-->Banner printedBanner = printBanner(environment);<!--8、创建上下文,根据项目类型创建上下文-->context = createApplicationContext();<!--9、获取异常报告事件监听-->exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,new Class[] { ConfigurableApplicationContext.class }, context);<!--10、准备上下文,执行完成后调用contextPrepared()方法,contextLoaded()方法-->prepareContext(context, environment, listeners, applicationArguments,printedBanner);<!--11、这个是spring启动的代码了,这里就回去里面就回去扫描并且初始化单实列bean了-->//这个refreshContext()加载了bean,还启动了内置web容器,需要细细的去看看refreshContext(context);<!--12、啥事情都没有做-->afterRefresh(context, applicationArguments);stopWatch.stop();if (this.logStartupInfo) {new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);}<!--13、执行ApplicationRunListeners中的started()方法-->listeners.started(context);<!--执行Runner(ApplicationRunner和CommandLineRunner)-->callRunners(context, applicationArguments);}catch (Throwable ex) {handleRunFailure(context, listeners, exceptionReporters, ex);throw new IllegalStateException(ex);}listeners.running(context);return context;}
2.1 createApplicationContext()
一起来看下context = createApplicationContext(); 这段代码,这段代码主要是根据项目类型创建上下文,并且会注入几个核心组件类。
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);}
public AnnotationConfigServletWebServerApplicationContext(DefaultListableBeanFactory beanFactory) {super(beanFactory);//1:会去注入一些spring核心组件this.reader = new AnnotatedBeanDefinitionReader(this);this.scanner = new ClassPathBeanDefinitionScanner(this);}
Web类型项目创建上下文对象 AnnotationConfigServletWebServerApplicationContext 。这里会把 ConfigurationClassPostProcessor 、AutowiredAnnotationBeanPostProcessor 等一些核心组件加入到Spring容器(扯淡有点远,这是spring容器启动的一些知识点,去了解一下对理解springboot有很大的帮助)
2.2 refreshContext()
下面一起来看下refreshContext(context) 这个方法,这个方法启动spring的代码加载了bean,还启动了内置web容器
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);//看看refresh()方法去((AbstractApplicationContext) applicationContext).refresh();}
转到AbstractApplicationContext - >refresh()方法里面发现这是spring容器启动代码
@Overridepublic 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();}}}
spring容器启动代码就不说了,这里主要看一下onRefresh() 这个方法。转到定义发现这个方法里面啥都没有,这明显是一个钩子方法,它会钩到它子类重写onRefresh()方法。所以去看子类里面的onRefresh()
protected void onRefresh() throws BeansException {//这是一个空方法,AbstractApplicationContext 这个类是一个抽象类,//所以我们要找到集成AbstractApplicationContext的子类,去看子类里面的onRefresh()// For subclasses: do nothing by default.}

我们这里是一个Web项目,所以我们就去看 ServletWebServerApplicationContext 这个类 ,我还是把类的关系图贴一下

我们就去看 ServletWebServerApplicationContext 这个类下面的 onRefresh() 方法
protected void onRefresh() {super.onRefresh();try {//看到内置容器的影子了,进去看看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) {//1、这个获取webServerFactory还是要进去看看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();}
我们继续看下getWebServletFactory() 这个方法,这个里面其实就是选择出哪种类型的web容器了
protected ServletWebServerFactory getWebServerFactory() {// Use bean names so that we don't consider the hierarchyString[] beanNames = getBeanFactory().getBeanNamesForType(ServletWebServerFactory.class);if (beanNames.length == 0) {throw new ApplicationContextException("Unable to start ServletWebServerApplicationContext due to missing "+ "ServletWebServerFactory bean.");}if (beanNames.length > 1) {throw new ApplicationContextException("Unable to start ServletWebServerApplicationContext due to multiple "+ "ServletWebServerFactory beans : "+ StringUtils.arrayToCommaDelimitedString(beanNames));}return getBeanFactory().getBean(beanNames[0], ServletWebServerFactory.class);}

我们再回头去看factory.getWebServer(getSelfInitializer()) ,转到定义就会看到很熟悉的名字tomcat
public WebServer getWebServer(ServletContextInitializer... initializers) {//tomcat这位大哥出现了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);return getTomcatWebServer(tomcat);}
内置的Servlet容器就是在onRefresh() 方法里面启动的,至此一个Servlet容器就启动OK了。
总结:
1、new了一个SpringApplication对象,使用SPI技术加载加载 ApplicationContextInitializer、ApplicationListener 接口实例
2、调用SpringApplication.run() 方法
3、调用createApplicationContext()方法创建上下文对象,创建上下文对象同时会注册spring的核心组件类(ConfigurationClassPostProcessor 、AutowiredAnnotationBeanPostProcessor 等)。
4、调用refreshContext() 方法启动Spring容器和内置的Servlet容器
本人也是一个刚入门的小白,也是看了很多大神写的这类文章,按照自己思路整理一下,就是为了加深下印象。但也希望对大家有帮助

