1 简介
- 使用 SpringBoot 步骤:
- ① 创建 SpringBoot 应用,选中我们需要的模块。
- ② SpringBoot 已经默认将这些场景配置好了,只需要在配置文件指定少量配置就可以运行起来。
- ③ 自己编写业务逻辑代码。
- 自动配置原理:这个场景 SpringBoot 帮我们配置了什么?能不能修改?能修改哪些配置?能不能扩展?
xxxAutoConfiguration:帮我们在容器中自动配置组件。xxxProperties:配置类封装配置文件的内容。
2 SpringBoot 对静态资源的映射规则
- WebMvcAutoConfiguration 的部分源码如下:
@Configuration@ConditionalOnWebApplication@ConditionalOnClass({ Servlet.class, DispatcherServlet.class,WebMvcConfigurerAdapter.class })@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)@AutoConfigureAfter({ DispatcherServletAutoConfiguration.class,ValidationAutoConfiguration.class })public class WebMvcAutoConfiguration {@Configuration@Import(EnableWebMvcConfiguration.class)@EnableConfigurationProperties({ WebMvcProperties.class, ResourceProperties.class })public static class WebMvcAutoConfigurationAdapter extends WebMvcConfigurerAdapter {//静态资源处理器@Overridepublic void addResourceHandlers(ResourceHandlerRegistry registry) {if (!this.resourceProperties.isAddMappings()) {logger.debug("Default resource handling disabled");return;}Integer cachePeriod = this.resourceProperties.getCachePeriod();if (!registry.hasMappingForPattern("/webjars/**")) {customizeResourceHandlerRegistration(registry.addResourceHandler("/webjars/**").addResourceLocations("classpath:/META-INF/resources/webjars/").setCachePeriod(cachePeriod));}String staticPathPattern = this.mvcProperties.getStaticPathPattern();if (!registry.hasMappingForPattern(staticPathPattern)) {customizeResourceHandlerRegistration(registry.addResourceHandler(staticPathPattern).addResourceLocations(this.resourceProperties.getStaticLocations()).setCachePeriod(cachePeriod));}}//欢迎页@Beanpublic WelcomePageHandlerMapping welcomePageHandlerMapping(ResourceProperties resourceProperties) {return new WelcomePageHandlerMapping(resourceProperties.getWelcomePage(),this.mvcProperties.getStaticPathPattern());}//配置喜欢的图标@Configuration@ConditionalOnProperty(value = "spring.mvc.favicon.enabled", matchIfMissing = true)public static class FaviconConfiguration {private final ResourceProperties resourceProperties;public FaviconConfiguration(ResourceProperties resourceProperties) {this.resourceProperties = resourceProperties;}@Beanpublic SimpleUrlHandlerMapping faviconHandlerMapping() {SimpleUrlHandlerMapping mapping = new SimpleUrlHandlerMapping();mapping.setOrder(Ordered.HIGHEST_PRECEDENCE + 1);//所有**/favicon.icomapping.setUrlMap(Collections.singletonMap("**/favicon.ico",faviconRequestHandler()));return mapping;}@Beanpublic ResourceHttpRequestHandler faviconRequestHandler() {ResourceHttpRequestHandler requestHandler = new ResourceHttpRequestHandler();requestHandler.setLocations(this.resourceProperties.getFaviconLocations());return requestHandler;}}}}
① 所有
/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文件。<dependency><groupId>org.webjars</groupId><artifactId>jquery</artifactId><version>3.5.1</version></dependency>

②
/**访问当前项目的任何资源(静态资源的文件夹):"classpath:/META-INF/resources/""classpath:/resources/""classpath:/static/""classpath:/public/""/"
- 比如:浏览器发送
http://localhost:8080/asserts/css/bootstrap.min.css请求,SpringBoot 就会到静态资源文件夹中寻找/asserts/css/bootstrap.min.css的路径去寻找bootstrap.min.css文件。

- ③ 欢迎页,静态资源文件夹下的 index.html 页面,被
/**映射。 - 比如:浏览器发送
http://localhost:8080请求,默认情况下,会去静态资源文件夹下找 index.html 页面。 - ④ 所有的
**/favicon.ico都是在静态资源文件夹下寻找。
3 模板引擎
3.1 简介
- JSP 、Velocity 、Freemarker 、Thymeleaf……都是模板引擎。
- 模板引擎的原理:

3.2 Thymeleaf
3.2.1 Thymeleaf 概述
- SpringBoot 推荐 Thymeleaf 。
- Thymeleaf 语法更简单,功能更强大。
3.2.2 Thymeleaf 准备
- 导入 thymeleaf 相关 jar 包的 Maven 坐标:
<properties><thymeleaf.version>3.0.9.RELEASE</thymeleaf.version><thymeleaf-layout-dialect.version>2.3.0</thymeleaf-layout-dialect.version></properties><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-thymeleaf</artifactId></dependency>
- ThymeleafProperties:
@ConfigurationProperties(prefix = "spring.thymeleaf")public class ThymeleafProperties {private static final Charset DEFAULT_ENCODING = Charset.forName("UTF-8");private static final MimeType DEFAULT_CONTENT_TYPE = MimeType.valueOf("text/html");public static final String DEFAULT_PREFIX = "classpath:/templates/";public static final String DEFAULT_SUFFIX = ".html";//略}
- 从 ThymeleafProperties 的源码可知:我们只要将 HTML 页面放在
classpath:/templates/,Thymeleaf 就能自动渲染。
3.2.3 Thymeleaf 使用
- 导入 thymeleaf 的名称空间:
<html lang="en" xmlns:th="http://www.thymeleaf.org">
- 使用 thymeleaf 语法:
<!DOCTYPE html><html lang="en" xmlns:th="http://www.thymeleaf.org"><head><meta charset="UTF-8"><title>Title</title></head><body><div th:text="helloworld">哇,好(*^▽^*)</div></body></html>
3.2.4 Thymeleaf 语法规则
文本
th:text:改变当前元素里面的文本内容。th:任意html属性:来替换原生属性的值。th:insert、th:replace:片段包含。th:each:遍历。th:if、th:unless、th:case:条件判断。th:object、th:with:声明变量。th:attr、th:attrprepend、th:attrappend:任意属性修改。th:value、th:href、th:src:修改指定属性默认值。th:fragment:声明片段。th"remove:移除。
表达式
Simple expressions:(表达式语法)Variable Expressions: ${...}:获取变量值;OGNL;1)、获取对象的属性、调用方法2)、使用内置的基本对象:#ctx : the context object.#vars: the context variables.#locale : the context locale.#request : (only in Web Contexts) the HttpServletRequest object.#response : (only in Web Contexts) the HttpServletResponse object.#session : (only in Web Contexts) the HttpSession object.#servletContext : (only in Web Contexts) the ServletContext object.${session.foo}3)、内置的一些工具对象:#execInfo : information about the template being processed.#messages : methods for obtaining externalized messages inside variables expressions, in the same way as they would be obtained using #{…} syntax.#uris : methods for escaping parts of URLs/URIs#conversions : methods for executing the configured conversion service (if any).#dates : methods for java.util.Date objects: formatting, component extraction, etc.#calendars : analogous to #dates , but for java.util.Calendar objects.#numbers : methods for formatting numeric objects.#strings : methods for String objects: contains, startsWith, prepending/appending, etc.#objects : methods for objects in general.#bools : methods for boolean evaluation.#arrays : methods for arrays.#lists : methods for lists.#sets : methods for sets.#maps : methods for maps.#aggregates : methods for creating aggregates on arrays or collections.#ids : methods for dealing with id attributes that might be repeated (for example, as a result of an iteration).Selection Variable Expressions: *{...}:选择表达式:和${}在功能上是一样;补充:配合 th:object="${session.user}:<div th:object="${session.user}"><p>Name: <span th:text="*{firstName}">Sebastian</span>.</p><p>Surname: <span th:text="*{lastName}">Pepper</span>.</p><p>Nationality: <span th:text="*{nationality}">Saturn</span>.</p></div>Message Expressions: #{...}:获取国际化内容Link URL Expressions: @{...}:定义URL;@{/order/process(execId=${execId},execType='FAST')}Fragment Expressions: ~{...}:片段引用表达式<div th:insert="~{commons :: main}">...</div>Literals(字面量)Text literals: 'one text' , 'Another one!' ,…Number literals: 0 , 34 , 3.0 , 12.3 ,…Boolean literals: true , falseNull literal: nullLiteral tokens: one , sometext , main ,…Text operations:(文本操作)String concatenation: +Literal substitutions: |The name is ${name}|Arithmetic operations:(数学运算)Binary operators: + , - , * , / , %Minus sign (unary operator): -Boolean operations:(布尔运算)Binary operators: and , orBoolean negation (unary operator): ! , notComparisons and equality:(比较运算)Comparators: > , < , >= , <= ( gt , lt , ge , le )Equality operators: == , != ( eq , ne )Conditional operators:条件运算(三元运算符)If-then: (if) ? (then)If-then-else: (if) ? (then) : (else)Default: (value) ?: (defaultvalue)Special tokens:No-Operation: _
4 SpringMVC 自动配置
4.1 SpringMVC 自动配置
SpringBoot 自动配置好了 SpringMVC ,下面 SpringBoot 对 SpringMVC 的默认配置(
WebMvcAutoConfiguration):- ① Inclusion of
ContentNegotiatingViewResolverandBeanNameViewResolverbeans。- 自动配置了 ViewResolver(视图解析器,根据方法的返回值得到视图对象(View),视图对象决定如何渲染(转发?重定向?))。
ContentNegotiatingViewResolver:组合所有的视图解析器。- 如何定制?我们可以自己给容器中添加一个视图解析器,自动将其组合进来。
- ② Support for serving static resources, including support for WebJars (see below)。
- 支持静态资源文件夹路径和 webjars 。
- ③ Static
index.htmlsupport.。- 首页访问。
- ④ Custom
Faviconsupport (see below)。- favicon.ico。
⑤ 自动注册了 of
Converter,GenericConverter,Formatterbeans。Converter:转换器。Formatter:格式化器。@Bean@ConditionalOnProperty(prefix = "spring.mvc", name = "date-format") //在配置文件中配置日期格式化的规则public Formatter<Date> dateFormatter() {return new DateFormatter(this.mvcProperties.getDateFormat()); //日期格式化组件}
我们自己添加的格式化器、转换器只需要放在容器中即可。
@Overridepublic void addFormatters(FormatterRegistry registry) {for (Converter<?, ?> converter : getBeansOfType(Converter.class)) {registry.addConverter(converter);}for (GenericConverter converter : getBeansOfType(GenericConverter.class)) {registry.addConverter(converter);}for (Formatter<?> formatter : getBeansOfType(Formatter.class)) {registry.addFormatter(formatter);}}
⑥ Support for
HttpMessageConverters(see below)。- HttpMessageConverter:SpringMVC 用来转换 Http 的请求和响应的。比如 JavaBean 和 JSON 之间的转换。
HttpMessageConverters是从容器中获取的,并且获取所有的 HttpMessageConverter 。- 自己给容器中添加 HttpMessageConverter ,只需要自己将组件注册到容器中。
- ⑦ Automatic registration of
MessageCodesResolver(see below)。- 定义错误代码生成规则。
- ⑧ Automatic use of a
ConfigurableWebBindingInitializerbean (see below)。- 我们可以配置一个 ConfigurableWebBindingInitializer 来替换默认的。
- ConfigurableWebBindingInitializer的作用就是初始化所有的WebDataBinder,而WebDataBinder的作用就是请求数据—>JavaBean。
- ① Inclusion of
- ⑨ 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
@Configurationclass of typeWebMvcConfigurerAdapter, but without@EnableWebMvc. If you wish to provide custom instances ofRequestMappingHandlerMapping,RequestMappingHandlerAdapterorExceptionHandlerExceptionResolveryou can declare aWebMvcRegistrationsAdapterinstance providing such components.
If you want to take complete control of Spring MVC, you can add your own@Configurationannotated with@EnableWebMvc.
4.2 扩展 SpringMVC
- 以前的 springmvc.xml 中配置拦截器等:
<mvc:view-controller path="/hello" view-name="success"/><mvc:interceptors><mvc:interceptor><mvc:mapping path="/hello"/><bean></bean></mvc:interceptor></mvc:interceptors>
- 现在在 SpringBoot中 ,编写一个配置类(用 @Configuration 注解标注的类),并且继承 WebMvcConfigurerAdapter,但是
不要标注@EnableWebMvc注解。这样既可以保留自动配置功能,又能使用我们自己扩展的配置。
package com.sunxiaping.springboot.config;import org.springframework.context.annotation.Configuration;import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;@Configurationpublic class SpringMvcConfig extends WebMvcConfigurerAdapter {@Overridepublic void addViewControllers(ViewControllerRegistry registry) {registry.addViewController("/index").setViewName("index");registry.addViewController("/").setViewName("index");registry.addViewController("/index.html").setViewName("index");registry.addViewController("/index.htm").setViewName("index");}}
原理:
- ① WebMvcAutoConfiguration 是 SpringMVC 的自动配置类。
② 在做其他自动配置的时候,会导入 @Import(EnableWebMvcConfiguration.class) 。
@Configuration@ConditionalOnWebApplication@ConditionalOnClass({ Servlet.class, DispatcherServlet.class,WebMvcConfigurerAdapter.class })@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)@AutoConfigureAfter({ DispatcherServletAutoConfiguration.class,ValidationAutoConfiguration.class })public class WebMvcAutoConfiguration {@Configuration@Import(EnableWebMvcConfiguration.class)@EnableConfigurationProperties({ WebMvcProperties.class, ResourceProperties.class })public static class WebMvcAutoConfigurationAdapter extends WebMvcConfigurerAdapter {//其他略}@Configurationpublic static class EnableWebMvcConfiguration extends DelegatingWebMvcConfiguration {}//其他略}
③ 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)) { this.configurers.addWebMvcConfigurers(configurers);
} }
//循环遍历所有的WebMvcConfigurer,并将每个WebMvcConfigurer配置的ViewController加入到容器中 //这样就可以将所有的WebMvcConfigurer的相关配置一起作用 @Override protected void addViewControllers(ViewControllerRegistry registry) { this.configurers.addViewControllers(registry); }
//其他略
}
- ④ 容器中的所有 WebMvcConfigurer 都会一起起作用。- ⑤ 我们自己配置类也会被调用。<a name="ebd444af"></a>## 4.3 全面接管 SpringMVC- SpringBoot 对 SpringMVC 的自动配置不需要了,所有的都需要我们配置。- 只需要在配置类中加 `@EnableWebMvc` 注解即可。```javapackage com.sunxiaping.springboot.config;import org.springframework.context.annotation.Configuration;import org.springframework.web.servlet.config.annotation.EnableWebMvc;import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;@Configuration@EnableWebMvcpublic class SpringMvcConfig extends WebMvcConfigurerAdapter {@Overridepublic void addViewControllers(ViewControllerRegistry registry) {registry.addViewController("/index").setViewName("index");registry.addViewController("/").setViewName("index");registry.addViewController("/index.html").setViewName("index");registry.addViewController("/index.htm").setViewName("index");}}
原理如下:
@EnableWebMvc 注解源码如下:
@Retention(RetentionPolicy.RUNTIME)@Target(ElementType.TYPE)@Documented@Import(DelegatingWebMvcConfiguration.class) //会向容器中导入DelegatingWebMvcConfiguration组件public @interface EnableWebMvc {}
DelegatingWebMvcConfiguration 的部分源码如下:
@Configurationpublic class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport {//其他略}
WebMvcAutoConfiguration 的部分源码如下:
@Configuration@ConditionalOnWebApplication@ConditionalOnClass({ Servlet.class, DispatcherServlet.class,WebMvcConfigurerAdapter.class })@ConditionalOnMissingBean(WebMvcConfigurationSupport.class) //只有在容器中没有WebMvcConfigurationSupport组件的时候,WebMvcAutoConfiguration才会起效,而@EnableWebMvc会将WebMvcConfigurationSupport组件导入到容器中@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)@AutoConfigureAfter({ DispatcherServletAutoConfiguration.class,ValidationAutoConfiguration.class })public class WebMvcAutoConfiguration {//其他略}
5 如何修改 SpringBoot 的默认配置
- ① SpringBoot 在自动配置很多组件的时候,先看容器中有没有用户自己配置(@Bean 、@Component ),如果有,就使用用户自己配置的;如果没有,才自动配置;如果有些组件可以有多个(比如 ViewResolver ),就将用户配置的和自己默认的组合起来。
- ② 在 SpringBoot 中会有非常多的 xxxConfigurer 帮助我们进行扩展配置。
- ③ 在 SpringBoot 中会有非常多的 xxxCustomizer 帮助我们进行定制配置。
6 错误处理机制
6.1 SpringBoot 错误的默认效果
- 如果是浏览器:返回一个默认的错误页面:

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

原理:可以参照 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(),
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,
ResourceProperties resourceProperties) {this.applicationContext = applicationContext;this.resourceProperties = resourceProperties;
}
//给容器中添加DefaultErrorViewResolver组件
@Bean @ConditionalOnBean(DispatcherServlet.class) @ConditionalOnMissingBean public DefaultErrorViewResolver conventionErrorViewResolver() {
return new DefaultErrorViewResolver(this.applicationContext,this.resourceProperties);
} } //其他略
}
- ErrorPageCustomizer:```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 {/*** {@link EmbeddedServletContainerCustomizer} that configures the container's error* pages.*/private static class ErrorPageCustomizer implements ErrorPageRegistrar, Ordered {private final ServerProperties properties;protected ErrorPageCustomizer(ServerProperties properties) {this.properties = properties;}// @Value("${error.path:/error}")//getPath() = private String path = "/error";//系统出现错误以后会来到error请求进行处理。就如同在web.xml中配置错误页面规则@Overridepublic void registerErrorPages(ErrorPageRegistry errorPageRegistry) {ErrorPage errorPage = new ErrorPage(this.properties.getServletPrefix()+ this.properties.getError().getPath());errorPageRegistry.addErrorPages(errorPage);}@Overridepublic int getOrder() {return 0;}}//其他略}
BasicErrorController:处理默认的 /error 请求。
@Controller@RequestMapping("${server.error.path:${error.path:/error}}")public class BasicErrorController extends AbstractErrorController {//产生HTML类型的数据,浏览器发送的请求来到这个方法处理@RequestMapping(produces = "text/html")public ModelAndView errorHtml(HttpServletRequest request,HttpServletResponse response) {HttpStatus status = getStatus(request);//getErrorAttributes其实就是调用的DefaultErrorAttributes的getErrorAttributes方法,而DefaultErrorAttributes是从容器中获取的。Map<String, Object> model = Collections.unmodifiableMap(getErrorAttributes(request, isIncludeStackTrace(request, MediaType.TEXT_HTML)));response.setStatus(status.value());ModelAndView modelAndView = resolveErrorView(request, response, status, model);return (modelAndView == null ? new ModelAndView("error", model) : modelAndView);}//产生JSON数据,其他客户端来到这个方法处理@RequestMapping@ResponseBodypublic ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {Map<String, Object> body = getErrorAttributes(request,isIncludeStackTrace(request, MediaType.ALL));HttpStatus status = getStatus(request);return new ResponseEntity<Map<String, Object>>(body, status);}//略}
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, Map<String, Object> model) {
ModelAndView modelAndView = resolve(String.valueOf(status), 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
model) { //默认SpringBoot可以去找到一个页面 比如:/error/404 String errorViewName = “error/“ + viewName; //模板引擎可以解析这个页面地址就用模板引擎解析 TemplateAvailabilityProvider provider = this.templateAvailabilityProviders .getProvider(errorViewName, this.applicationContext);
if (provider != null) {
//模板引擎可用的情况下返回errorViewName指定的视图地址return new ModelAndView(errorViewName, model);
} //模板引擎不可用,就在静态资源文件夹下找errorViewName对象的页面 return resolveResource(errorViewName, model); }
private ModelAndView resolveResource(String viewName, Map
model) { for (String location : this.resourceProperties.getStaticLocations()) { try {Resource resource = this.applicationContext.getResource(location);resource = resource.createRelative(viewName + ".html");if (resource.exists()) {return new ModelAndView(new HtmlResourceView(resource), model);}}catch (Exception ex) {}
} return null; }
//其他略
}
- DefaultErrorAttributes:```java@Order(Ordered.HIGHEST_PRECEDENCE)public class DefaultErrorAttributes{//帮助我们在页面中共享信息@Overridepublic Map<String, Object> getErrorAttributes(RequestAttributes requestAttributes,boolean includeStackTrace) {Map<String, Object> errorAttributes = new LinkedHashMap<String, Object>();errorAttributes.put("timestamp", new Date());addStatus(errorAttributes, requestAttributes);addErrorDetails(errorAttributes, requestAttributes, includeStackTrace);addPath(errorAttributes, requestAttributes);return errorAttributes;}}
- 步骤:
- 一旦系统出现 4xx 或者 5xx 之类的错误,ErrorPageCustomizer 就会生效(定制错误的响应规则),就会来到 /error 请求,就会被 BasicErrorController 处理。
- 响应页面:去哪个页面是由 DefaultErrorViewResolver 解析得到的。
public abstract class AbstractErrorController implements ErrorController {/*** Resolve any specific error views. By default this method delegates to* {@link ErrorViewResolver ErrorViewResolvers}.* @param request the request* @param response the response* @param status the HTTP status* @param model the suggested model* @return a specific {@link ModelAndView} or {@code null} if the default should be* used* @since 1.4.0*/protected ModelAndView resolveErrorView(HttpServletRequest request,HttpServletResponse response, HttpStatus status, Map<String, Object> model) {for (ErrorViewResolver resolver : this.errorViewResolvers) {ModelAndView modelAndView = resolver.resolveErrorView(request, status, model);if (modelAndView != null) {return modelAndView;}}return null;}//其他略}
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 数据:
package com.sunxiaping.springboot.config;import org.springframework.web.bind.annotation.ControllerAdvice;import org.springframework.web.bind.annotation.ExceptionHandler;import org.springframework.web.bind.annotation.ResponseBody;import java.util.HashMap;import java.util.Map;/*** @ControllerAdvice标注的类:就是对控制器进行增强,在这个类中,可以使用@InitBinder、@InitBinder和@ModelAttribute,这些功能都可以被所有的控制器共享*/@ControllerAdvicepublic class SelfExceptionHandler {//浏览器客户端返回的都是JSON@ExceptionHandler(value = SelfException.class)@ResponseBodypublic Map<String,Object> handleException(SelfException e){Map<String,Object> map = new HashMap<>();map.put("code",1);map.put("msg",e.getMessage());return map;}}
- ② 转发到 /error 进行自适应响应效果处理:
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,这些功能都可以被所有的控制器共享*/@ControllerAdvicepublic class SelfExceptionHandler {@ExceptionHandler(value = SelfException.class)public String handleException(HttpServletRequest request, HttpServletResponse response, SelfException e) {Map<String, Object> map = new HashMap<>();//传入我们自己的错误状态码,否则就不会进入定制的错误页面的解析流程request.setAttribute("javax.servlet.error.status_code", 500);map.put("code", 1);map.put("msg", e.getMessage());return "forward:/error";}}
- ③ 将我们的定制数据携带出去:
- 出现错误以后,会来到 /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) {
Map<String, Object> map = new HashMap<>();//传入我们自己的错误状态码request.setAttribute("javax.servlet.error.status_code", 500);map.put("code", 1);map.put("msg", e.getMessage());request.setAttribute("ext", map);return "forward:/error";
}
}
```javapackage com.sunxiaping.springboot.config;import org.springframework.boot.autoconfigure.web.DefaultErrorAttributes;import org.springframework.stereotype.Component;import org.springframework.web.context.request.RequestAttributes;import java.util.Map;@Componentpublic class SelfErrorAttributes extends DefaultErrorAttributes {@Overridepublic Map<String, Object> getErrorAttributes(RequestAttributes requestAttributes, boolean includeStackTrace) {Map<String, Object> map = super.getErrorAttributes(requestAttributes, includeStackTrace);Object ext = requestAttributes.getAttribute("ext", RequestAttributes.SCOPE_REQUEST);map.put("ext", ext);return map;}}
7 配置嵌入式 Servlet 容器
7.1 如何定制和修改 Servlet 容器的相关配置
7.1.1 修改和 server 有关的配置( ServerProperties )
server:port: 8081context-path: /springtomcat:uri-encoding: utf-8# 通用的servlet容器配置server:xxx:# Tomcat的设置server:tomcat:xxx:
7.1.2 编写一个 EmbeddedServletContainerCustomizer (嵌入式的 Servlet 容器的定制器)来修改 Servlet 有关配置
package com.sunxiaping.springboot.config;import org.springframework.boot.context.embedded.ConfigurableEmbeddedServletContainer;import org.springframework.boot.context.embedded.EmbeddedServletContainerCustomizer;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;@Configurationpublic class SpringMvcConfig extends WebMvcConfigurerAdapter {@Overridepublic void addViewControllers(ViewControllerRegistry registry) {registry.addViewController("/index").setViewName("index");registry.addViewController("/").setViewName("index");registry.addViewController("/index.html").setViewName("index");registry.addViewController("/index.htm").setViewName("index");}@Beanpublic EmbeddedServletContainerCustomizer embeddedServletContainerCustomizer() {return new EmbeddedServletContainerCustomizer() {/**定制嵌入式的Servlet容器的相关规则* @param container*/@Overridepublic void customize(ConfigurableEmbeddedServletContainer container) {container.setPort(8081);}};}}
7.1.3 注册 Servlet 三大组件
package com.sunxiaping.springboot.servet;import javax.servlet.ServletException;import javax.servlet.http.HttpServlet;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import java.io.IOException;public class SelfServlet extends HttpServlet {@Overrideprotected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {resp.getWriter().println("hello servlet");}@Overrideprotected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {this.doGet(req, resp);}}
package com.sunxiaping.springboot.config;import com.sunxiaping.springboot.servet.SelfServlet;import org.springframework.boot.web.servlet.ServletRegistrationBean;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;@Configurationpublic class ServletConfig {/*** 注册Servlet,向容器中添加ServletRegistrationBean组件* 注册Filter,向容器中添加FilterRegistrationBean组件* 注册Listener,向容器中添加ServletListenerRegistrationBean组件** @return*/@Beanpublic ServletRegistrationBean servletRegistrationBean() {return new ServletRegistrationBean(new SelfServlet(), "/hello");}}
7.2 SpringBoot 支持其他的 Servlet 容器
- SpringBoot 默认支持 Tomcat 、Jetty 、Undertow 。

- Tomcat(默认支持):
<dependency><!-- 引入web模块,默认支持Tomcat --><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency>
- Jetty:
<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><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-jetty</artifactId></dependency>
- Undertow:
<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><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-undertow</artifactId></dependency>
7.3 嵌入式 Servlet 容器自动配置原理
- EmbeddedServletContainerAutoConfiguration:嵌入式的 Servlet 容器自动配置
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)@Configuration@ConditionalOnWebApplication@Import(BeanPostProcessorsRegistrar.class)//BeanPostProcessorsRegistrar:给容器中导入一些组件。//导入了EmbeddedServletContainerCustomizerBeanPostProcessor//后置处理器:Bean初始化前后(创建完对象,还没赋值)执行初始化工作。public class EmbeddedServletContainerAutoConfiguration {@Configuration@ConditionalOnClass({ Servlet.class, Tomcat.class }) //判断当前是否引入了Tomcat的依赖@ConditionalOnMissingBean(value = EmbeddedServletContainerFactory.class, search = SearchStrategy.CURRENT)//判断当前容器没有用户自己定义的EmbeddedServletContainerFactory:嵌入式的Servlet容器工厂;作用是创建嵌入式的Servlet容器public static class EmbeddedTomcat {@Beanpublic TomcatEmbeddedServletContainerFactory tomcatEmbeddedServletContainerFactory() {return new TomcatEmbeddedServletContainerFactory();}}/*** Nested configuration if Jetty is being used.*/@Configuration@ConditionalOnClass({ Servlet.class, Server.class, Loader.class,WebAppContext.class })@ConditionalOnMissingBean(value = EmbeddedServletContainerFactory.class, search = SearchStrategy.CURRENT)public static class EmbeddedJetty {@Beanpublic JettyEmbeddedServletContainerFactory jettyEmbeddedServletContainerFactory() {return new JettyEmbeddedServletContainerFactory();}}/*** Nested configuration if Undertow is being used.*/@Configuration@ConditionalOnClass({ Servlet.class, Undertow.class, SslClientAuthMode.class })@ConditionalOnMissingBean(value = EmbeddedServletContainerFactory.class, search = SearchStrategy.CURRENT)public static class EmbeddedUndertow {@Beanpublic UndertowEmbeddedServletContainerFactory undertowEmbeddedServletContainerFactory() {return new UndertowEmbeddedServletContainerFactory();}}//略}
- EmbeddedServletContainerFactory:嵌入式的 Servlet 容器工厂
public interface EmbeddedServletContainerFactory {/*** 获取的嵌入式的Servlet容器*/EmbeddedServletContainer getEmbeddedServletContainer(ServletContextInitializer... initializers);}

- EmbeddedServletContainer:嵌入式的 Servlet 容器。
public interface EmbeddedServletContainer {/*** Starts the embedded servlet container. Calling this method on an already started* container has no effect.* @throws EmbeddedServletContainerException if the container cannot be started*/void start() throws EmbeddedServletContainerException;/*** Stops the embedded servlet container. Calling this method on an already stopped* container has no effect.* @throws EmbeddedServletContainerException if the container cannot be stopped*/void stop() throws EmbeddedServletContainerException;/*** Return the port this server is listening on.* @return the port (or -1 if none)*/int getPort();}

- 以 TomcatEmbeddedServletContainerFactory 为例:
public class TomcatEmbeddedServletContainerFactoryextends AbstractEmbeddedServletContainerFactory implements ResourceLoaderAware {@Overridepublic EmbeddedServletContainer getEmbeddedServletContainer(ServletContextInitializer... initializers) {//创建一个TomcatTomcat tomcat = new Tomcat();//配置TOmcat的基本环境File baseDir = (this.baseDirectory != null ? this.baseDirectory: createTempDir("tomcat"));tomcat.setBaseDir(baseDir.getAbsolutePath());Connector connector = new Connector(this.protocol);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传入进去,返回一个嵌入式的Servlet容器,并且启动Tomcat服务器return getTomcatEmbeddedServletContainer(tomcat);}protected TomcatEmbeddedServletContainer getTomcatEmbeddedServletContainer(Tomcat tomcat) {return new TomcatEmbeddedServletContainer(tomcat, getPort() >= 0);}public TomcatEmbeddedServletContainer(Tomcat tomcat, boolean autoStart) {Assert.notNull(tomcat, "Tomcat Server must not be null");this.tomcat = tomcat;this.autoStart = autoStart;initialize();}private void initialize() throws EmbeddedServletContainerException {TomcatEmbeddedServletContainer.logger.info("Tomcat initialized with port(s): " + getPortsDescription(false));synchronized (this.monitor) {try {addInstanceIdToEngineName();try {// Remove service connectors to that protocol binding doesn't happen// yetremoveServiceConnectors();// 启动Tomcatthis.tomcat.start();// We can re-throw failure exception directly in the main threadrethrowDeferredStartupExceptions();Context context = findContext();try {ContextBindings.bindClassLoader(context, getNamingToken(context),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 shutdownstartDaemonAwaitThread();}catch (Exception ex) {containerCounter.decrementAndGet();throw ex;}}catch (Exception ex) {throw new EmbeddedServletContainerException("Unable to start embedded Tomcat", ex);}}}//略}
- 我们对嵌入式容器的配置修改是怎么修改的?
- ① ServerProperties。ServerProperties 其实是 EmbeddedServletContainerCustomizer 的子类。
- ② EmbeddedServletContainerCustomizer :定制器帮我们修改了 Servlet 容器的配置。
- 容器中导入了 EmbeddedServletContainerCustomizerBeanPostProcessor :
public class EmbeddedServletContainerCustomizerBeanPostProcessorimplements BeanPostProcessor, BeanFactoryAware {//在初始化之前@Overridepublic Object postProcessBeforeInitialization(Object bean, String beanName)throws BeansException {//如果当前初始化的是一个ConfigurableEmbeddedServletContainer类型的组件if (bean instanceof ConfigurableEmbeddedServletContainer) {//EmbeddedServletContainerCustomizer定制器,调用每个定制器的customize方法,来给Servlet容器进行属性赋值postProcessBeforeInitialization((ConfigurableEmbeddedServletContainer) bean);}return bean;}@Overridepublic Object postProcessAfterInitialization(Object bean, String beanName)throws BeansException {return bean;}private void postProcessBeforeInitialization(ConfigurableEmbeddedServletContainer bean) {for (EmbeddedServletContainerCustomizer customizer : getCustomizers()) {customizer.customize(bean);}}private Collection<EmbeddedServletContainerCustomizer> getCustomizers() {if (this.customizers == null) {// Look up does not include the parent contextthis.customizers = new ArrayList<EmbeddedServletContainerCustomizer>(this.beanFactory//从容器中获取所有EmbeddedServletContainerCustomizer类型的组件//所以定制容器,可以给容器中配置EmbeddedServletContainerCustomizer类型的组件.getBeansOfType(EmbeddedServletContainerCustomizer.class,false, false).values());Collections.sort(this.customizers, AnnotationAwareOrderComparator.INSTANCE);this.customizers = Collections.unmodifiableList(this.customizers);}return this.customizers;}//其他略}
总结:
- ① 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 容器。
@Overridepublic 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();// 刷新容器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();}}}
- ④ onRefresh(); web 的 IOC 容器重写了 onRefresh 方法。
- ⑤ web 的IOC 容器会创建嵌入式的 Servlet 容器。
public class EmbeddedWebApplicationContext extends GenericWebApplicationContext {@Overrideprotected void onRefresh() {super.onRefresh();try {//创建嵌入式的Servlet容器createEmbeddedServletContainer();}catch (Throwable ex) {throw new ApplicationContextException("Unable to start embedded container",ex);}}private void createEmbeddedServletContainer() {EmbeddedServletContainer localContainer = this.embeddedServletContainer;ServletContext localServletContext = getServletContext();if (localContainer == null && localServletContext == null) {//获取嵌入式的Servlet容器工厂//从IOC容器中获取EmbeddedServletContainerFactory组件。TomcatEmbeddedServletContainerFactory创建对象,后置处理器一看是这个对象,就获取所有的定制器来先定制Servlet容器的相关配置EmbeddedServletContainerFactory containerFactory = getEmbeddedServletContainerFactory();this.embeddedServletContainer = containerFactory.getEmbeddedServletContainer(getSelfInitializer());}else if (localServletContext != null) {try {getSelfInitializer().onStartup(localServletContext);}catch (ServletException ex) {throw new ApplicationContextException("Cannot initialize servlet context",ex);}}initPropertySources();}//其他略}
- ⑥ 获取嵌入式的 Servlet 容器工厂:
public class EmbeddedWebApplicationContext extends GenericWebApplicationContext {@Overrideprotected void onRefresh() {super.onRefresh();try {//创建嵌入式的Servlet容器createEmbeddedServletContainer();}catch (Throwable ex) {throw new ApplicationContextException("Unable to start embedded container",ex);}}private void createEmbeddedServletContainer() {EmbeddedServletContainer localContainer = this.embeddedServletContainer;ServletContext localServletContext = getServletContext();if (localContainer == null && localServletContext == null) {//获取嵌入式的Servlet容器工厂//从IOC容器中获取EmbeddedServletContainerFactory组件。TomcatEmbeddedServletContainerFactory创建对象,后置处理器一看是这个对象,就获取所有的定制器来先定制Servlet容器的相关配置EmbeddedServletContainerFactory containerFactory = getEmbeddedServletContainerFactory();this.embeddedServletContainer = containerFactory.getEmbeddedServletContainer(getSelfInitializer());}else if (localServletContext != null) {try {getSelfInitializer().onStartup(localServletContext);}catch (ServletException ex) {throw new ApplicationContextException("Cannot initialize servlet context",ex);}}initPropertySources();}//其他略}
- ⑦ 使用容器工厂获取嵌入式的 Servle t容器:
this.embeddedServletContainer = containerFactory.getEmbeddedServletContainer(getSelfInitializer()); - ⑧ 嵌入式的 Servlet 容器创建对象并启动 Servlet 容器。
