1 简介

  • 使用 SpringBoot 步骤:
    • ① 创建 SpringBoot 应用,选中我们需要的模块。
    • ② SpringBoot 已经默认将这些场景配置好了,只需要在配置文件指定少量配置就可以运行起来。
    • ③ 自己编写业务逻辑代码。
  • 自动配置原理:这个场景 SpringBoot 帮我们配置了什么?能不能修改?能修改哪些配置?能不能扩展?
  1. xxxAutoConfiguration:帮我们在容器中自动配置组件。
  2. xxxProperties:配置类封装配置文件的内容。

2 SpringBoot 对静态资源的映射规则

  • WebMvcAutoConfiguration 的部分源码如下:
  1. @Configuration
  2. @ConditionalOnWebApplication
  3. @ConditionalOnClass({ Servlet.class, DispatcherServlet.class,
  4. WebMvcConfigurerAdapter.class })
  5. @ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
  6. @AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
  7. @AutoConfigureAfter({ DispatcherServletAutoConfiguration.class,
  8. ValidationAutoConfiguration.class })
  9. public class WebMvcAutoConfiguration {
  10. @Configuration
  11. @Import(EnableWebMvcConfiguration.class)
  12. @EnableConfigurationProperties({ WebMvcProperties.class, ResourceProperties.class })
  13. public static class WebMvcAutoConfigurationAdapter extends WebMvcConfigurerAdapter {
  14. //静态资源处理器
  15. @Override
  16. public void addResourceHandlers(ResourceHandlerRegistry registry) {
  17. if (!this.resourceProperties.isAddMappings()) {
  18. logger.debug("Default resource handling disabled");
  19. return;
  20. }
  21. Integer cachePeriod = this.resourceProperties.getCachePeriod();
  22. if (!registry.hasMappingForPattern("/webjars/**")) {
  23. customizeResourceHandlerRegistration(
  24. registry.addResourceHandler("/webjars/**")
  25. .addResourceLocations(
  26. "classpath:/META-INF/resources/webjars/")
  27. .setCachePeriod(cachePeriod));
  28. }
  29. String staticPathPattern = this.mvcProperties.getStaticPathPattern();
  30. if (!registry.hasMappingForPattern(staticPathPattern)) {
  31. customizeResourceHandlerRegistration(
  32. registry.addResourceHandler(staticPathPattern)
  33. .addResourceLocations(
  34. this.resourceProperties.getStaticLocations())
  35. .setCachePeriod(cachePeriod));
  36. }
  37. }
  38. //欢迎页
  39. @Bean
  40. public WelcomePageHandlerMapping welcomePageHandlerMapping(
  41. ResourceProperties resourceProperties) {
  42. return new WelcomePageHandlerMapping(resourceProperties.getWelcomePage(),
  43. this.mvcProperties.getStaticPathPattern());
  44. }
  45. //配置喜欢的图标
  46. @Configuration
  47. @ConditionalOnProperty(value = "spring.mvc.favicon.enabled", matchIfMissing = true)
  48. public static class FaviconConfiguration {
  49. private final ResourceProperties resourceProperties;
  50. public FaviconConfiguration(ResourceProperties resourceProperties) {
  51. this.resourceProperties = resourceProperties;
  52. }
  53. @Bean
  54. public SimpleUrlHandlerMapping faviconHandlerMapping() {
  55. SimpleUrlHandlerMapping mapping = new SimpleUrlHandlerMapping();
  56. mapping.setOrder(Ordered.HIGHEST_PRECEDENCE + 1);
  57. //所有**/favicon.ico
  58. mapping.setUrlMap(Collections.singletonMap("**/favicon.ico",
  59. faviconRequestHandler()));
  60. return mapping;
  61. }
  62. @Bean
  63. public ResourceHttpRequestHandler faviconRequestHandler() {
  64. ResourceHttpRequestHandler requestHandler = new ResourceHttpRequestHandler();
  65. requestHandler
  66. .setLocations(this.resourceProperties.getFaviconLocations());
  67. return requestHandler;
  68. }
  69. }
  70. }
  71. }
  • ① 所有 /webjars/** ,都去 classpath:/META-INF/resources/webjars/ 找静态资源。

    • webjars:以 jar 包的形式引入静态资源。
    • 官网
    • 比如:浏览器发送 http://localhost:8080/webjars/jquery/3.5.1/jquery.js 请求,SpringBoot 就会到 classpath:/META-INF/resources/webjars/jquery/3.5.1/jquery.js 这个路径查找 jquery.js 文件。
      1. <dependency>
      2. <groupId>org.webjars</groupId>
      3. <artifactId>jquery</artifactId>
      4. <version>3.5.1</version>
      5. </dependency>
      webjars.png
  • /** 访问当前项目的任何资源(静态资源的文件夹):

    1. "classpath:/META-INF/resources/"
    2. "classpath:/resources/"
    3. "classpath:/static/"
    4. "classpath:/public/"
    5. "/"
    • 比如:浏览器发送 http://localhost:8080/asserts/css/bootstrap.min.css 请求,SpringBoot 就会到静态资源文件夹中寻找 /asserts/css/bootstrap.min.css 的路径去寻找 bootstrap.min.css 文件。

访问当前项目的任何资源.png

  • ③ 欢迎页,静态资源文件夹下的 index.html 页面,被 /** 映射。
  • 比如:浏览器发送 http://localhost:8080 请求,默认情况下,会去静态资源文件夹下找 index.html 页面。
  • ④ 所有的 **/favicon.ico 都是在静态资源文件夹下寻找。

3 模板引擎

3.1 简介

  • JSP 、Velocity 、Freemarker 、Thymeleaf……都是模板引擎。
  • 模板引擎的原理:

模板引擎.jpg

3.2 Thymeleaf

3.2.1 Thymeleaf 概述

  • SpringBoot 推荐 Thymeleaf 。
  • Thymeleaf 语法更简单,功能更强大。

3.2.2 Thymeleaf 准备

  • 导入 thymeleaf 相关 jar 包的 Maven 坐标:
  1. <properties>
  2. <thymeleaf.version>3.0.9.RELEASE</thymeleaf.version>
  3. <thymeleaf-layout-dialect.version>2.3.0</thymeleaf-layout-dialect.version>
  4. </properties>
  5. <dependency>
  6. <groupId>org.springframework.boot</groupId>
  7. <artifactId>spring-boot-starter-thymeleaf</artifactId>
  8. </dependency>
  • ThymeleafProperties:
  1. @ConfigurationProperties(prefix = "spring.thymeleaf")
  2. public class ThymeleafProperties {
  3. private static final Charset DEFAULT_ENCODING = Charset.forName("UTF-8");
  4. private static final MimeType DEFAULT_CONTENT_TYPE = MimeType.valueOf("text/html");
  5. public static final String DEFAULT_PREFIX = "classpath:/templates/";
  6. public static final String DEFAULT_SUFFIX = ".html";
  7. //略
  8. }
  • 从 ThymeleafProperties 的源码可知:我们只要将 HTML 页面放在 classpath:/templates/ ,Thymeleaf 就能自动渲染。

3.2.3 Thymeleaf 使用

  • 导入 thymeleaf 的名称空间:
  1. <html lang="en" xmlns:th="http://www.thymeleaf.org">
  • 使用 thymeleaf 语法:
  1. <!DOCTYPE html>
  2. <html lang="en" xmlns:th="http://www.thymeleaf.org">
  3. <head>
  4. <meta charset="UTF-8">
  5. <title>Title</title>
  6. </head>
  7. <body>
  8. <div th:text="helloworld">哇,好(*^▽^*)</div>
  9. </body>
  10. </html>

3.2.4 Thymeleaf 语法规则

文本

  • th:text:改变当前元素里面的文本内容。
  • th:任意html属性:来替换原生属性的值。
  • th:insertth:replace:片段包含。
  • th:each:遍历。
  • th:ifth:unlessth:case:条件判断。
  • th:objectth:with:声明变量。
  • th:attrth:attrprependth:attrappend:任意属性修改。
  • th:valueth:hrefth:src:修改指定属性默认值。
  • th:fragment:声明片段。
  • th"remove:移除。

表达式

  1. Simple expressions:(表达式语法)
  2. Variable Expressions: ${...}:获取变量值;OGNL
  3. 1)、获取对象的属性、调用方法
  4. 2)、使用内置的基本对象:
  5. #ctx : the context object.
  6. #vars: the context variables.
  7. #locale : the context locale.
  8. #request : (only in Web Contexts) the HttpServletRequest object.
  9. #response : (only in Web Contexts) the HttpServletResponse object.
  10. #session : (only in Web Contexts) the HttpSession object.
  11. #servletContext : (only in Web Contexts) the ServletContext object.
  12. ${session.foo}
  13. 3)、内置的一些工具对象:
  14. #execInfo : information about the template being processed.
  15. #messages : methods for obtaining externalized messages inside variables expressions, in the same way as they would be obtained using #{…} syntax.
  16. #uris : methods for escaping parts of URLs/URIs
  17. #conversions : methods for executing the configured conversion service (if any).
  18. #dates : methods for java.util.Date objects: formatting, component extraction, etc.
  19. #calendars : analogous to #dates , but for java.util.Calendar objects.
  20. #numbers : methods for formatting numeric objects.
  21. #strings : methods for String objects: contains, startsWith, prepending/appending, etc.
  22. #objects : methods for objects in general.
  23. #bools : methods for boolean evaluation.
  24. #arrays : methods for arrays.
  25. #lists : methods for lists.
  26. #sets : methods for sets.
  27. #maps : methods for maps.
  28. #aggregates : methods for creating aggregates on arrays or collections.
  29. #ids : methods for dealing with id attributes that might be repeated (for example, as a result of an iteration).
  30. Selection Variable Expressions: *{...}:选择表达式:和${}在功能上是一样;
  31. 补充:配合 th:object="${session.user}:
  32. <div th:object="${session.user}">
  33. <p>Name: <span th:text="*{firstName}">Sebastian</span>.</p>
  34. <p>Surname: <span th:text="*{lastName}">Pepper</span>.</p>
  35. <p>Nationality: <span th:text="*{nationality}">Saturn</span>.</p>
  36. </div>
  37. Message Expressions: #{...}:获取国际化内容
  38. Link URL Expressions: @{...}:定义URL;
  39. @{/order/process(execId=${execId},execType='FAST')}
  40. Fragment Expressions: ~{...}:片段引用表达式
  41. <div th:insert="~{commons :: main}">...</div>
  42. Literals(字面量)
  43. Text literals: 'one text' , 'Another one!' ,…
  44. Number literals: 0 , 34 , 3.0 , 12.3 ,…
  45. Boolean literals: true , false
  46. Null literal: null
  47. Literal tokens: one , sometext , main ,…
  48. Text operations:(文本操作)
  49. String concatenation: +
  50. Literal substitutions: |The name is ${name}|
  51. Arithmetic operations:(数学运算)
  52. Binary operators: + , - , * , / , %
  53. Minus sign (unary operator): -
  54. Boolean operations:(布尔运算)
  55. Binary operators: and , or
  56. Boolean negation (unary operator): ! , not
  57. Comparisons and equality:(比较运算)
  58. Comparators: > , < , >= , <= ( gt , lt , ge , le )
  59. Equality operators: == , != ( eq , ne )
  60. Conditional operators:条件运算(三元运算符)
  61. If-then: (if) ? (then)
  62. If-then-else: (if) ? (then) : (else)
  63. Default: (value) ?: (defaultvalue)
  64. Special tokens:
  65. No-Operation: _

4 SpringMVC 自动配置

4.1 SpringMVC 自动配置

  • SpringBoot 自动配置好了 SpringMVC ,下面 SpringBoot 对 SpringMVC 的默认配置( WebMvcAutoConfiguration ):

    • ① Inclusion of ContentNegotiatingViewResolver and BeanNameViewResolver beans。
      • 自动配置了 ViewResolver(视图解析器,根据方法的返回值得到视图对象(View),视图对象决定如何渲染(转发?重定向?))。
      • ContentNegotiatingViewResolver :组合所有的视图解析器。
      • 如何定制?我们可以自己给容器中添加一个视图解析器,自动将其组合进来。
    • ② Support for serving static resources, including support for WebJars (see below)。
      • 支持静态资源文件夹路径和 webjars 。
    • ③ Static index.html support.。
      • 首页访问。
    • ④ Custom Favicon support (see below)。
      • favicon.ico。
    • ⑤ 自动注册了 of Converter, GenericConverter, Formatter beans。

      • Converter:转换器。
      • Formatter :格式化器。

        1. @Bean
        2. @ConditionalOnProperty(prefix = "spring.mvc", name = "date-format") //在配置文件中配置日期格式化的规则
        3. public Formatter<Date> dateFormatter() {
        4. return new DateFormatter(this.mvcProperties.getDateFormat()); //日期格式化组件
        5. }
      • 我们自己添加的格式化器、转换器只需要放在容器中即可。

        1. @Override
        2. public void addFormatters(FormatterRegistry registry) {
        3. for (Converter<?, ?> converter : getBeansOfType(Converter.class)) {
        4. registry.addConverter(converter);
        5. }
        6. for (GenericConverter converter : getBeansOfType(GenericConverter.class)) {
        7. registry.addConverter(converter);
        8. }
        9. for (Formatter<?> formatter : getBeansOfType(Formatter.class)) {
        10. registry.addFormatter(formatter);
        11. }
        12. }
    • ⑥ Support for HttpMessageConverters (see below)。

      • HttpMessageConverter:SpringMVC 用来转换 Http 的请求和响应的。比如 JavaBean 和 JSON 之间的转换。
      • HttpMessageConverters 是从容器中获取的,并且获取所有的 HttpMessageConverter 。
      • 自己给容器中添加 HttpMessageConverter ,只需要自己将组件注册到容器中。
    • ⑦ Automatic registration of MessageCodesResolver (see below)。
      • 定义错误代码生成规则。
    • ⑧ Automatic use of a ConfigurableWebBindingInitializer bean (see below)。
      • 我们可以配置一个 ConfigurableWebBindingInitializer 来替换默认的。
    • ConfigurableWebBindingInitializer的作用就是初始化所有的WebDataBinder,而WebDataBinder的作用就是请求数据—>JavaBean。
  • ⑨ If you want to keep Spring Boot MVC features, and you just want to add additional MVC configuration (interceptors, formatters, view controllers etc.) you can add your own @Configuration class of type WebMvcConfigurerAdapter, but without @EnableWebMvc. If you wish to provide custom instances of RequestMappingHandlerMapping, RequestMappingHandlerAdapter or ExceptionHandlerExceptionResolver you can declare a WebMvcRegistrationsAdapter instance providing such components.
    If you want to take complete control of Spring MVC, you can add your own @Configuration annotated with @EnableWebMvc.

4.2 扩展 SpringMVC

  • 以前的 springmvc.xml 中配置拦截器等:
  1. <mvc:view-controller path="/hello" view-name="success"/>
  2. <mvc:interceptors>
  3. <mvc:interceptor>
  4. <mvc:mapping path="/hello"/>
  5. <bean></bean>
  6. </mvc:interceptor>
  7. </mvc:interceptors>
  • 现在在 SpringBoot中 ,编写一个配置类(用 @Configuration 注解标注的类),并且继承 WebMvcConfigurerAdapter,但是 不要标注@EnableWebMvc注解 。这样既可以保留自动配置功能,又能使用我们自己扩展的配置。
  1. package com.sunxiaping.springboot.config;
  2. import org.springframework.context.annotation.Configuration;
  3. import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
  4. import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
  5. @Configuration
  6. public class SpringMvcConfig extends WebMvcConfigurerAdapter {
  7. @Override
  8. public void addViewControllers(ViewControllerRegistry registry) {
  9. registry.addViewController("/index").setViewName("index");
  10. registry.addViewController("/").setViewName("index");
  11. registry.addViewController("/index.html").setViewName("index");
  12. registry.addViewController("/index.htm").setViewName("index");
  13. }
  14. }
  • 原理:

    • ① WebMvcAutoConfiguration 是 SpringMVC 的自动配置类。
    • ② 在做其他自动配置的时候,会导入 @Import(EnableWebMvcConfiguration.class) 。

      1. @Configuration
      2. @ConditionalOnWebApplication
      3. @ConditionalOnClass({ Servlet.class, DispatcherServlet.class,
      4. WebMvcConfigurerAdapter.class })
      5. @ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
      6. @AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
      7. @AutoConfigureAfter({ DispatcherServletAutoConfiguration.class,
      8. ValidationAutoConfiguration.class })
      9. public class WebMvcAutoConfiguration {
      10. @Configuration
      11. @Import(EnableWebMvcConfiguration.class)
      12. @EnableConfigurationProperties({ WebMvcProperties.class, ResourceProperties.class })
      13. public static class WebMvcAutoConfigurationAdapter extends WebMvcConfigurerAdapter {
      14. //其他略
      15. }
      16. @Configuration
      17. public static class EnableWebMvcConfiguration extends DelegatingWebMvcConfiguration {
      18. }
      19. //其他略
      20. }
    • ③ DelegatingWebMvcConfiguration 的部分源码如下: ```java @Configuration public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport {

      private final WebMvcConfigurerComposite configurers = new WebMvcConfigurerComposite();

      //会将所有的WebMvcConfigurer都导入到容器中 @Autowired(required = false) public void setConfigurers(List configurers) { if (!CollectionUtils.isEmpty(configurers)) {

      1. this.configurers.addWebMvcConfigurers(configurers);

      } }

      //循环遍历所有的WebMvcConfigurer,并将每个WebMvcConfigurer配置的ViewController加入到容器中 //这样就可以将所有的WebMvcConfigurer的相关配置一起作用 @Override protected void addViewControllers(ViewControllerRegistry registry) { this.configurers.addViewControllers(registry); }

      //其他略

}

  1. - 容器中的所有 WebMvcConfigurer 都会一起起作用。
  2. - 我们自己配置类也会被调用。
  3. <a name="ebd444af"></a>
  4. ## 4.3 全面接管 SpringMVC
  5. - SpringBoot SpringMVC 的自动配置不需要了,所有的都需要我们配置。
  6. - 只需要在配置类中加 `@EnableWebMvc` 注解即可。
  7. ```java
  8. package com.sunxiaping.springboot.config;
  9. import org.springframework.context.annotation.Configuration;
  10. import org.springframework.web.servlet.config.annotation.EnableWebMvc;
  11. import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
  12. import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
  13. @Configuration
  14. @EnableWebMvc
  15. public class SpringMvcConfig extends WebMvcConfigurerAdapter {
  16. @Override
  17. public void addViewControllers(ViewControllerRegistry registry) {
  18. registry.addViewController("/index").setViewName("index");
  19. registry.addViewController("/").setViewName("index");
  20. registry.addViewController("/index.html").setViewName("index");
  21. registry.addViewController("/index.htm").setViewName("index");
  22. }
  23. }
  • 原理如下:

    • @EnableWebMvc 注解源码如下:

      1. @Retention(RetentionPolicy.RUNTIME)
      2. @Target(ElementType.TYPE)
      3. @Documented
      4. @Import(DelegatingWebMvcConfiguration.class) //会向容器中导入DelegatingWebMvcConfiguration组件
      5. public @interface EnableWebMvc {
      6. }
    • DelegatingWebMvcConfiguration 的部分源码如下:

      1. @Configuration
      2. public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport {
      3. //其他略
      4. }
    • WebMvcAutoConfiguration 的部分源码如下:

      1. @Configuration
      2. @ConditionalOnWebApplication
      3. @ConditionalOnClass({ Servlet.class, DispatcherServlet.class,
      4. WebMvcConfigurerAdapter.class })
      5. @ConditionalOnMissingBean(WebMvcConfigurationSupport.class) //只有在容器中没有WebMvcConfigurationSupport组件的时候,WebMvcAutoConfiguration才会起效,而@EnableWebMvc会将WebMvcConfigurationSupport组件导入到容器中
      6. @AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
      7. @AutoConfigureAfter({ DispatcherServletAutoConfiguration.class,
      8. ValidationAutoConfiguration.class })
      9. public class WebMvcAutoConfiguration {
      10. //其他略
      11. }

5 如何修改 SpringBoot 的默认配置

  • ① SpringBoot 在自动配置很多组件的时候,先看容器中有没有用户自己配置(@Bean 、@Component ),如果有,就使用用户自己配置的;如果没有,才自动配置;如果有些组件可以有多个(比如 ViewResolver ),就将用户配置的和自己默认的组合起来。
  • ② 在 SpringBoot 中会有非常多的 xxxConfigurer 帮助我们进行扩展配置。
  • ③ 在 SpringBoot 中会有非常多的 xxxCustomizer 帮助我们进行定制配置。

6 错误处理机制

6.1 SpringBoot 错误的默认效果

  • 如果是浏览器:返回一个默认的错误页面:

返回一个默认的错误页面.png

  • 如果是其他客户端:返回一个默认的 JSON 数据。

返回一个默认的JSON数据.png

  • 原理:可以参照 ErrorMvcAutoConfiguration(错误处理的自动配置)。

    • ErrorMvcAutoConfiguration 的部分源码如下: ```java @Configuration @ConditionalOnWebApplication @ConditionalOnClass({ Servlet.class, DispatcherServlet.class }) // Load before the main WebMvcAutoConfiguration so that the error View is available @AutoConfigureBefore(WebMvcAutoConfiguration.class) @EnableConfigurationProperties(ResourceProperties.class) public class ErrorMvcAutoConfiguration { //给容器中添加DefaultErrorAttributes组件 @Bean @ConditionalOnMissingBean(value = ErrorAttributes.class, search = SearchStrategy.CURRENT) public DefaultErrorAttributes errorAttributes() { return new DefaultErrorAttributes(); } //给容器中添加BasicErrorController组件 @Bean @ConditionalOnMissingBean(value = ErrorController.class, search = SearchStrategy.CURRENT) public BasicErrorController basicErrorController(ErrorAttributes errorAttributes) { return new BasicErrorController(errorAttributes, this.serverProperties.getError(),

      1. this.errorViewResolvers);

      } //给容器中添加ErrorPageCustomizer组件 @Bean public ErrorPageCustomizer errorPageCustomizer() { return new ErrorPageCustomizer(this.serverProperties); }

      @Configuration static class DefaultErrorViewResolverConfiguration {

      private final ApplicationContext applicationContext;

      private final ResourceProperties resourceProperties;

      DefaultErrorViewResolverConfiguration(ApplicationContext applicationContext,

      1. ResourceProperties resourceProperties) {
      2. this.applicationContext = applicationContext;
      3. this.resourceProperties = resourceProperties;

      }

      1. //给容器中添加DefaultErrorViewResolver组件

      @Bean @ConditionalOnBean(DispatcherServlet.class) @ConditionalOnMissingBean public DefaultErrorViewResolver conventionErrorViewResolver() {

      1. return new DefaultErrorViewResolver(this.applicationContext,
      2. this.resourceProperties);

      } } //其他略

}

  1. - ErrorPageCustomizer
  2. ```java
  3. @Configuration
  4. @ConditionalOnWebApplication
  5. @ConditionalOnClass({ Servlet.class, DispatcherServlet.class })
  6. // Load before the main WebMvcAutoConfiguration so that the error View is available
  7. @AutoConfigureBefore(WebMvcAutoConfiguration.class)
  8. @EnableConfigurationProperties(ResourceProperties.class)
  9. public class ErrorMvcAutoConfiguration {
  10. /**
  11. * {@link EmbeddedServletContainerCustomizer} that configures the container's error
  12. * pages.
  13. */
  14. private static class ErrorPageCustomizer implements ErrorPageRegistrar, Ordered {
  15. private final ServerProperties properties;
  16. protected ErrorPageCustomizer(ServerProperties properties) {
  17. this.properties = properties;
  18. }
  19. // @Value("${error.path:/error}")
  20. //getPath() = private String path = "/error";
  21. //系统出现错误以后会来到error请求进行处理。就如同在web.xml中配置错误页面规则
  22. @Override
  23. public void registerErrorPages(ErrorPageRegistry errorPageRegistry) {
  24. ErrorPage errorPage = new ErrorPage(this.properties.getServletPrefix()
  25. + this.properties.getError().getPath());
  26. errorPageRegistry.addErrorPages(errorPage);
  27. }
  28. @Override
  29. public int getOrder() {
  30. return 0;
  31. }
  32. }
  33. //其他略
  34. }
  • BasicErrorController:处理默认的 /error 请求。

    1. @Controller
    2. @RequestMapping("${server.error.path:${error.path:/error}}")
    3. public class BasicErrorController extends AbstractErrorController {
    4. //产生HTML类型的数据,浏览器发送的请求来到这个方法处理
    5. @RequestMapping(produces = "text/html")
    6. public ModelAndView errorHtml(HttpServletRequest request,
    7. HttpServletResponse response) {
    8. HttpStatus status = getStatus(request);
    9. //getErrorAttributes其实就是调用的DefaultErrorAttributes的getErrorAttributes方法,而DefaultErrorAttributes是从容器中获取的。
    10. Map<String, Object> model = Collections.unmodifiableMap(getErrorAttributes(
    11. request, isIncludeStackTrace(request, MediaType.TEXT_HTML)));
    12. response.setStatus(status.value());
    13. ModelAndView modelAndView = resolveErrorView(request, response, status, model);
    14. return (modelAndView == null ? new ModelAndView("error", model) : modelAndView);
    15. }
    16. //产生JSON数据,其他客户端来到这个方法处理
    17. @RequestMapping
    18. @ResponseBody
    19. public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
    20. Map<String, Object> body = getErrorAttributes(request,
    21. isIncludeStackTrace(request, MediaType.ALL));
    22. HttpStatus status = getStatus(request);
    23. return new ResponseEntity<Map<String, Object>>(body, status);
    24. }
    25. //略
    26. }
  • DefaultErrorViewResolver: ```java public class DefaultErrorViewResolver implements ErrorViewResolver, Ordered { private static final Map SERIES_VIEWS;

    static { Map views = new HashMap(); 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,

    1. Map<String, Object> model) {

    ModelAndView modelAndView = resolve(String.valueOf(status), model); if (modelAndView == null && SERIES_VIEWS.containsKey(status.series())) {

    1. modelAndView = resolve(SERIES_VIEWS.get(status.series()), model);

    } return modelAndView; }

    private ModelAndView resolve(String viewName, Map model) { //默认SpringBoot可以去找到一个页面 比如:/error/404 String errorViewName = “error/“ + viewName; //模板引擎可以解析这个页面地址就用模板引擎解析 TemplateAvailabilityProvider provider = this.templateAvailabilityProviders

    1. .getProvider(errorViewName, this.applicationContext);

    if (provider != null) {

    1. //模板引擎可用的情况下返回errorViewName指定的视图地址
    2. return new ModelAndView(errorViewName, model);

    } //模板引擎不可用,就在静态资源文件夹下找errorViewName对象的页面 return resolveResource(errorViewName, model); }

    private ModelAndView resolveResource(String viewName, Map model) { for (String location : this.resourceProperties.getStaticLocations()) {

    1. try {
    2. Resource resource = this.applicationContext.getResource(location);
    3. resource = resource.createRelative(viewName + ".html");
    4. if (resource.exists()) {
    5. return new ModelAndView(new HtmlResourceView(resource), model);
    6. }
    7. }
    8. catch (Exception ex) {
    9. }

    } return null; }
    //其他略

}

  1. - DefaultErrorAttributes
  2. ```java
  3. @Order(Ordered.HIGHEST_PRECEDENCE)
  4. public class DefaultErrorAttributes{
  5. //帮助我们在页面中共享信息
  6. @Override
  7. public Map<String, Object> getErrorAttributes(RequestAttributes requestAttributes,
  8. boolean includeStackTrace) {
  9. Map<String, Object> errorAttributes = new LinkedHashMap<String, Object>();
  10. errorAttributes.put("timestamp", new Date());
  11. addStatus(errorAttributes, requestAttributes);
  12. addErrorDetails(errorAttributes, requestAttributes, includeStackTrace);
  13. addPath(errorAttributes, requestAttributes);
  14. return errorAttributes;
  15. }
  16. }
  • 步骤:
    • 一旦系统出现 4xx 或者 5xx 之类的错误,ErrorPageCustomizer 就会生效(定制错误的响应规则),就会来到 /error 请求,就会被 BasicErrorController 处理。
    • 响应页面:去哪个页面是由 DefaultErrorViewResolver 解析得到的。
      1. public abstract class AbstractErrorController implements ErrorController {
      2. /**
      3. * Resolve any specific error views. By default this method delegates to
      4. * {@link ErrorViewResolver ErrorViewResolvers}.
      5. * @param request the request
      6. * @param response the response
      7. * @param status the HTTP status
      8. * @param model the suggested model
      9. * @return a specific {@link ModelAndView} or {@code null} if the default should be
      10. * used
      11. * @since 1.4.0
      12. */
      13. protected ModelAndView resolveErrorView(HttpServletRequest request,
      14. HttpServletResponse response, HttpStatus status, Map<String, Object> model) {
      15. for (ErrorViewResolver resolver : this.errorViewResolvers) {
      16. ModelAndView modelAndView = resolver.resolveErrorView(request, status, model);
      17. if (modelAndView != null) {
      18. return modelAndView;
      19. }
      20. }
      21. return null;
      22. }
      23. //其他略
      24. }

6.2 如果定制错误响应

6.2.1 如何定制错误页面

  • ① 如果有模板引擎的情况下,默认请求 error/状态码 ,并且返回 error/状态码的视图地址 ,那么我们只需要将错误页面命名为 错误状态码.html 并放在模板引擎文件夹(默认是 classpath:/template )里面的 error 文件夹下。
    • 我们可以使用 4xx 和 5xx 作为错误页面的文件名来匹配这种类型的所有错误,精确优先(比如同时存在 404.html 和 4xx.html ,那么当发生 404 的错误的时候,会优先寻找 404.html,一旦匹配到了,就响应 404.html)。
    • 页面能获取的信息:
      • timestamp:时间戳。
      • status:状态码。
      • error:错误提示。
      • exception:异常对象。
      • errors:JSR303错误校验的信息。
      • path:错误路径。
  • ② 如果没有模板引擎(模板引擎找不多对应的错误页面),就去静态资源文件夹下寻找。
  • ③ 以上都没有错误页面,就来到 SpringBoot 默认的错误提示页面。

6.2.2 如何定制错误的 JSON 数据

  • ① 自定义异常处理&返回定义 JSON 数据:
  1. package com.sunxiaping.springboot.config;
  2. import org.springframework.web.bind.annotation.ControllerAdvice;
  3. import org.springframework.web.bind.annotation.ExceptionHandler;
  4. import org.springframework.web.bind.annotation.ResponseBody;
  5. import java.util.HashMap;
  6. import java.util.Map;
  7. /**
  8. * @ControllerAdvice标注的类:就是对控制器进行增强,在这个类中,可以使用@InitBinder、@InitBinder和@ModelAttribute,这些功能都可以被所有的控制器共享
  9. */
  10. @ControllerAdvice
  11. public class SelfExceptionHandler {
  12. //浏览器客户端返回的都是JSON
  13. @ExceptionHandler(value = SelfException.class)
  14. @ResponseBody
  15. public Map<String,Object> handleException(SelfException e){
  16. Map<String,Object> map = new HashMap<>();
  17. map.put("code",1);
  18. map.put("msg",e.getMessage());
  19. return map;
  20. }
  21. }
  • ② 转发到 /error 进行自适应响应效果处理:
  1. package com.sunxiaping.springboot.config;
  2. import org.springframework.web.bind.annotation.ControllerAdvice;
  3. import org.springframework.web.bind.annotation.ExceptionHandler;
  4. import javax.servlet.http.HttpServletRequest;
  5. import javax.servlet.http.HttpServletResponse;
  6. import java.util.HashMap;
  7. import java.util.Map;
  8. /**
  9. * @ControllerAdvice标注的类:就是对控制器进行增强,在这个类中,可以使用@InitBinder、@InitBinder和@ModelAttribute,这些功能都可以被所有的控制器共享
  10. */
  11. @ControllerAdvice
  12. public class SelfExceptionHandler {
  13. @ExceptionHandler(value = SelfException.class)
  14. public String handleException(HttpServletRequest request, HttpServletResponse response, SelfException e) {
  15. Map<String, Object> map = new HashMap<>();
  16. //传入我们自己的错误状态码,否则就不会进入定制的错误页面的解析流程
  17. request.setAttribute("javax.servlet.error.status_code", 500);
  18. map.put("code", 1);
  19. map.put("msg", e.getMessage());
  20. return "forward:/error";
  21. }
  22. }
  • ③ 将我们的定制数据携带出去:
    • 出现错误以后,会来到 /error 请求,会被 BasicErrorController 处理,响应出去可以获取的数据是由 getErrorAttributes 得到的(是 AbstractErrorController 中规定的方法),所以我们可以编写一个 ErrorController 的实现类或 AbstractErrorController 的子类,放在容器中。
    • 页面上能用的数据或者是 JSON 返回能用的数据都是通过 errorAttributes.getErrorAttributes() 方法得到的,容器中的 DefaultErrorAttributes.getErrorAttributes() 方法是默认进行数据处理的。 ```java package com.sunxiaping.springboot.config;

import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler;

import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.util.HashMap; import java.util.Map;

/**

  • @ControllerAdvice标注的类:就是对控制器进行增强,在这个类中,可以使用@InitBinder、@InitBinder和@ModelAttribute,这些功能都可以被所有的控制器共享 */ @ControllerAdvice public class SelfExceptionHandler {

    @ExceptionHandler(value = SelfException.class) public String handleException(HttpServletRequest request, HttpServletResponse response, SelfException e) {

    1. Map<String, Object> map = new HashMap<>();
    2. //传入我们自己的错误状态码
    3. request.setAttribute("javax.servlet.error.status_code", 500);
    4. map.put("code", 1);
    5. map.put("msg", e.getMessage());
    6. request.setAttribute("ext", map);
    7. return "forward:/error";

    }

}

  1. ```java
  2. package com.sunxiaping.springboot.config;
  3. import org.springframework.boot.autoconfigure.web.DefaultErrorAttributes;
  4. import org.springframework.stereotype.Component;
  5. import org.springframework.web.context.request.RequestAttributes;
  6. import java.util.Map;
  7. @Component
  8. public class SelfErrorAttributes extends DefaultErrorAttributes {
  9. @Override
  10. public Map<String, Object> getErrorAttributes(RequestAttributes requestAttributes, boolean includeStackTrace) {
  11. Map<String, Object> map = super.getErrorAttributes(requestAttributes, includeStackTrace);
  12. Object ext = requestAttributes.getAttribute("ext", RequestAttributes.SCOPE_REQUEST);
  13. map.put("ext", ext);
  14. return map;
  15. }
  16. }

7 配置嵌入式 Servlet 容器

7.1 如何定制和修改 Servlet 容器的相关配置

7.1.1 修改和 server 有关的配置( ServerProperties )

  1. server:
  2. port: 8081
  3. context-path: /spring
  4. tomcat:
  5. uri-encoding: utf-8
  6. # 通用的servlet容器配置
  7. server:
  8. xxx:
  9. # Tomcat的设置
  10. server:
  11. tomcat:
  12. xxx:

7.1.2 编写一个 EmbeddedServletContainerCustomizer (嵌入式的 Servlet 容器的定制器)来修改 Servlet 有关配置

  1. package com.sunxiaping.springboot.config;
  2. import org.springframework.boot.context.embedded.ConfigurableEmbeddedServletContainer;
  3. import org.springframework.boot.context.embedded.EmbeddedServletContainerCustomizer;
  4. import org.springframework.context.annotation.Bean;
  5. import org.springframework.context.annotation.Configuration;
  6. import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
  7. import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
  8. @Configuration
  9. public class SpringMvcConfig extends WebMvcConfigurerAdapter {
  10. @Override
  11. public void addViewControllers(ViewControllerRegistry registry) {
  12. registry.addViewController("/index").setViewName("index");
  13. registry.addViewController("/").setViewName("index");
  14. registry.addViewController("/index.html").setViewName("index");
  15. registry.addViewController("/index.htm").setViewName("index");
  16. }
  17. @Bean
  18. public EmbeddedServletContainerCustomizer embeddedServletContainerCustomizer() {
  19. return new EmbeddedServletContainerCustomizer() {
  20. /**定制嵌入式的Servlet容器的相关规则
  21. * @param container
  22. */
  23. @Override
  24. public void customize(ConfigurableEmbeddedServletContainer container) {
  25. container.setPort(8081);
  26. }
  27. };
  28. }
  29. }

7.1.3 注册 Servlet 三大组件

  1. package com.sunxiaping.springboot.servet;
  2. import javax.servlet.ServletException;
  3. import javax.servlet.http.HttpServlet;
  4. import javax.servlet.http.HttpServletRequest;
  5. import javax.servlet.http.HttpServletResponse;
  6. import java.io.IOException;
  7. public class SelfServlet extends HttpServlet {
  8. @Override
  9. protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
  10. resp.getWriter().println("hello servlet");
  11. }
  12. @Override
  13. protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
  14. this.doGet(req, resp);
  15. }
  16. }
  1. package com.sunxiaping.springboot.config;
  2. import com.sunxiaping.springboot.servet.SelfServlet;
  3. import org.springframework.boot.web.servlet.ServletRegistrationBean;
  4. import org.springframework.context.annotation.Bean;
  5. import org.springframework.context.annotation.Configuration;
  6. @Configuration
  7. public class ServletConfig {
  8. /**
  9. * 注册Servlet,向容器中添加ServletRegistrationBean组件
  10. * 注册Filter,向容器中添加FilterRegistrationBean组件
  11. * 注册Listener,向容器中添加ServletListenerRegistrationBean组件
  12. *
  13. * @return
  14. */
  15. @Bean
  16. public ServletRegistrationBean servletRegistrationBean() {
  17. return new ServletRegistrationBean(new SelfServlet(), "/hello");
  18. }
  19. }

7.2 SpringBoot 支持其他的 Servlet 容器

  • SpringBoot 默认支持 Tomcat 、Jetty 、Undertow 。

SpringBoot默认支持的Servlet容器.png

  • Tomcat(默认支持):
  1. <dependency>
  2. <!-- 引入web模块,默认支持Tomcat -->
  3. <groupId>org.springframework.boot</groupId>
  4. <artifactId>spring-boot-starter-web</artifactId>
  5. </dependency>
  • Jetty:
  1. <dependency>
  2. <groupId>org.springframework.boot</groupId>
  3. <artifactId>spring-boot-starter-web</artifactId>
  4. <exclusions>
  5. <exclusion>
  6. <artifactId>spring-boot-starter-tomcat</artifactId>
  7. <groupId>org.springframework.boot</groupId>
  8. </exclusion>
  9. </exclusions>
  10. </dependency>
  11. <dependency>
  12. <groupId>org.springframework.boot</groupId>
  13. <artifactId>spring-boot-starter-jetty</artifactId>
  14. </dependency>
  • Undertow:
  1. <dependency>
  2. <groupId>org.springframework.boot</groupId>
  3. <artifactId>spring-boot-starter-web</artifactId>
  4. <exclusions>
  5. <exclusion>
  6. <artifactId>spring-boot-starter-tomcat</artifactId>
  7. <groupId>org.springframework.boot</groupId>
  8. </exclusion>
  9. </exclusions>
  10. </dependency>
  11. <dependency>
  12. <groupId>org.springframework.boot</groupId>
  13. <artifactId>spring-boot-starter-undertow</artifactId>
  14. </dependency>

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

  • EmbeddedServletContainerAutoConfiguration:嵌入式的 Servlet 容器自动配置
  1. @AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
  2. @Configuration
  3. @ConditionalOnWebApplication
  4. @Import(BeanPostProcessorsRegistrar.class)
  5. //BeanPostProcessorsRegistrar:给容器中导入一些组件。
  6. //导入了EmbeddedServletContainerCustomizerBeanPostProcessor
  7. //后置处理器:Bean初始化前后(创建完对象,还没赋值)执行初始化工作。
  8. public class EmbeddedServletContainerAutoConfiguration {
  9. @Configuration
  10. @ConditionalOnClass({ Servlet.class, Tomcat.class }) //判断当前是否引入了Tomcat的依赖
  11. @ConditionalOnMissingBean(value = EmbeddedServletContainerFactory.class, search = SearchStrategy.CURRENT)//判断当前容器没有用户自己定义的EmbeddedServletContainerFactory:嵌入式的Servlet容器工厂;作用是创建嵌入式的Servlet容器
  12. public static class EmbeddedTomcat {
  13. @Bean
  14. public TomcatEmbeddedServletContainerFactory tomcatEmbeddedServletContainerFactory() {
  15. return new TomcatEmbeddedServletContainerFactory();
  16. }
  17. }
  18. /**
  19. * Nested configuration if Jetty is being used.
  20. */
  21. @Configuration
  22. @ConditionalOnClass({ Servlet.class, Server.class, Loader.class,
  23. WebAppContext.class })
  24. @ConditionalOnMissingBean(value = EmbeddedServletContainerFactory.class, search = SearchStrategy.CURRENT)
  25. public static class EmbeddedJetty {
  26. @Bean
  27. public JettyEmbeddedServletContainerFactory jettyEmbeddedServletContainerFactory() {
  28. return new JettyEmbeddedServletContainerFactory();
  29. }
  30. }
  31. /**
  32. * Nested configuration if Undertow is being used.
  33. */
  34. @Configuration
  35. @ConditionalOnClass({ Servlet.class, Undertow.class, SslClientAuthMode.class })
  36. @ConditionalOnMissingBean(value = EmbeddedServletContainerFactory.class, search = SearchStrategy.CURRENT)
  37. public static class EmbeddedUndertow {
  38. @Bean
  39. public UndertowEmbeddedServletContainerFactory undertowEmbeddedServletContainerFactory() {
  40. return new UndertowEmbeddedServletContainerFactory();
  41. }
  42. }
  43. //略
  44. }
  • EmbeddedServletContainerFactory:嵌入式的 Servlet 容器工厂
  1. public interface EmbeddedServletContainerFactory {
  2. /**
  3. * 获取的嵌入式的Servlet容器
  4. */
  5. EmbeddedServletContainer getEmbeddedServletContainer(
  6. ServletContextInitializer... initializers);
  7. }

嵌入式的Servlet容器.png

  • EmbeddedServletContainer:嵌入式的 Servlet 容器。
  1. public interface EmbeddedServletContainer {
  2. /**
  3. * Starts the embedded servlet container. Calling this method on an already started
  4. * container has no effect.
  5. * @throws EmbeddedServletContainerException if the container cannot be started
  6. */
  7. void start() throws EmbeddedServletContainerException;
  8. /**
  9. * Stops the embedded servlet container. Calling this method on an already stopped
  10. * container has no effect.
  11. * @throws EmbeddedServletContainerException if the container cannot be stopped
  12. */
  13. void stop() throws EmbeddedServletContainerException;
  14. /**
  15. * Return the port this server is listening on.
  16. * @return the port (or -1 if none)
  17. */
  18. int getPort();
  19. }

嵌入式的Servlet容器工厂.png

  • 以 TomcatEmbeddedServletContainerFactory 为例:
  1. public class TomcatEmbeddedServletContainerFactory
  2. extends AbstractEmbeddedServletContainerFactory implements ResourceLoaderAware {
  3. @Override
  4. public EmbeddedServletContainer getEmbeddedServletContainer(
  5. ServletContextInitializer... initializers) {
  6. //创建一个Tomcat
  7. Tomcat tomcat = new Tomcat();
  8. //配置TOmcat的基本环境
  9. File baseDir = (this.baseDirectory != null ? this.baseDirectory
  10. : createTempDir("tomcat"));
  11. tomcat.setBaseDir(baseDir.getAbsolutePath());
  12. Connector connector = new Connector(this.protocol);
  13. tomcat.getService().addConnector(connector);
  14. customizeConnector(connector);
  15. tomcat.setConnector(connector);
  16. tomcat.getHost().setAutoDeploy(false);
  17. configureEngine(tomcat.getEngine());
  18. for (Connector additionalConnector : this.additionalTomcatConnectors) {
  19. tomcat.getService().addConnector(additionalConnector);
  20. }
  21. prepareContext(tomcat.getHost(), initializers);
  22. //将配置好的Tomcat传入进去,返回一个嵌入式的Servlet容器,并且启动Tomcat服务器
  23. return getTomcatEmbeddedServletContainer(tomcat);
  24. }
  25. protected TomcatEmbeddedServletContainer getTomcatEmbeddedServletContainer(
  26. Tomcat tomcat) {
  27. return new TomcatEmbeddedServletContainer(tomcat, getPort() >= 0);
  28. }
  29. public TomcatEmbeddedServletContainer(Tomcat tomcat, boolean autoStart) {
  30. Assert.notNull(tomcat, "Tomcat Server must not be null");
  31. this.tomcat = tomcat;
  32. this.autoStart = autoStart;
  33. initialize();
  34. }
  35. private void initialize() throws EmbeddedServletContainerException {
  36. TomcatEmbeddedServletContainer.logger
  37. .info("Tomcat initialized with port(s): " + getPortsDescription(false));
  38. synchronized (this.monitor) {
  39. try {
  40. addInstanceIdToEngineName();
  41. try {
  42. // Remove service connectors to that protocol binding doesn't happen
  43. // yet
  44. removeServiceConnectors();
  45. // 启动Tomcat
  46. this.tomcat.start();
  47. // We can re-throw failure exception directly in the main thread
  48. rethrowDeferredStartupExceptions();
  49. Context context = findContext();
  50. try {
  51. ContextBindings.bindClassLoader(context, getNamingToken(context),
  52. getClass().getClassLoader());
  53. }
  54. catch (NamingException ex) {
  55. // Naming is not enabled. Continue
  56. }
  57. // Unlike Jetty, all Tomcat threads are daemon threads. We create a
  58. // blocking non-daemon to stop immediate shutdown
  59. startDaemonAwaitThread();
  60. }
  61. catch (Exception ex) {
  62. containerCounter.decrementAndGet();
  63. throw ex;
  64. }
  65. }
  66. catch (Exception ex) {
  67. throw new EmbeddedServletContainerException(
  68. "Unable to start embedded Tomcat", ex);
  69. }
  70. }
  71. }
  72. //略
  73. }
  • 我们对嵌入式容器的配置修改是怎么修改的?
    • ① ServerProperties。ServerProperties 其实是 EmbeddedServletContainerCustomizer 的子类。
    • ② EmbeddedServletContainerCustomizer :定制器帮我们修改了 Servlet 容器的配置。
  • 容器中导入了 EmbeddedServletContainerCustomizerBeanPostProcessor :
  1. public class EmbeddedServletContainerCustomizerBeanPostProcessor
  2. implements BeanPostProcessor, BeanFactoryAware {
  3. //在初始化之前
  4. @Override
  5. public Object postProcessBeforeInitialization(Object bean, String beanName)
  6. throws BeansException {
  7. //如果当前初始化的是一个ConfigurableEmbeddedServletContainer类型的组件
  8. if (bean instanceof ConfigurableEmbeddedServletContainer) {
  9. //EmbeddedServletContainerCustomizer定制器,调用每个定制器的customize方法,来给Servlet容器进行属性赋值
  10. postProcessBeforeInitialization((ConfigurableEmbeddedServletContainer) bean);
  11. }
  12. return bean;
  13. }
  14. @Override
  15. public Object postProcessAfterInitialization(Object bean, String beanName)
  16. throws BeansException {
  17. return bean;
  18. }
  19. private void postProcessBeforeInitialization(
  20. ConfigurableEmbeddedServletContainer bean) {
  21. for (EmbeddedServletContainerCustomizer customizer : getCustomizers()) {
  22. customizer.customize(bean);
  23. }
  24. }
  25. private Collection<EmbeddedServletContainerCustomizer> getCustomizers() {
  26. if (this.customizers == null) {
  27. // Look up does not include the parent context
  28. this.customizers = new ArrayList<EmbeddedServletContainerCustomizer>(
  29. this.beanFactory
  30. //从容器中获取所有EmbeddedServletContainerCustomizer类型的组件
  31. //所以定制容器,可以给容器中配置EmbeddedServletContainerCustomizer类型的组件
  32. .getBeansOfType(EmbeddedServletContainerCustomizer.class,
  33. false, false)
  34. .values());
  35. Collections.sort(this.customizers, AnnotationAwareOrderComparator.INSTANCE);
  36. this.customizers = Collections.unmodifiableList(this.customizers);
  37. }
  38. return this.customizers;
  39. }
  40. //其他略
  41. }

总结:

  • ① SpringBoot 根据导入的依赖情况,给容器中添加相应的 EmbeddedServletContainerFactory(以 TomcatEmbeddedServletContainerFactory 为例)。
  • ② 容器中某个组件要创建对象就会惊动后置处理器(EmbeddedServletContainerCustomizerBeanPostProcessor),是因为 EmbeddedServletContainerAutoConfiguration 中使用 @Import 注解,导入了 BeanPostProcessorsRegistrar ,而 BeanPostProcessorsRegistrar 是一个 ImportBeanDefinitionRegistrar ,向容器中导入了 EmbeddedServletContainerCustomizerBeanPostProcessor ,简而言之,只要是嵌入式的 Servlet 容器工厂,后置处理器就工作。
  • ③ 后置处理器,从容器中获取所有的 EmbeddedServletContainerCustomizer ,调用定制器的 customize(ServerProperties 其实就是 EmbeddedServletContainerCustomizer )。

7.4 嵌入式 Servlet 容器启动原理

  • ① SpringBoot 应用启动运行 run 方法。
  • ② refreshContext(context);SpringBoot 刷新 IOC 容器(创建 IOC 容器,并初始化容器,创建容器中的每一个组件);如果是 Web 应用,创建的是 AnnotationConfigEmbeddedWebApplicationContext ;如果不是 web 应用,创建的是 ConfigurableWebApplicationContext 。
  • ③ refresh(context); 刷新刚才创建好的 web 容器。
  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. // 刷新容器
  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. }
  • ④ onRefresh(); web 的 IOC 容器重写了 onRefresh 方法。
  • ⑤ web 的IOC 容器会创建嵌入式的 Servlet 容器。
  1. public class EmbeddedWebApplicationContext extends GenericWebApplicationContext {
  2. @Override
  3. protected void onRefresh() {
  4. super.onRefresh();
  5. try {
  6. //创建嵌入式的Servlet容器
  7. createEmbeddedServletContainer();
  8. }
  9. catch (Throwable ex) {
  10. throw new ApplicationContextException("Unable to start embedded container",
  11. ex);
  12. }
  13. }
  14. private void createEmbeddedServletContainer() {
  15. EmbeddedServletContainer localContainer = this.embeddedServletContainer;
  16. ServletContext localServletContext = getServletContext();
  17. if (localContainer == null && localServletContext == null) {
  18. //获取嵌入式的Servlet容器工厂
  19. //从IOC容器中获取EmbeddedServletContainerFactory组件。TomcatEmbeddedServletContainerFactory创建对象,后置处理器一看是这个对象,就获取所有的定制器来先定制Servlet容器的相关配置
  20. EmbeddedServletContainerFactory containerFactory = getEmbeddedServletContainerFactory();
  21. this.embeddedServletContainer = containerFactory
  22. .getEmbeddedServletContainer(getSelfInitializer());
  23. }
  24. else if (localServletContext != null) {
  25. try {
  26. getSelfInitializer().onStartup(localServletContext);
  27. }
  28. catch (ServletException ex) {
  29. throw new ApplicationContextException("Cannot initialize servlet context",
  30. ex);
  31. }
  32. }
  33. initPropertySources();
  34. }
  35. //其他略
  36. }
  • ⑥ 获取嵌入式的 Servlet 容器工厂:
  1. public class EmbeddedWebApplicationContext extends GenericWebApplicationContext {
  2. @Override
  3. protected void onRefresh() {
  4. super.onRefresh();
  5. try {
  6. //创建嵌入式的Servlet容器
  7. createEmbeddedServletContainer();
  8. }
  9. catch (Throwable ex) {
  10. throw new ApplicationContextException("Unable to start embedded container",
  11. ex);
  12. }
  13. }
  14. private void createEmbeddedServletContainer() {
  15. EmbeddedServletContainer localContainer = this.embeddedServletContainer;
  16. ServletContext localServletContext = getServletContext();
  17. if (localContainer == null && localServletContext == null) {
  18. //获取嵌入式的Servlet容器工厂
  19. //从IOC容器中获取EmbeddedServletContainerFactory组件。TomcatEmbeddedServletContainerFactory创建对象,后置处理器一看是这个对象,就获取所有的定制器来先定制Servlet容器的相关配置
  20. EmbeddedServletContainerFactory containerFactory = getEmbeddedServletContainerFactory();
  21. this.embeddedServletContainer = containerFactory
  22. .getEmbeddedServletContainer(getSelfInitializer());
  23. }
  24. else if (localServletContext != null) {
  25. try {
  26. getSelfInitializer().onStartup(localServletContext);
  27. }
  28. catch (ServletException ex) {
  29. throw new ApplicationContextException("Cannot initialize servlet context",
  30. ex);
  31. }
  32. }
  33. initPropertySources();
  34. }
  35. //其他略
  36. }
  • ⑦ 使用容器工厂获取嵌入式的 Servle t容器:this.embeddedServletContainer = containerFactory.getEmbeddedServletContainer(getSelfInitializer());
  • ⑧ 嵌入式的 Servlet 容器创建对象并启动 Servlet 容器。