四、SpringBoot框架之Web开发(下)

529 阅读18分钟

(六)错误处理机制

1. SpringBoot默认的错误处理机制

1)浏览器会返回默认的错误界面,并携带一些显示信息

浏览器发送请求的请求头

image-20201028150846118

2)使用postman进行请求访问,会返回json数组

image-20201027153253148

postman发送请求的请求头

image-20201028151131372

默认处理机制的原理:

ErrorMvcAutoConfiguration 中配置了Mvc错误的处理方式,是SpringBoot中处理Mvc错误信息的自动配置,通过向IoC容器中添加组件来完成配置。

自动配置中向容器中添加了以下组件:

① DefaultErrorAttributes:默认进行错误信息处理

② BasicErrorController:处理默认/error的请求

③ ErrorPageCustomizer:指定错误页面跳转

④ DefaultErrorViewResolver:设置默认的错误视图

这些组件具体的功能:

① DefaultErrorAttributes:用于错误信息处理

@Order(Ordered.HIGHEST_PRECEDENCE)
public class DefaultErrorAttributes implements ErrorAttributes, HandlerExceptionResolver, Ordered {

    ......

        @Override
        @Deprecated
        public Map<String, Object> getErrorAttributes(WebRequest webRequest, boolean includeStackTrace) {
        Map<String, Object> errorAttributes = new LinkedHashMap<>();
        // 将时间戳存放到
        errorAttributes.put("timestamp", new Date());
        addStatus(errorAttributes, webRequest);
        addErrorDetails(errorAttributes, webRequest, includeStackTrace);
        addPath(errorAttributes, webRequest);
        return errorAttributes;
    }

    // 取出错误状态码,错误提示等
    private void addStatus(Map<String, Object> errorAttributes, RequestAttributes requestAttributes) {
        Integer status = getAttribute(requestAttributes, RequestDispatcher.ERROR_STATUS_CODE);
        if (status == null) {
            errorAttributes.put("status", 999);
            errorAttributes.put("error", "None");
            return;
        }
        errorAttributes.put("status", status);
        try {
            errorAttributes.put("error", HttpStatus.valueOf(status).getReasonPhrase());
        }
        catch (Exception ex) {
            // Unable to obtain a reason
            errorAttributes.put("error", "Http Status " + status);
        }
    }

    // 取出详细的错误信息,包括异常、获取异常路径、获取异常提示等
    private void addErrorDetails(Map<String, Object> errorAttributes, WebRequest webRequest,
                                 boolean includeStackTrace) {
        Throwable error = getError(webRequest);
        if (error != null) {
            while (error instanceof ServletException && error.getCause() != null) {
                error = error.getCause();
            }
            errorAttributes.put("exception", error.getClass().getName());
            if (includeStackTrace) {
                addStackTrace(errorAttributes, error);
            }
        }
        addErrorMessage(errorAttributes, webRequest, error);
    }

    ......

}

② BasicErrorController:处理默认/error的请求

@Controller
@RequestMapping("${server.error.path:${error.path:/error}}")
public class BasicErrorController extends AbstractErrorController {
    
    ......

    // 产生html类型的数据;浏览器发送的请求来到此方法处理
    @RequestMapping(produces = MediaType.TEXT_HTML_VALUE)
	public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) {
		HttpStatus status = getStatus(request);
		Map<String, Object> model = Collections
				.unmodifiableMap(getErrorAttributes(request, getErrorAttributeOptions(request, MediaType.TEXT_HTML)));
		response.setStatus(status.value());
        // 指定错误页面,并包含错误页面的地址和页面内容
		ModelAndView modelAndView = resolveErrorView(request, response, status, model);
		return (modelAndView != null) ? modelAndView : new ModelAndView("error", model);
	}

    // 产生json数据,其他客户端的请求来到此方法处理
	@RequestMapping
	public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
		HttpStatus status = getStatus(request);
		if (status == HttpStatus.NO_CONTENT) {
			return new ResponseEntity<>(status);
		}
		Map<String, Object> body = getErrorAttributes(request, getErrorAttributeOptions(request, MediaType.ALL));
		return new ResponseEntity<>(body, status);
	}
    
    ......
        
}

ModelAndView 方法中调用 resolveErrorView 方法进行页面响应。

protected ModelAndView resolveErrorView(HttpServletRequest request, HttpServletResponse response, HttpStatus status,
                                        Map<String, Object> model) {
    // 遍历所有的ErrorViewResolver得到ModelAndView
    for (ErrorViewResolver resolver : this.errorViewResolvers) {
        ModelAndView modelAndView = resolver.resolveErrorView(request, status, model);
        if (modelAndView != null) {
            return modelAndView;
        }
    }
    return null;
}

具体的页面指向由 DefaultErrorViewResolver 类解析得到,具体的实现方式请看下面关于 DefaultErrorViewResolver 类的说明。

③ ErrorPageCustomizer:配置错误页面跳转

private final DispatcherServletPath dispatcherServletPath;

protected ErrorPageCustomizer(ServerProperties properties, DispatcherServletPath dispatcherServletPath) {
    this.properties = properties;
    this.dispatcherServletPath = dispatcherServletPath;
}

ErrorPageCustomizer 类中为 dispatcherServletPath 赋值在注册错误信息相关的类中

@Override
public void registerErrorPages(ErrorPageRegistry errorPageRegistry) {
    ErrorPage errorPage = new ErrorPage(
        this.dispatcherServletPath.getRelativePath(this.properties.getError().getPath()));
    errorPageRegistry.addErrorPages(errorPage);
}

最终得到的请求路径为 /error

@Value("${error.path:/error}")
private String path = "/error";// 系统发生错误后来发送error请求进行处理,相当于以前在web.xml中的注册的错误规则

④ DefaultErrorViewResolver:配置默认的错误视图显示

image-20201028154250175

ErrorMvcAutoConfiguration 中返回 DefaultErrorViewResolver

@Bean
@ConditionalOnBean(DispatcherServlet.class)
@ConditionalOnMissingBean(ErrorViewResolver.class)
DefaultErrorViewResolver conventionErrorViewResolver() {
    return new DefaultErrorViewResolver(this.applicationContext, this.resourceProperties);
}

DefaultErrorViewResolver 类中定义响应的页面信息

public class DefaultErrorViewResolver implements ErrorViewResolver, Ordered {

    private static final Map<Series, String> SERIES_VIEWS;

    // 定义响应码
    static {
        Map<Series, String> views = new EnumMap<>(Series.class);
        views.put(Series.CLIENT_ERROR, "4xx");
        views.put(Series.SERVER_ERROR, "5xx");
        SERIES_VIEWS = Collections.unmodifiableMap(views);
    }

    ......

        @Override
        public ModelAndView resolveErrorView(HttpServletRequest request, HttpStatus status, Map<String, Object> model) {
        ModelAndView modelAndView = resolve(String.valueOf(status.value()), model);
        if (modelAndView == null && SERIES_VIEWS.containsKey(status.series())) {
            modelAndView = resolve(SERIES_VIEWS.get(status.series()), model);
        }
        return modelAndView;
    }

    private ModelAndView resolve(String viewName, Map<String, Object> model) {
        // SpringBoot默认找到一个页面? error/以状态码命名的页面(如:error/404)
        String errorViewName = "error/" + viewName;
        // 如果模板引擎可以解析这个页面地址则用模板引擎解析
        TemplateAvailabilityProvider provider = this.templateAvailabilityProviders.getProvider(errorViewName,
                                                                                               this.applicationContext);
        if (provider != null) {
            // 模板引擎可用的情况下返回errorViewName指定的视图地址
            return new ModelAndView(errorViewName, model);
        }
        // 模板引擎不可用,则在静态资源文件夹下寻找errorViewName对应的页面。如:error/404.html
        return resolveResource(errorViewName, model);
    }
}

产生错误信息后 ErrorMvcAutoConfiguration 的处理步骤:

  • 当系统中出现4xx或5xx类的错误;
  • ErrorPageCustomizer 就会生效(用于定制错误的响应规则的bean)设置发送 /error 请求;
  • /error 请求会被 BasicErrorController 处理,响应html页面或json数据;
  • 响应的html页面或json数据,由 DefaultErrorViewResolver 配置后返回;

2. 定制错误响应

2.1 如何定制错误页面

1)有模板引擎的情况下

error/状态码:将错误页面命名为 错误状态码.html 放在模板引擎文件夹里面的error文件夹内,发生状态码对应的错误时会进入到对应的页面。

我们也可以用4xx和5xx作为错误页面的文件名来匹配这种类型的所有错误,精确定位优先(即优先寻找和状态码对应的页面,找不到的时候才使用此类页面)

页面中能获取到的信息:

获取到的页面信息放到共享页面信息 DefaultErrorAttributes 类中

  • timestamp:时间戳
  • status:状态码
  • error:错误提示
  • exception:异常对象
  • message:JSR303数据校验错误都在这里

2)没有模板引擎的情况下

模板引擎中找不到这个错误页面,则在静态资源文件夹中找

3)以上都没有的错误页面

则使用SpringBoot默认的错误提示页面

2.2 如何定制错误的数据

1)自定义异常处理&返回定制的json数据

//没有自适应效果...
@ControllerAdvice
public class MyExceptionHandler {
    @ResponseBody
    @ExceptionHandler(UserNotExistException.class)
    public Map<String,Object> handleException(Exception e){
        Map<String,Object> map = new HashMap<>();
        map.put("code","user.notexist");
        map.put("message",e.getMessage());
        return map;
    }
}

2)转发到/error进行自适应响应效果处理

@ControllerAdvice
public class MyExceptionHandler {

    @ExceptionHandler(UserNotExistException.class)
    public String handleException(Exception e, HttpServletRequest request) {
        Map<String, Object> map = new HashMap<>();
        // 传入自定义的错误状态码   4xx  5xx
        request.setAttribute("javax.servlet.error.status_code",500);
        map.put("code", "user.notexist");
        map.put("message", "用户出错啦");

        request.setAttribute("ext", map);
        // 转发到/error页面,此时浏览器和其他客户端响应的内容不同,由BasicErrorController处理
        return "forward:/error";
    }
}

2.3 将定制的数据传递出去

出现异常时Spring Boot的处理步骤:

  • 当页面发生异常时,会发送/error请求;
  • 发送的/error请求会被 BasicErrorController 类处理;
  • 响应出去可以获取的数据由 getErrorAttributes 获取到(ErrorController 的实现类AbstractErrorController中规定的方法)

因此我们传递数据的方法:

① 书写 ErrorController 的实现类【或着编写 AbstractErrorController 的子类】注入到容器中;

② 页面上能用到的数据,或者json返回能用的数据都是通过 DefaultErrorAttributesgetErrorAttributes 方法获取到的,容器中的 DefaultErrorAttributes 默认是进行数据处理的,因此我们可以通过自定义 ErrorAttributes ,实现错误信息传递;

代码示例:

// 给容器中注入我们自定义的ErrorAttributes
@Component
public class MyErrorAttributes extends DefaultErrorAttributes {

    // 返回值的map就是页面和json能获取到的字段
    @Override
    public Map<String, Object> getErrorAttributes(WebRequest webRequest, ErrorAttributeOptions options) {
        Map<String, Object> map = super.getErrorAttributes(webRequest, options);
        map.put("author", "Bruce");

        // 我们异常处理器携带的数据
        Map<String, Object> ext = (Map<String, Object>) webRequest.getAttribute("ext", RequestAttributes.SCOPE_REQUEST);
        map.put("ext", ext);
        return map;
    }
}

最终效果:响应是自适应的,可以通过定制ErrorAttributes改变需要返回的内容

image-20201029113106610

(七)Servlet容器相关

SpringBoot默认使用的是嵌入式的servlet容器(内嵌Tomcat作为Servlet容器)

image-20201030103228149

1. 嵌入式Servlet容器

1.1 修改和定制嵌入式Servlet容器的相关配置

① 通过配置文件修改,修改和server有关的配置(ServerProperties 类中规定)

# server.xxx是server相关的配置
server.port=9083
server.servlet.context-path=/servlet

# server.tomcat.xxx是tomcat相关的配置
server.tomcat.uri-encoding=utf-8

② 通过实现 WebServerFactoryCustomizer 接口进行修改,WebServerFactoryCustomizer 是嵌入式servlet容器定制器,可以用来修改Servlet容器配置

@Component
public class CustomizationBean implements WebServerFactoryCustomizer<ConfigurableWebServerFactory> {
    @Override
    public void customize(ConfigurableWebServerFactory factory) {
        factory.setPort(9000);
    }
}

③ 通过 ConfigurableServletWebServerFactory 的子类定制配置

@Configuration
public class MyServerConfig {
    /**
     * 也可以将创建tomcat定制器示例,进行更多的配置
     * @return
     */
    @Bean
    public ConfigurableServletWebServerFactory webServerFactory() {

        TomcatServletWebServerFactory factory = new TomcatServletWebServerFactory();
        factory.setPort(9090);
        return factory;

    }

}

发现Spring Boot的配置执行优先级 @component > application.properties > @bean 不清楚是否准确还需要进一步的自动配置原理研究确定。

SpringBoot中对嵌入式Servlet容器的配置:Embedded Servlet Container Support

1.2 注册三大Servlet组件【Servlet、Filter、Listener】

SpringBoot默认采用jar包的方式启动嵌入式Servlet容器来启动SpringBoot的web引用,由于没有web.xml

文件,因此注册三大组件采用 @Bean 的方式进行配置。

① 注册Servlet组件,使用 ServletRegistrationBean

自定义Servlet

public class MyServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doPost(req,resp);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.getWriter().write("this is my servlet");
    }
}

在配置类中书写 ServletRegistrationBean 类型的配置Bean注册Servlet组件

@Bean
public ServletRegistrationBean myServlet() {

    ServletRegistrationBean servletRegistrationBean = new ServletRegistrationBean(new MyServlet(), "/myServlet");
    servletRegistrationBean.setLoadOnStartup(1);
    return servletRegistrationBean;
}

② 注册Filter组件

自定义Fliter

public class MyFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {

    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        System.out.println("MyFilter is running ......");
        filterChain.doFilter(servletRequest,servletResponse);
    }

    @Override
    public void destroy() {

    }
}

在配置类中书写 FilterRegistrationBean 类型的配置Bean注册Filter组件

@Bean
public FilterRegistrationBean myFilter() {
    FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
    filterRegistrationBean.setFilter(new MyFilter());
    filterRegistrationBean.setUrlPatterns(Arrays.asList("/test", "/myServlet"));
    return filterRegistrationBean;
}

③ 注册Listener组件

自定义Listener

public class MyListener implements ServletContextListener {

    @Override
    public void contextInitialized(ServletContextEvent sce) {
        System.out.println("contextInitialized web 应用启动。。。。。。");
    }

    @Override
    public void contextDestroyed(ServletContextEvent sce) {
        System.out.println("contextDestroyed web 应用销毁。。。。。。");
    }
}

在配置类中书写 ServletListenerRegistrationBean 类型的配置Bean注册Listener组件

@Bean
public ServletListenerRegistrationBean myListener() {
    ServletListenerRegistrationBean<MyListener> servletListenerRegistrationBean = new ServletListenerRegistrationBean<>(new MyListener());
    return servletListenerRegistrationBean;
}

SpringBoot在自动配置SpringMVC时,会自动注册SpringMVC前端控制器 DispatcherServlet ,通过DispatcherServletAutoConfiguration 类加载 DispatcherServlet

@Configuration(proxyBeanMethods = false)
@Conditional(DispatcherServletRegistrationCondition.class)
@ConditionalOnClass(ServletRegistration.class)
@EnableConfigurationProperties(WebMvcProperties.class)
@Import(DispatcherServletConfiguration.class)
protected static class DispatcherServletRegistrationConfiguration {

    @Bean(name = DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME)
    @ConditionalOnBean(value = DispatcherServlet.class, name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
    public DispatcherServletRegistrationBean dispatcherServletRegistration(DispatcherServlet dispatcherServlet,
                                                                           WebMvcProperties webMvcProperties, ObjectProvider<MultipartConfigElement> multipartConfig) {
        // 默认拦截:/,即所有的请求,包括静态资源,但不会拦截jsp请求。 /*会拦截jsp
        // 也可以通过在配置文件中设置spring.mvc.servlet.path修改SpringMVC前端控制器默认拦截请求路径
        DispatcherServletRegistrationBean registration = new DispatcherServletRegistrationBean(dispatcherServlet,
                                                                                               webMvcProperties.getServlet().getPath());
        registration.setName(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME);
        registration.setLoadOnStartup(webMvcProperties.getServlet().getLoadOnStartup());
        multipartConfig.ifAvailable(registration::setMultipartConfig);
        return registration;
    }

}

1.3 使用其他的嵌入式Servlet容器

SpringBoot默认使用Tomcat作为内置的Servlet容器,我们也可以切换为其他的Servlet容器,如:Jetty(适合于长连接)、Undertow(不支持jsp)

Spring Boot中默认支持的Servlet容器关系图

image-20201030163742990

SpringBoot中默认支持的内嵌式Servlet容器:

① Tomcat容器(默认使用)

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

② 使用Jetty作为Servlet容器

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <exclusions>
        <exclusion>
            <artifactId>spring-boot-starter-tomcat</artifactId>
            <groupId>org.springframework.boot</groupId>
        </exclusion>
    </exclusions>
</dependency>

<!--使用jetty作为Servlet容器-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-jetty</artifactId>
</dependency>

③ 使用Undertow作为Servlet容器

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <exclusions>
        <exclusion>
            <artifactId>spring-boot-starter-tomcat</artifactId>
            <groupId>org.springframework.boot</groupId>
        </exclusion>
    </exclusions>
</dependency>

<!--使用undertow作为Servlet容器-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-undertow</artifactId>
</dependency>

Tomcat Jetty Undertow三种Servlet容器对比介绍:Undertow,Tomcat和Jetty服务器配置详解与性能测试

1.4 嵌入式Servlet容器自动配置原理

EmbeddedWebServerFactoryCustomizerAutoConfiguration SpringBoot中嵌入式servlet容器的自动配置类,自动配置里中展示了SpringBoot支持的Servlet容器:Tomcat、Jetty、Undertow、Netty,默认使用的是Tomcat。根据pom文件导入的Starter来决定使用哪种Servlet容器。

@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication
@EnableConfigurationProperties(ServerProperties.class)
public class EmbeddedWebServerFactoryCustomizerAutoConfiguration {

	/**
	 * Nested configuration if Tomcat is being used.
	 */
	@Configuration(proxyBeanMethods = false)
    // 条件判断,判断是否导入了Tomcat依赖
	@ConditionalOnClass({ Tomcat.class, UpgradeProtocol.class })
	public static class TomcatWebServerFactoryCustomizerConfiguration {

		@Bean
		public TomcatWebServerFactoryCustomizer tomcatWebServerFactoryCustomizer(Environment environment,
				ServerProperties serverProperties) {
			return new TomcatWebServerFactoryCustomizer(environment, serverProperties);
		}

	}

	/**
	 * Nested configuration if Jetty is being used.
	 */
	@Configuration(proxyBeanMethods = false)
	@ConditionalOnClass({ Server.class, Loader.class, WebAppContext.class })
	public static class JettyWebServerFactoryCustomizerConfiguration {

		@Bean
		public JettyWebServerFactoryCustomizer jettyWebServerFactoryCustomizer(Environment environment,
				ServerProperties serverProperties) {
			return new JettyWebServerFactoryCustomizer(environment, serverProperties);
		}

	}

	/**
	 * Nested configuration if Undertow is being used.
	 */
	@Configuration(proxyBeanMethods = false)
	@ConditionalOnClass({ Undertow.class, SslClientAuthMode.class })
	public static class UndertowWebServerFactoryCustomizerConfiguration {

		@Bean
		public UndertowWebServerFactoryCustomizer undertowWebServerFactoryCustomizer(Environment environment,
				ServerProperties serverProperties) {
			return new UndertowWebServerFactoryCustomizer(environment, serverProperties);
		}

	}

	/**
	 * Nested configuration if Netty is being used.
	 */
	@Configuration(proxyBeanMethods = false)
	@ConditionalOnClass(HttpServer.class)
	public static class NettyWebServerFactoryCustomizerConfiguration {

		@Bean
		public NettyWebServerFactoryCustomizer nettyWebServerFactoryCustomizer(Environment environment,
				ServerProperties serverProperties) {
			return new NettyWebServerFactoryCustomizer(environment, serverProperties);
		}

	}

}

Servlet容器自动配置原理:

1)ServletWebServerFactory 是Web服务器定制器(Servlet定制工厂):创建Servlet容器,根据不同的类别创建对应的Servlet容器

@FunctionalInterface
public interface ServletWebServerFactory {

	/**
	 * Gets a new fully configured but paused {@link WebServer} instance. Clients should
	 * not be able to connect to the returned server until {@link WebServer#start()} is
	 * called (which happens when the {@code ApplicationContext} has been fully
	 * refreshed).
	 * @param initializers {@link ServletContextInitializer}s that should be applied as
	 * the server starts
	 * @return a fully configured and started {@link WebServer}
	 * @see WebServer#stop()
	 */
	WebServer getWebServer(ServletContextInitializer... initializers);

}

继承关系图

image-20201031095921744

2)WebServer 是Servlet容器类的父类

public interface WebServer {

	/**
	 * Starts the web server. Calling this method on an already started server has no
	 * effect.
	 * @throws WebServerException if the server cannot be started
	 */
	void start() throws WebServerException;

	/**
	 * Stops the web server. Calling this method on an already stopped server has no
	 * effect.
	 * @throws WebServerException if the server cannot be stopped
	 */
	void stop() throws WebServerException;

	/**
	 * Return the port this server is listening on.
	 * @return the port (or -1 if none)
	 */
	int getPort();

	/**
	 * Initiates a graceful shutdown of the web server. Handling of new requests is
	 * prevented and the given {@code callback} is invoked at the end of the attempt. The
	 * attempt can be explicitly ended by invoking {@link #stop}. The default
	 * implementation invokes the callback immediately with
	 * {@link GracefulShutdownResult#IMMEDIATE}, i.e. no attempt is made at a graceful
	 * shutdown.
	 * @param callback the callback to invoke when the graceful shutdown completes
	 * @since 2.3.0
	 */
	default void shutDownGracefully(GracefulShutdownCallback callback) {
		callback.shutdownComplete(GracefulShutdownResult.IMMEDIATE);
	}

}

继承关系图

image-20201031102313155

TomcatServletWebServerFactory 为例介绍Servlet容器的自动配置原理:

① 创建Tomcat容器的工厂 TomcatServletWebServerFactory

public class TomcatServletWebServerFactory extends AbstractServletWebServerFactory
    implements ConfigurableTomcatWebServerFactory, ResourceLoaderAware {

    ......

        @Override
        public WebServer getWebServer(ServletContextInitializer... initializers) {
        if (this.disableMBeanRegistry) {
            Registry.disableRegistry();
        }
        // 创建一个Tomcat
        Tomcat tomcat = new Tomcat();
        // 配置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);
        }
        prepareContext(tomcat.getHost(), initializers);
        // 将配置好的Tomcat传入到getTomcatWebServer方法中,返回Tomcat容器并启动容器
        return getTomcatWebServer(tomcat);
    }

    ......

		// 将配置好的Tomcat传入到TomcatWebServer中,创建Tomcat容器并返回
        protected TomcatWebServer getTomcatWebServer(Tomcat tomcat) {
        return new TomcatWebServer(tomcat, getPort() >= 0, getShutdown());
    }

    ......

}

TomcatWebServer 的构造器用于初始化Tomcat容器

public class TomcatWebServer implements WebServer {

    ......

        /**
	 * Create a new {@link TomcatWebServer} instance.
	 * @param tomcat the underlying Tomcat server
	 * @param autoStart if the server should be started
	 * @param shutdown type of shutdown supported by the server
	 * @since 2.3.0
	 */
        // 通过构造器初始化Tomcat容器
        public TomcatWebServer(Tomcat tomcat, boolean autoStart, Shutdown shutdown) {
        Assert.notNull(tomcat, "Tomcat Server must not be null");
        this.tomcat = tomcat;
        this.autoStart = autoStart;
        this.gracefulShutdown = (shutdown == Shutdown.GRACEFUL) ? new GracefulShutdown(tomcat) : null;
        initialize();
    }

    // 初始化Tomcat容器
    private void initialize() throws WebServerException {
        logger.info("Tomcat initialized with port(s): " + getPortsDescription(false));
        synchronized (this.monitor) {
            try {
                addInstanceIdToEngineName();

                Context context = findContext();
                context.addLifecycleListener((event) -> {
                    if (context.equals(event.getSource()) && Lifecycle.START_EVENT.equals(event.getType())) {
                        // Remove service connectors so that protocol binding doesn't
                        // happen when the service is started.
                        removeServiceConnectors();
                    }
                });

                // Start the server to trigger initialization listeners
                this.tomcat.start();

                // We can re-throw failure exception directly in the main thread
                rethrowDeferredStartupExceptions();

                try {
                    ContextBindings.bindClassLoader(context, context.getNamingToken(), getClass().getClassLoader());
                }
                catch (NamingException ex) {
                    // Naming is not enabled. Continue
                }

                // Unlike Jetty, all Tomcat threads are daemon threads. We create a
                // blocking non-daemon to stop immediate shutdown
                startDaemonAwaitThread();
            }
            catch (Exception ex) {
                stopSilently();
                destroySilently();
                throw new WebServerException("Unable to start embedded Tomcat", ex);
            }
        }
    }

    ......
}

3)修改嵌入式Servlet容器的配置

① 修改配置文件,即配置 ServerProperties 中的配置信息,通过读取配置信息配置Servlet容器

② 通过实现 WebServerFactoryCustomizer 接口,进行自定义Servlet容器定制

对Servlet容器修改配置原理的方式

4)Spring Boot中 ServletWebServerFactoryAutoConfiguration 类进行自动配置

@Configuration(proxyBeanMethods = false)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@ConditionalOnClass(ServletRequest.class)
@ConditionalOnWebApplication(type = Type.SERVLET)
@EnableConfigurationProperties(ServerProperties.class)
@Import({ ServletWebServerFactoryAutoConfiguration.BeanPostProcessorsRegistrar.class,
         ServletWebServerFactoryConfiguration.EmbeddedTomcat.class,
         ServletWebServerFactoryConfiguration.EmbeddedJetty.class,
         ServletWebServerFactoryConfiguration.EmbeddedUndertow.class })
// 导入后置处理器,导入内嵌式Servlet容器的相关配置和与Servlet容器相关的配置类
public class ServletWebServerFactoryAutoConfiguration {

    @Bean
    public ServletWebServerFactoryCustomizer servletWebServerFactoryCustomizer(ServerProperties serverProperties) {
        return new ServletWebServerFactoryCustomizer(serverProperties);
    }

    @Bean
    @ConditionalOnClass(name = "org.apache.catalina.startup.Tomcat")
    public TomcatServletWebServerFactoryCustomizer tomcatServletWebServerFactoryCustomizer(
        ServerProperties serverProperties) {
        return new TomcatServletWebServerFactoryCustomizer(serverProperties);
    }

    @Bean
    @ConditionalOnMissingFilterBean(ForwardedHeaderFilter.class)
    @ConditionalOnProperty(value = "server.forward-headers-strategy", havingValue = "framework")
    public FilterRegistrationBean<ForwardedHeaderFilter> forwardedHeaderFilter() {
        ForwardedHeaderFilter filter = new ForwardedHeaderFilter();
        FilterRegistrationBean<ForwardedHeaderFilter> registration = new FilterRegistrationBean<>(filter);
        registration.setDispatcherTypes(DispatcherType.REQUEST, DispatcherType.ASYNC, DispatcherType.ERROR);
        registration.setOrder(Ordered.HIGHEST_PRECEDENCE);
        return registration;
    }

    /**
	 * Registers a {@link WebServerFactoryCustomizerBeanPostProcessor}. Registered via
	 * {@link ImportBeanDefinitionRegistrar} for early registration.
	 */
    public static class BeanPostProcessorsRegistrar implements ImportBeanDefinitionRegistrar, BeanFactoryAware {

        private ConfigurableListableBeanFactory beanFactory;

        @Override
        public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
            if (beanFactory instanceof ConfigurableListableBeanFactory) {
                this.beanFactory = (ConfigurableListableBeanFactory) beanFactory;
            }
        }

        // 注册registerBeanDefinitions,调用后置处理器WebServerFactoryCustomizerBeanPostProcessor
        @Override
        public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata,
                                            BeanDefinitionRegistry registry) {
            if (this.beanFactory == null) {
                return;
            }
            registerSyntheticBeanIfMissing(registry, "webServerFactoryCustomizerBeanPostProcessor",
                                           WebServerFactoryCustomizerBeanPostProcessor.class);
            registerSyntheticBeanIfMissing(registry, "errorPageRegistrarBeanPostProcessor",
                                           ErrorPageRegistrarBeanPostProcessor.class);
        }

        private void registerSyntheticBeanIfMissing(BeanDefinitionRegistry registry, String name, Class<?> beanClass) {
            if (ObjectUtils.isEmpty(this.beanFactory.getBeanNamesForType(beanClass, true, false))) {
                RootBeanDefinition beanDefinition = new RootBeanDefinition(beanClass);
                beanDefinition.setSynthetic(true);
                registry.registerBeanDefinition(name, beanDefinition);
            }
        }

    }

}

WebServerFactoryCustomizerBeanPostProcessor web服务工厂定制组件的后置处理器,负责在bean初始化前执行初始化工作。从IoC容器中获取类型为 WebServerFactoryCustomizer 类型的组件。

后置处理器:bean初始化前后(创建完对象,还没进行赋值,执行初始化工作)

public class WebServerFactoryCustomizerBeanPostProcessor implements BeanPostProcessor, BeanFactoryAware {

    private ListableBeanFactory beanFactory;

    private List<WebServerFactoryCustomizer<?>> customizers;

    @Override
    public void setBeanFactory(BeanFactory beanFactory) {
        Assert.isInstanceOf(ListableBeanFactory.class, beanFactory,
                            "WebServerCustomizerBeanPostProcessor can only be used with a ListableBeanFactory");
        this.beanFactory = (ListableBeanFactory) beanFactory;
    }

    // 在初始化Bean之前
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        // 如果当前初始化的是WebServerFactory类型的组件
        if (bean instanceof WebServerFactory) {
            postProcessBeforeInitialization((WebServerFactory) bean);
        }
        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        return bean;
    }

    // 获取所有的定制器,调用每一个定制器的customize方法来给Servlet容器进行属性赋值;
    @SuppressWarnings("unchecked")
    private void postProcessBeforeInitialization(WebServerFactory webServerFactory) {
        LambdaSafe.callbacks(WebServerFactoryCustomizer.class, getCustomizers(), webServerFactory)
            .withLogger(WebServerFactoryCustomizerBeanPostProcessor.class)
            .invoke((customizer) -> customizer.customize(webServerFactory));
    }

    // 
    private Collection<WebServerFactoryCustomizer<?>> getCustomizers() {
        if (this.customizers == null) {
            // Look up does not include the parent context
            this.customizers = new ArrayList<>(getWebServerFactoryCustomizerBeans());
            this.customizers.sort(AnnotationAwareOrderComparator.INSTANCE);
            this.customizers = Collections.unmodifiableList(this.customizers);
        }
        return this.customizers;
    }

    // 获取定制的Servlet容器的组件
    @SuppressWarnings({ "unchecked", "rawtypes" })
    private Collection<WebServerFactoryCustomizer<?>> getWebServerFactoryCustomizerBeans() {
        return (Collection) this.beanFactory.getBeansOfType(WebServerFactoryCustomizer.class, false, false).values();
    }

}

例如:通过后置处理器获取得到 TomcatServletWebServerFactoryCustomizer 调用 customize() 定制方法,获取Servlet容器相关的配置类 serverProperties 进行自动配置

public class TomcatServletWebServerFactoryCustomizer
		implements WebServerFactoryCustomizer<TomcatServletWebServerFactory>, Ordered {

	private final ServerProperties serverProperties;

	public TomcatServletWebServerFactoryCustomizer(ServerProperties serverProperties) {
		this.serverProperties = serverProperties;
	}

	@Override
	public int getOrder() {
		return 0;
	}

    // 通过此方法定制Servlet容器
	@Override
	public void customize(TomcatServletWebServerFactory factory) {
        // 获取配置类serverProperties中的配置信息
		ServerProperties.Tomcat tomcatProperties = this.serverProperties.getTomcat();
		if (!ObjectUtils.isEmpty(tomcatProperties.getAdditionalTldSkipPatterns())) {
			factory.getTldSkipPatterns().addAll(tomcatProperties.getAdditionalTldSkipPatterns());
		}
		if (tomcatProperties.getRedirectContextRoot() != null) {
			customizeRedirectContextRoot(factory, tomcatProperties.getRedirectContextRoot());
		}
		customizeUseRelativeRedirects(factory, tomcatProperties.isUseRelativeRedirects());
		factory.setDisableMBeanRegistry(!tomcatProperties.getMbeanregistry().isEnabled());
	}

	private void customizeRedirectContextRoot(ConfigurableTomcatWebServerFactory factory, boolean redirectContextRoot) {
		factory.addContextCustomizers((context) -> context.setMapperContextRootRedirectEnabled(redirectContextRoot));
	}

	private void customizeUseRelativeRedirects(ConfigurableTomcatWebServerFactory factory,
			boolean useRelativeRedirects) {
		factory.addContextCustomizers((context) -> context.setUseRelativeRedirects(useRelativeRedirects));
	}

}

嵌入式Servlet容器自动配置原理总结:

① SpringBoot根据导入的依赖情况,自动给容器中添加对应的 WebServerFactoryCustomizer 组件(web服务定制器)

② 当容器中某个组件创建对象时会触发后置处理器 WebServerFactoryCustomizerBeanPostProcessor

WebServerFactoryCustomizerBeanPostProcessor (Web工厂定制组件后置处理器)获取所有服务工厂定制组件(即实现 WebServerFactoryCustomizer 的接口,自定义的定制器组件)依次调用 customize 定制接口,定制Servlet容器配置;

④ 嵌入式Servlet容器工厂创建tomcat容器,初始化并启动Servlet容器

1.5 嵌入式Servlet容器启动原理

嵌入式Servlet容器工厂创建的时机以及获取嵌入式Servlet容器并启动Tomcat

获取嵌入式Servlet容器工厂:

1)Spring Boot应用通过main方法入口启动,并运行run方法;

2)调用 Application 类中的 run 方法,调用 refreshContext 方法刷新容器【创建IoC容器对象并初始化,创建容器中的各个组件对象】,如果是web应用则创建 AnnotationConfigServletWebServerApplicationContext 容器,否则创建默认IoC容器 AnnotationConfigApplicationContext 容器

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);
        // 创建IoC容器,根据不同的环境创建不同的IoC容器
        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;
}

关于Spring中IoC容器还有很多的内容,会在后续写博客详细介绍的。

3)调用 refresh 方法刷新容器,经过一系列的调用后达到 AbstractApplicationContext 类中的 refresh 方法。

@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.
        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.
            // 在此处刷新IoC容器
            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();
        }
    }
}

4)AbstractApplicationContext 类中的 onRefresh 方法刷新IoC容器,其中web的IoC容器重写了 onRefresh 方法,调用的时候通过父类进行多态调用。

5)调用 ServletWebServerApplication 类中 createWebServer 方法,让web的IoC创建嵌入式Servlet容器

private void createWebServer() {
    WebServer webServer = this.webServer;
    ServletContext servletContext = getServletContext();
    if (webServer == null && servletContext == null) {
        // 获取嵌入式的Servlet工厂
        ServletWebServerFactory factory = getWebServerFactory();
        this.webServer = factory.getWebServer(getSelfInitializer());
        getBeanFactory().registerSingleton("webServerGracefulShutdown",
                                           new WebServerGracefulShutdownLifecycle(this.webServer));
        getBeanFactory().registerSingleton("webServerStartStop",
                                           new WebServerStartStopLifecycle(this, this.webServer));
    }
    else if (servletContext != null) {
        try {
            getSelfInitializer().onStartup(servletContext);
        }
        catch (ServletException ex) {
            throw new ApplicationContextException("Cannot initialize servlet context", ex);
        }
    }
    initPropertySources();
}

6)调用 ServletWebServerApplication 类中 getWebServerFactory 方法创建Servlet创建工厂

protected ServletWebServerFactory getWebServerFactory() {
    // Use bean names so that we don't consider the hierarchy
    // 通过getBean方法从IoC容器中获取ServletWebServerFactory组件
    String[] 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);
}

getWebServerFactory 方法中通过 getBeanFactory().getBeanNamesForType(ServletWebServerFactory.class); 从IoC容器中获取 ServletWebServerFactory 组件。我们通过debug调试可以看到在IoC容器中获取 tomcatServletWebServerFactory 组件。我们在SpringBoot自动配置类: ServletWebServerFactoryAutoConfiguration 类中,注入了 tomcatServletWebServerFactory 组件

image-20201102104832599

我们在得到 tomcatServletWebServerFactory 组件后,就进入到了 tomcatServletWebServerFactory 类中创建Servlet容器对象此时会触发后置处理器 WebServerFactoryCustomizerBeanPostProcessor,后置处理器会获取所有的定制器来定制Servlet容器类的相关配置。

7)通过容器工厂获取嵌入式的Servlet容器,即通过 tomcatServletWebServerFactory 类创建 tomcatWebServer 类;

8)创建嵌入式Servlet容器,并启动容器。tomcatWebServer 类中包含对tomcat容器的相关配置和启动方法。

首先启动嵌入式的Servlet容器,再创建IoC容器中的其他组件,即IoC容器在启动的时候就创建了Servlet容器。

本章中涉及到的IoC容器相关的内容并没有详细说明,会在后续的博文中详细说明的。

2. 外置Servlet容器

  • 嵌入式Servlet容器:应用打包成可执行的jar包,通过执行jar包启动应用。
    • 优点:启动简单,不同的嵌入式Servlet容器间切换方便,且不用安装外部的Servlet容器
    • 缺点:不支持jsp,优化定制比较复杂,通过配置文件进行配置或定制Servlet创建工厂 WebServerFactoryCustomizer
  • 外置的Servlet容器:将应用打包成war包,通过外置的Servlet容器,如:使用安装配置好的Tomcat运行项目打包后的war包

2.1 外置Servlet容器使用方法

① 设置项目的打包方式设置为war包;

② 设置项目不使用内置的Servlet容器;

③ 必须编写 SpringBootServletInitializer 的实现类,并实现 configure 方法;

④ 通过外部的Servlet容器,启动war包;

具体的使用操作:

① 设置项目的打包方式设置为war包;(注意创建webapp的目录结构)

使用SpringBoot项目创建工具时指定,或修改项目的 pom.xml 文件

<packaging>war</packaging>

② 设置项目不使用内置的Servlet容器;

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-tomcat</artifactId>
    <scope>provided</scope>
</dependency>

③ 必须编写 SpringBootServletInitializer 的实现类,并实现 configure 方法;

public class ServletInitializer extends SpringBootServletInitializer {

    @Override
    protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
        // 调用configure方法,传入SpringBoot应用主程序
        return application.sources(Springboot04JspApplication.class);
    }

}

④ 通过外部的Servlet容器,启动war包;

关于在IDEA中配置Tomcat服务器,请查看博文:SpringBoot项目修改为外置Tomcat启动

2.2 外置Servlet容器启动原理

不同打包方式的执行步骤:

  • jar包:执行SpringBoot主类的main方法,创建并启动IoC容器,创建嵌入式Servlet容器;
  • war包:启动服务器,服务器启动SpringBoot应用【SpringBootServletInitializer】,创建并启动IoC容器;

Servlet 3.1 Shared libraries / runtimes pluggability 的规范:

  • 服务器启动时(web应用启动时)会创建当前web应用内每个jar包里的 ServletContainerInitializer 实例;
  • ServletContainerInitializer 的实现类必须放在jar包的 META—INF/services 文件夹内,包含一个名为 javax.servlet.ServletContainerInitializer 的文件,内容为 ServletContainerInitializer 的实现类的全类名;
  • 还可以使用 @HandlesTypes 注解,在启动应用时加载我们感兴趣的类;

流程:

① 启动Tomcat服务器;

org\springframework\spring-web\5.2.10.RELEASE\spring-web-5.2.10.RELEASE.jar!\META-INF\services\javax.servlet.ServletContainerInitializer Spring的web模块中包含的文件,文件的内容:org.springframework.web.SpringServletContainerInitializer,即:应用启动时需要创建这个类;

SpringServletContainerInitializer@HandlesTypes(WebApplicationInitializer.class) 注解标注的所有 WebApplicationInitializer 类型的类都传入到 onStartup 方法中 ,为这些 WebApplicationInitializer 类型的类创建实例;

@HandlesTypes(WebApplicationInitializer.class)
public class SpringServletContainerInitializer implements ServletContainerInitializer {

    @Override
    public void onStartup(@Nullable Set<Class<?>> webAppInitializerClasses, ServletContext servletContext)
        throws ServletException {

        List<WebApplicationInitializer> initializers = new LinkedList<>();

        if (webAppInitializerClasses != null) {
            for (Class<?> waiClass : webAppInitializerClasses) {
                // Be defensive: Some servlet containers provide us with invalid classes,
                // no matter what @HandlesTypes says...
                if (!waiClass.isInterface() && !Modifier.isAbstract(waiClass.getModifiers()) &&
                    WebApplicationInitializer.class.isAssignableFrom(waiClass)) {
                    try {
                        // 为这些WebApplicationInitializer类型的类创建实例
                        initializers.add((WebApplicationInitializer)
                                         ReflectionUtils.accessibleConstructor(waiClass).newInstance());
                    }
                    catch (Throwable ex) {
                        throw new ServletException("Failed to instantiate WebApplicationInitializer class", ex);
                    }
                }
            }
        }

        if (initializers.isEmpty()) {
            servletContext.log("No Spring WebApplicationInitializer types detected on classpath");
            return;
        }

        servletContext.log(initializers.size() + " Spring WebApplicationInitializers detected on classpath");
        AnnotationAwareOrderComparator.sort(initializers);
        for (WebApplicationInitializer initializer : initializers) {
            // 每一个WebApplicationInitializer类型的类都调用自己的 `onStartup` 方法;
            initializer.onStartup(servletContext);
        }
    }

}

④ 每一个 WebApplicationInitializer 类型的类都调用自己的 onStartup 方法;

WebApplicationInitializer 接口的继承关系图

⑤ 相当于我们的 SpringBootServletInitializer 实现类会被创建对象,并执行 onStartup 方法;

SpringBootServletInitializer 实例执行 onStartup 方法时会调用 createRootApplicationContext 方法创建IoC容器;

createRootApplicationContext 方法中调用run方法启动SpringBoot应用并创建IoC容器

SpringBootServletInitializer 类源码:

public abstract class SpringBootServletInitializer implements WebApplicationInitializer {

    ......

        // 执行onStartup方法
        @Override
        public void onStartup(ServletContext servletContext) throws ServletException {
        // Logger initialization is deferred in case an ordered
        // LogServletContextInitializer is being used
        this.logger = LogFactory.getLog(getClass());
        // 创建IoC容器
        WebApplicationContext rootApplicationContext = createRootApplicationContext(servletContext);
        if (rootApplicationContext != null) {
            servletContext.addListener(new SpringBootContextLoaderListener(rootApplicationContext, servletContext));
        }
        else {
            this.logger.debug("No ContextLoaderListener registered, as createRootApplicationContext() did not "
                              + "return an application context");
        }
    }

    ......

        // 创建IoC容器的方法
        protected WebApplicationContext createRootApplicationContext(ServletContext servletContext) {
        // 创建SpringApplicationBuilder
        SpringApplicationBuilder builder = createSpringApplicationBuilder();
        builder.main(getClass());
        ApplicationContext parent = getExistingRootWebApplicationContext(servletContext);
        if (parent != null) {
            this.logger.info("Root context already created (using as parent).");
            servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, null);
            builder.initializers(new ParentContextApplicationContextInitializer(parent));
        }
        builder.initializers(new ServletContextApplicationContextInitializer(servletContext));
        builder.contextClass(AnnotationConfigServletWebServerApplicationContext.class);
        // 调用configure方法,子类重写此方法,将SpringBoot的主程序传入
        builder = configure(builder);
        builder.listeners(new WebEnvironmentPropertySourceInitializer(servletContext));
        // 调用build方法创建一个Spring应用
        SpringApplication application = builder.build();
        if (application.getAllSources().isEmpty()
            && MergedAnnotations.from(getClass(), SearchStrategy.TYPE_HIERARCHY).isPresent(Configuration.class)) {
            application.addPrimarySources(Collections.singleton(getClass()));
        }
        Assert.state(!application.getAllSources().isEmpty(),
                     "No SpringApplication sources have been defined. Either override the "
                     + "configure method or add an @Configuration annotation");
        // Ensure error pages are registered
        if (this.registerErrorPageFilter) {
            application.addPrimarySources(Collections.singleton(ErrorPageFilterConfiguration.class));
        }
        application.setRegisterShutdownHook(false);
        // 调用run方法启动SpringBoot应用
        return run(application);
    }

    ......

        // configure方法,子类实现后传入主启动类
        protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
        return builder;
    }

    ......


}

因此通过外置Servlet容器启动SpringBoot应用的过程是:先启动外置Servlet容器,再启动SpringBoot应用。

关于Spring中的IoC容器相关的内容,会再写文章进行详细讲解的。这一篇博文比较长,能看到这里的小伙伴都很棒啊ヾ(≧▽≦*)o