分享

SpringBoot启动流程总结

 米老鼠的世界 2021-06-08

一直很好奇SpringBoot这么一个大怪物,启动的时候做了哪些事情,然后看了很多老师讲的教学视频,然后自己想好好整理一下,做下学习笔记下次也方便自己阅读

1.执行main方法

  1. public static void main(String[] args) {
  2. //代码很简单SpringApplication.run();
  3. SpringApplication.run(ConsumerApp.class, args);
  4. }
  1. public static ConfigurableApplicationContext run(Class<?> primarySource,
  2. String... args) {
  3. //这个里面调用了run() 方法,我们转到定义
  4. return run(new Class<?>[] { primarySource }, args);
  5. }
  6. //这个run方法代码也很简单,就做了两件事情
  7. //1、new了一个SpringApplication() 这么一个对象
  8. //2、执行new出来的SpringApplication()对象的run()方法
  9. public static ConfigurableApplicationContext run(Class<?>[] primarySources,
  10. String[] args) {
  11. return new SpringApplication(primarySources).run(args);
  12. }

 上面代码主要做了两件事情。第一步new了一个SpringApplication对象 ,第二部调用了run()方法。接下来我们一起看下new SpringApplication() 主要做了什么事情。

  1. public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
  2. this.resourceLoader = resourceLoader;
  3. Assert.notNull(primarySources, "PrimarySources must not be null");
  4. //1、先把主类保存起来
  5. this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
  6. //2、判断运行项目的类型
  7. this.webApplicationType = WebApplicationType.deduceFromClasspath();
  8. //3、扫描当前路径下META-INF/spring.factories文件的,加载ApplicationContextInitializer接口实例
  9. setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
  10. //4、扫描当前路径下META-INF/spring.factories文件的,加载ApplicationListener接口实例
  11. setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
  12. this.mainApplicationClass = deduceMainApplicationClass();
  13. }

利用SPI机制扫描 META-INF/spring.factories 这个文件,并且加载 ApplicationContextInitializerApplicationListener 接口实例。

1、ApplicationContextInitializer 这个类当springboot上下文Context初始化完成后会调用

2、ApplicationListener 当springboot启动时事件change后都会触发

下面我们来自定义ApplicationContextInitializer、ApplicationListener 接口实现类,然后Debug来看下效果。

  1. /**
  2. * Context初始化后调用类
  3. * @author ShiMinChen
  4. *
  5. */
  6. public class StarterApplicationContextInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
  7. @Override
  8. public void initialize(ConfigurableApplicationContext applicationContext) {
  9. System.out.println("applicationContext 初始化完成 ... ");
  10. }
  11. }
  1. public class StarterApplicationListener implements ApplicationListener {
  2. @Override
  3. public void onApplicationEvent(ApplicationEvent event) {
  4. System.out.println(event.toString());
  5. System.out.println("ApplicationListener .... " + System.currentTimeMillis());
  6. }
  7. }

我们需要把这两个类集成到springboot里面去,其实操作也挺简单的

然后在META-INF/spring.factories 文件配置那两个类

  1. org.springframework.context.ApplicationContextInitializer=\
  2. org.admin.starter.test.listener.StarterApplicationContextInitializer
  3. org.springframework.context.ApplicationListener=\
  4. org.admin.starter.test.listener.StarterApplicationListener

4、我们代码DEBUG一下,在loadSpringFactories() 方法打一个断点

  1. private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
  2. MultiValueMap<String, String> result = cache.get(classLoader);
  3. if (result != null)
  4. return result;
  5. try {
  6. Enumeration<URL> urls = (classLoader != null ?
  7. classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
  8. ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
  9. result = new LinkedMultiValueMap<>();
  10. while (urls.hasMoreElements()) {
  11. URL url = urls.nextElement();
  12. UrlResource resource = new UrlResource(url);
  13. Properties properties = PropertiesLoaderUtils.loadProperties(resource);
  14. for (Map.Entry<?, ?> entry : properties.entrySet()) {
  15. List<String> factoryClassNames = Arrays.asList(
  16. StringUtils.commaDelimitedListToStringArray((String) entry.getValue()));
  17. result.addAll((String) entry.getKey(), factoryClassNames);
  18. }
  19. }
  20. cache.put(classLoader, result);
  21. // 端点打在这里就行了
  22. return result;
  23. }
  24. catch (IOException ex) {
  25. throw new IllegalArgumentException("Unable to load factories from location [" +
  26. FACTORIES_RESOURCE_LOCATION + "]", ex);
  27. }
  28. }

总结:上面就是SpringApplication初始化的代码,new SpringApplication()没做啥事情 ,利用SPI机制主要加载了META-INF/spring.factories 下面定义的事件监听器接口实现类

2.执行run() 方法

  1. public ConfigurableApplicationContext run(String... args) {
  2. <!--1、这个是一个计时器,没什么好说的-->
  3. StopWatch stopWatch = new StopWatch();
  4. stopWatch.start();
  5. ConfigurableApplicationContext context = null;
  6. Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
  7. <!--2、这个也不是重点,就是设置了一些环境变量-->
  8. configureHeadlessProperty();
  9. <!--3、获取事件监听器SpringApplicationRunListener类型,并且执行starting()方法-->
  10. SpringApplicationRunListeners listeners = getRunListeners(args);
  11. listeners.starting();
  12. try {
  13. <!--4、把参数args封装成DefaultApplicationArguments,这个了解一下就知道-->
  14. ApplicationArguments applicationArguments = new DefaultApplicationArguments(
  15. args);
  16. <!--5、这个很重要准备环境了,并且把环境跟spring上下文绑定好,并且执行environmentPrepared()方法-->
  17. ConfigurableEnvironment environment = prepareEnvironment(listeners,
  18. applicationArguments);
  19. <!--6、判断一些环境的值,并设置一些环境的值-->
  20. configureIgnoreBeanInfo(environment);
  21. <!--7、打印banner-->
  22. Banner printedBanner = printBanner(environment);
  23. <!--8、创建上下文,根据项目类型创建上下文-->
  24. context = createApplicationContext();
  25. <!--9、获取异常报告事件监听-->
  26. exceptionReporters = getSpringFactoriesInstances(
  27. SpringBootExceptionReporter.class,
  28. new Class[] { ConfigurableApplicationContext.class }, context);
  29. <!--10、准备上下文,执行完成后调用contextPrepared()方法,contextLoaded()方法-->
  30. prepareContext(context, environment, listeners, applicationArguments,
  31. printedBanner);
  32. <!--11、这个是spring启动的代码了,这里就回去里面就回去扫描并且初始化单实列bean了-->
  33. //这个refreshContext()加载了bean,还启动了内置web容器,需要细细的去看看
  34. refreshContext(context);
  35. <!--12、啥事情都没有做-->
  36. afterRefresh(context, applicationArguments);
  37. stopWatch.stop();
  38. if (this.logStartupInfo) {
  39. new StartupInfoLogger(this.mainApplicationClass)
  40. .logStarted(getApplicationLog(), stopWatch);
  41. }
  42. <!--13、执行ApplicationRunListeners中的started()方法-->
  43. listeners.started(context);
  44. <!--执行Runner(ApplicationRunner和CommandLineRunner)-->
  45. callRunners(context, applicationArguments);
  46. }
  47. catch (Throwable ex) {
  48. handleRunFailure(context, listeners, exceptionReporters, ex);
  49. throw new IllegalStateException(ex);
  50. }
  51. listeners.running(context);
  52. return context;
  53. }

2.1  createApplicationContext()

一起来看下context = createApplicationContext(); 这段代码,这段代码主要是根据项目类型创建上下文,并且会注入几个核心组件类。

  1. protected ConfigurableApplicationContext createApplicationContext() {
  2. Class<?> contextClass = this.applicationContextClass;
  3. if (contextClass == null) {
  4. try {
  5. switch (this.webApplicationType) {
  6. case SERVLET:
  7. contextClass = Class.forName(DEFAULT_SERVLET_WEB_CONTEXT_CLASS);
  8. break;
  9. case REACTIVE:
  10. contextClass = Class.forName(DEFAULT_REACTIVE_WEB_CONTEXT_CLASS);
  11. break;
  12. default:
  13. contextClass = Class.forName(DEFAULT_CONTEXT_CLASS);
  14. }
  15. }
  16. catch (ClassNotFoundException ex) {
  17. throw new IllegalStateException(
  18. "Unable create a default ApplicationContext, please specify an ApplicationContextClass", ex);
  19. }
  20. }
  21. return (ConfigurableApplicationContext) BeanUtils.instantiateClass(contextClass);
  22. }
  1. public AnnotationConfigServletWebServerApplicationContext(DefaultListableBeanFactory beanFactory) {
  2. super(beanFactory);
  3. //1:会去注入一些spring核心组件
  4. this.reader = new AnnotatedBeanDefinitionReader(this);
  5. this.scanner = new ClassPathBeanDefinitionScanner(this);
  6. }

Web类型项目创建上下文对象 AnnotationConfigServletWebServerApplicationContext 。这里会把 ConfigurationClassPostProcessor 、AutowiredAnnotationBeanPostProcessor 等一些核心组件加入到Spring容器(扯淡有点远,这是spring容器启动的一些知识点,去了解一下对理解springboot有很大的帮助

2.2  refreshContext()

下面一起来看下refreshContext(context) 这个方法,这个方法启动spring的代码加载了bean,还启动了内置web容器

  1. private void refreshContext(ConfigurableApplicationContext context) {
  2. // 转到定义看看
  3. refresh(context);
  4. if (this.registerShutdownHook) {
  5. try {
  6. context.registerShutdownHook();
  7. }
  8. catch (AccessControlException ex) {
  9. // Not allowed in some environments.
  10. }
  11. }
  12. }
  1. protected void refresh(ApplicationContext applicationContext) {
  2. Assert.isInstanceOf(AbstractApplicationContext.class, applicationContext);
  3. //看看refresh()方法去
  4. ((AbstractApplicationContext) applicationContext).refresh();
  5. }

 转到AbstractApplicationContext - >refresh()方法里面发现这是spring容器启动代码

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

 spring容器启动代码就不说了,这里主要看一下onRefresh() 这个方法。转到定义发现这个方法里面啥都没有,这明显是一个钩子方法,它会钩到它子类重写onRefresh()方法。所以去看子类里面的onRefresh()

  1. protected void onRefresh() throws BeansException {
  2. //这是一个空方法,AbstractApplicationContext 这个类是一个抽象类,
  3. //所以我们要找到集成AbstractApplicationContext的子类,去看子类里面的onRefresh()
  4. // For subclasses: do nothing by default.
  5. }

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

我们就去看 ServletWebServerApplicationContext 这个类下面的 onRefresh() 方法

  1. protected void onRefresh() {
  2. super.onRefresh();
  3. try {
  4. //看到内置容器的影子了,进去看看
  5. createWebServer();
  6. }
  7. catch (Throwable ex) {
  8. throw new ApplicationContextException("Unable to start web server", ex);
  9. }
  10. }
  1. private void createWebServer() {
  2. WebServer webServer = this.webServer;
  3. ServletContext servletContext = getServletContext();
  4. if (webServer == null && servletContext == null) {
  5. //1、这个获取webServerFactory还是要进去看看
  6. ServletWebServerFactory factory = getWebServerFactory();
  7. this.webServer = factory.getWebServer(getSelfInitializer());
  8. }
  9. else if (servletContext != null) {
  10. try {
  11. getSelfInitializer().onStartup(servletContext);
  12. }
  13. catch (ServletException ex) {
  14. throw new ApplicationContextException("Cannot initialize servlet context",
  15. ex);
  16. }
  17. }
  18. initPropertySources();
  19. }

 我们继续看下getWebServletFactory() 这个方法,这个里面其实就是选择出哪种类型的web容器了

  1. protected ServletWebServerFactory getWebServerFactory() {
  2. // Use bean names so that we don't consider the hierarchy
  3. String[] beanNames = getBeanFactory()
  4. .getBeanNamesForType(ServletWebServerFactory.class);
  5. if (beanNames.length == 0) {
  6. throw new ApplicationContextException(
  7. "Unable to start ServletWebServerApplicationContext due to missing "
  8. + "ServletWebServerFactory bean.");
  9. }
  10. if (beanNames.length > 1) {
  11. throw new ApplicationContextException(
  12. "Unable to start ServletWebServerApplicationContext due to multiple "
  13. + "ServletWebServerFactory beans : "
  14. + StringUtils.arrayToCommaDelimitedString(beanNames));
  15. }
  16. return getBeanFactory().getBean(beanNames[0], ServletWebServerFactory.class);
  17. }

 

我们再回头去看factory.getWebServer(getSelfInitializer()) ,转到定义就会看到很熟悉的名字tomcat

  1. public WebServer getWebServer(ServletContextInitializer... initializers) {
  2. //tomcat这位大哥出现了
  3. Tomcat tomcat = new Tomcat();
  4. File baseDir = (this.baseDirectory != null ? this.baseDirectory
  5. : createTempDir("tomcat"));
  6. tomcat.setBaseDir(baseDir.getAbsolutePath());
  7. Connector connector = new Connector(this.protocol);
  8. tomcat.getService().addConnector(connector);
  9. customizeConnector(connector);
  10. tomcat.setConnector(connector);
  11. tomcat.getHost().setAutoDeploy(false);
  12. configureEngine(tomcat.getEngine());
  13. for (Connector additionalConnector : this.additionalTomcatConnectors) {
  14. tomcat.getService().addConnector(additionalConnector);
  15. }
  16. prepareContext(tomcat.getHost(), initializers);
  17. return getTomcatWebServer(tomcat);
  18. }

内置的Servlet容器就是在onRefresh() 方法里面启动的,至此一个Servlet容器就启动OK了。

总结:

1、new了一个SpringApplication对象,使用SPI技术加载加载 ApplicationContextInitializerApplicationListener 接口实例

2、调用SpringApplication.run() 方法

3、调用createApplicationContext()方法创建上下文对象,创建上下文对象同时会注册spring的核心组件类(ConfigurationClassPostProcessor 、AutowiredAnnotationBeanPostProcessor 等)。

4、调用refreshContext() 方法启动Spring容器和内置的Servlet容器

本人也是一个刚入门的小白,也是看了很多大神写的这类文章,按照自己思路整理一下,就是为了加深下印象。但也希望对大家有帮助

 

 

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多