Spring Boot启动过程

对于Spring Boot项目来说只需要如下代码就可以启动整个项目

public static void main(String[] args) {
    SpringApplication.run(OrderApplication.class, args);
}

那么Spring容器,Web容器等等是怎么启动的?

  1. new SpringApplication
    /**
     * Create a new {@link SpringApplication} instance. The application context will load
     * beans from the specified primary sources (see {@link SpringApplication class-level}
     * documentation for details. The instance can be customized before calling
     * {@link #run(String...)}.
     * @param resourceLoader the resource loader to use
     * @param primarySources the primary bean sources
     * @see #run(Class, String[])
     * @see #setSources(Set)
     */
    @SuppressWarnings({ "unchecked", "rawtypes" })
    public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
        this.resourceLoader = resourceLoader;
        Assert.notNull(primarySources, "PrimarySources must not be null");
        this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
        this.webApplicationType = WebApplicationType.deduceFromClasspath();
        setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
        setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
        this.mainApplicationClass = deduceMainApplicationClass();
    }
    
    • WebApplicationType.deduceFromClasspath();推断Web容器类型,具体参考容器推断

    • getSpringFactoriesInstances(ApplicationContextInitializer.class)
      从Spring factories中获取ApplicationContextInitializer.class对应的实现类,具体参考Spring factories

    •   //把对应的实现加进Initializers集合
        setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
        //把对应的实现加进Listeners集合
        setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
      
    • this.mainApplicationClass = deduceMainApplicationClass();
      一个有意思的写法,根据错误堆栈获取当前调用的类

      private Class<?> deduceMainApplicationClass() {
          try {
              //获取当前的堆栈
              StackTraceElement[] stackTrace = new RuntimeException().getStackTrace();
              for (StackTraceElement stackTraceElement : stackTrace) {
                  //判断方法名,获取对应的启动类
                  if ("main".equals(stackTraceElement.getMethodName())) {
                      return Class.forName(stackTraceElement.getClassName());
                  }
              }
          }
          catch (ClassNotFoundException ex) {
              // Swallow and continue
          }
          return null;
      }
      

      至此,完成了SpringApplication的初始化

  2. 调用SpringApplication的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(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;
    }
    
    •   //获取SpringApplicationRunListener接口对应的实现然后封装成SpringApplicationRunListeners,其实就是内部维护了一个集合
        SpringApplicationRunListeners listeners = getRunListeners(args);
        //观察者模式,循环调用内部集合进行通知
        listeners.starting();
      
    • ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);创建准备Environment对象

      private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments) {
          // 根据推断的web应用类型,创建对应的Environment
          ConfigurableEnvironment environment = getOrCreateEnvironment();
          // 对environment添加、删除、重排序PropertySource
          // 设置environment的active profiles
          configureEnvironment(environment, applicationArguments.getSourceArgs());
          //通知environment准备好
          listeners.environmentPrepared(environment);
          bindToSpringApplication(environment);
          if (!this.isCustomEnvironment) {
              environment = new EnvironmentConverter(getClassLoader()).convertEnvironmentIfNecessary(environment,
                      deduceEnvironmentClass());
          }
          ConfigurationPropertySources.attach(environment);
          return environment;
      }
      
    •   //根据推断来的web应用容器类型创建对应的Spring context
        //servlet容器对应的是AnnotationConfigServletWebServerApplicationContext
        context = createApplicationContext();
      
    • prepareContext(context, environment, listeners, applicationArguments, printedBanner);
      准备Spring context

      private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment,
          SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) {
          context.setEnvironment(environment);
          postProcessApplicationContext(context);
          // 从Spring factories中加载到的ApplicationContextInitializer对应的实例,
          // 调用对应实例的initialize方法,把Spring 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);
          }
          // 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);
      }
      
    • refreshContext(context);
      开始进入Web容器的启动

      • 根据之前创建的Spring context实例,调用对应的onRefresh()方法。这里调用的是org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.onRefresh()最终进入到
        private void createWebServer() {
            WebServer webServer = this.webServer;
            ServletContext servletContext = getServletContext();
            if (webServer == null && servletContext == null) {
                //webServer和servletContext都会空,会走到这里
                //从当前的BeanFactory获取类型为ServletWebServerFactory的bean
                //这里获取到的是TomcatServletWebServerFactory
                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();
        }
        
        getSelfInitializer()获取自身的初始化器,类型为ServletContextInitializer,这里返回的是一个lambda表达式,也就是返回了一个方法引用。
      • 至此,获取到了TomcatServletWebServerFactory实例和一个ServletContextInitializer类型的lambda表达式
        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);
            }
            //准备tomcat context
            prepareContext(tomcat.getHost(), initializers);
            return getTomcatWebServer(tomcat);
        }
        
        /**
        准备tomcat context
        */
        protected void prepareContext(Host host, ServletContextInitializer[] initializers) {
            File documentRoot = getValidDocumentRoot();
            TomcatEmbeddedContext context = new TomcatEmbeddedContext();
            if (documentRoot != null) {
                context.setResources(new LoaderHidingResourceRoot(context));
            }
            context.setName(getContextPath());
            context.setDisplayName(getDisplayName());
            context.setPath(getContextPath());
            File docBase = (documentRoot != null) ? documentRoot : createTempDir("tomcat-docbase");
            context.setDocBase(docBase.getAbsolutePath());
            context.addLifecycleListener(new FixContextListener());
            context.setParentClassLoader((this.resourceLoader != null) ? this.resourceLoader.getClassLoader()
                    : ClassUtils.getDefaultClassLoader());
            resetDefaultLocaleMapping(context);
            addLocaleMappings(context);
            context.setUseRelativeRedirects(false);
            try {
                context.setCreateUploadTargets(true);
            }
            catch (NoSuchMethodError ex) {
                // Tomcat is < 8.5.39. Continue.
            }
            configureTldSkipPatterns(context);
            WebappLoader loader = new WebappLoader(context.getParentClassLoader());
            loader.setLoaderClass(TomcatEmbeddedWebappClassLoader.class.getName());
            loader.setDelegate(true);
            context.setLoader(loader);
            if (isRegisterDefaultServlet()) {
                addDefaultServlet(context);
            }
            if (shouldRegisterJspServlet()) {
                addJspServlet(context);
                addJasperInitializer(context);
            }
            context.addLifecycleListener(new StaticResourceConfigurer(context));
            ServletContextInitializer[] initializersToUse = mergeInitializers(initializers);
            host.addChild(context);
            configureContext(context, initializersToUse);
            postProcessContext(context);
        }
        /**
        配置tomcat context
        */
        protected void configureContext(Context context, ServletContextInitializer[] initializers) {
            //初始化TomcatStarter,该类是tomcat容器初始化过程的一个委托或是代理的角色
            TomcatStarter starter = new TomcatStarter(initializers);
            if (context instanceof TomcatEmbeddedContext) {
                TomcatEmbeddedContext embeddedContext = (TomcatEmbeddedContext) context;
                embeddedContext.setStarter(starter);
                embeddedContext.setFailCtxIfServletStartFails(true);
            }
            //在这里会进行ServletContextInitializer的初始化工作
            context.addServletContainerInitializer(starter, NO_CLASSES);
            for (LifecycleListener lifecycleListener : this.contextLifecycleListeners) {
                context.addLifecycleListener(lifecycleListener);
            }
            for (Valve valve : this.contextValves) {
                context.getPipeline().addValve(valve);
            }
            for (ErrorPage errorPage : getErrorPages()) {
                org.apache.tomcat.util.descriptor.web.ErrorPage tomcatErrorPage = new org.apache.tomcat.util.descriptor.web.ErrorPage();
                tomcatErrorPage.setLocation(errorPage.getPath());
                tomcatErrorPage.setErrorCode(errorPage.getStatusCode());
                tomcatErrorPage.setExceptionType(errorPage.getExceptionName());
                context.addErrorPage(tomcatErrorPage);
            }
            for (MimeMappings.Mapping mapping : getMimeMappings()) {
                context.addMimeMapping(mapping.getExtension(), mapping.getMimeType());
            }
            configureSession(context);
            new DisableReferenceClearingContextCustomizer().customize(context);
            for (TomcatContextCustomizer customizer : this.tomcatContextCustomizers) {
                customizer.customize(context);
            }
        }
        
      • 最终ServletContext在启动过程中会被ServletContextInitializer的onStartup进行配置。会对ServletContext的任意servlet filters listeners context-params attributes进行必需的初始化配置。这里的关于Spring Boot的Web容器启动方式和传统的War包部署启动方式是有一定的差异
      • 在启动时TomcatStarter会获取到三个ServletContextInitializer的实例
        • (servletContext) -> this.initParameters.forEach(servletContext::setInitParameter)
          是一个lambda表达式,为servletContext设置初始化参数
        • new SessionConfiguringInitializer(this.session)
          配置session和cookie相关操作
        • ServletWebServerApplicationContext.getSelfInitializer()这里返回的是一个lambda表达式,也就是返回了一个方法引用。
          private org.springframework.boot.web.servlet.ServletContextInitializer getSelfInitializer() {
              return this::selfInitialize;
          }
          
          private void selfInitialize(ServletContext servletContext) throws ServletException {
              prepareWebApplicationContext(servletContext);
              registerApplicationScope(servletContext);
              WebApplicationContextUtils.registerEnvironmentBeans(getBeanFactory(), servletContext);
              for (ServletContextInitializer beans : getServletContextInitializerBeans()) {
                  beans.onStartup(servletContext);
              }
          }
          
          执行到这里的时候,会在这里获取到四个对应ServletContextInitializer实例
          • dispatcherServlet urls=[/]
          • characterEncodingFilter urls=[/*]
          • formContentFilter urls=[/*]
          • requestContextFilter urls=[/*]
      • tomcat都是通过编程的方式进行servlet等相关组件的注册,这里是用到Servlet 3.0规范的内容。
        例如Servlet的注册就是通过ServletRegistrationBean完成的。
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,132评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,802评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,566评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,858评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,867评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,695评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,064评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,705评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,915评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,677评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,796评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,432评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,041评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,992评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,223评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,185评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,535评论 2 343

推荐阅读更多精彩内容