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 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 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包部署启动方式是有一定的差异
        • 传统的Spring MVC War包的启动方式
        • Spring Boot jar包的启动方式
      • 在启动时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完成的。
    • 你可能感兴趣的:(Spring Boot启动过程)