Web 场景
Spring mvc 自动配置原理
mvc 自动配置类
org.springframework.boot.autoconfigure.web.servlet
包下有WebMvcAutoConfiguration
类,用于完成 web 场景的自动化配置
// 指明在以下自动配置后执行
@AutoConfiguration(
after = {DispatcherServletAutoConfiguration.class, TaskExecutionAutoConfiguration.class, ValidationAutoConfiguration.class}
)
// 指明在类型为SERVLET,REACTIVE的web应用情况下生效
@ConditionalOnWebApplication(
type = Type.SERVLET
)
// 指明当以下类存在时才创建
@ConditionalOnClass({Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class})
// 指明当以下bean不存在时才创建
@ConditionalOnMissingBean({WebMvcConfigurationSupport.class})
// 优先级
@AutoConfigureOrder(-2147483638)
// 引入运行时提示
@ImportRuntimeHints({WebResourcesRuntimeHints.class})
public class WebMvcAutoConfiguration{
// 过滤页面表单提交的Rest请求(用以支持PUT,DELETE请求)
@Bean
@ConditionalOnMissingBean({HiddenHttpMethodFilter.class})
@ConditionalOnProperty(
prefix = "spring.mvc.hiddenmethod.filter",
name = {"enabled"}
)
public OrderedHiddenHttpMethodFilter hiddenHttpMethodFilter() {
return new OrderedHiddenHttpMethodFilter();
}
// 过滤表单内容
@Bean
@ConditionalOnMissingBean({FormContentFilter.class})
@ConditionalOnProperty(
prefix = "spring.mvc.formcontent.filter",
name = {"enabled"},
matchIfMissing = true
)
public OrderedFormContentFilter formContentFilter() {
return new OrderedFormContentFilter();
}
// 负责注册并配置 Spring MVC 的各种组件(管理着配置文件中 spring.web.xxx开头的配置项)
// SpringBoot会给容器中放入WebMvcConfigurationSupport组件(是mvc的基本实现并包含了WebMvcConfigurer接口中的方法)
// 若我们自己放了该组件,Boot的WebMvcAutoConfiguration就会失效
// 当调用DelegatingWebMvcConfiguration的方法配置底层规则,它会调用WebMvcConfigurer的配置底层方法
@Configuration(
proxyBeanMethods = false
)
@EnableConfigurationProperties({WebProperties.class})
public static class EnableWebMvcConfiguration extends DelegatingWebMvcConfiguration implements ResourceLoaderAware{}
//在容器中放入`WebMvcConfigurer`组件,该组件给SpringMVC添加定制功能,所有功能最终会和配置文件进行绑定
@Configuration(
proxyBeanMethods = false
)
@Import({EnableWebMvcConfiguration.class})
@EnableConfigurationProperties({WebMvcProperties.class, WebProperties.class})
@Order(0)
public static class WebMvcAutoConfigurationAdapter implements WebMvcConfigurer, ServletContextAware{
// 配置静态资源
public void addResourceHandlers(ResourceHandlerRegistry registry) {
if (!this.resourceProperties.isAddMappings()) {
logger.debug("Default resource handling disabled");
} else {
// 访问this.mvcProperties.getWebjarsPathPattern()即"/webjars/**"
// 去 "classpath:/META-INF/resources/webjars/"下寻找资源
this.addResourceHandler(registry, this.mvcProperties.getWebjarsPathPattern(), "classpath:/META-INF/resources/webjars/");
// 访问this.mvcProperties.getStaticPathPattern()即"/**"
// 去this.resourceProperties即WebProperties.getResources()即{"classpath:/META-INF/resources/", "classpath:/resources/", "classpath:/static/", "classpath:/public/"}下寻找资源
this.addResourceHandler(registry, this.mvcProperties.getStaticPathPattern(), (registration) -> {
registration.addResourceLocations(this.resourceProperties.getStaticLocations());
if (this.servletContext != null) {
ServletContextResource resource = new ServletContextResource(this.servletContext, "/");
registration.addResourceLocations(new Resource[]{resource});
}
});
}
}
}
}
WebMVC 配置接口
package org.springframework.web.servlet.config.annotation;
// 该接口提供了配置SpringMVC底层的所有组件入口
public interface WebMvcConfigurer {
// 路径匹配
default void configurePathMatch(PathMatchConfigurer configurer) {
}
// 内容协商
default void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
}
// 异步支持
default void configureAsyncSupport(AsyncSupportConfigurer configurer) {
}
// 默认处理,默认接受'/'
default void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
}
// 格式化器
default void addFormatters(FormatterRegistry registry) {
}
// 拦截器
default void addInterceptors(InterceptorRegistry registry) {
}
// 添加资源处理器,处理静态资源规则
default void addResourceHandlers(ResourceHandlerRegistry registry) {
}
// 跨域
default void addCorsMappings(CorsRegistry registry) {
}
// 视图控制器
default void addViewControllers(ViewControllerRegistry registry) {
}
// 视图解析
default void configureViewResolvers(ViewResolverRegistry registry) {
}
// 参数解析器
default void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
}
// 返回值处理器
default void addReturnValueHandlers(List<HandlerMethodReturnValueHandler> handlers) {
}
// 消息转换器
default void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
}
// 扩展异常解析器
default void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
}
// 配置异常解析器
default void configureHandlerExceptionResolvers(List<HandlerExceptionResolver> resolvers) {
}
// 异常解析器
default void extendHandlerExceptionResolvers(List<HandlerExceptionResolver> resolvers) {
}
@Nullable
default Validator getValidator() {
return null;
}
@Nullable
default MessageCodesResolver getMessageCodesResolver() {
return null;
}
}
WebMvcConfigurationSupport配置类
是SpringMVC提供的扩展类,用于提供拦截器、资源处理器等注册功能
若有配置类继承了该类,则会使自动配置类WebMvcAutoConfiguration失效
public class WebMvcConfigurationSupport implements ApplicationContextAware, ServletContextAware{
}
配置类和配置接口区别
- WebMvcConfigurer:
- 是一个接口,它提供了多个回调方法,可以用于自定义Spring MVC的配置(如消息转换器、拦截器等)。
- 我们在使用时只需要实现该接口,重写其中的方法即可。
- 主要用于添加或修改Spring MVC的配置,如添加拦截器,自定义消息转换器等。
- WebMvcConfigurationSupport:
- 是一个抽象类,它也提供了多个回调方法,用于自定义Spring MVC的配置,但是需要继承该类并重写其中的方法。
- 主要用于完全自定义Spring MVC的配置
- 继承该类会覆盖Spring MVC的部分默认配置
web开发
自动配置
- 整合web场景
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
- 引入
autoconfigure
功能 - @EnableAutoConfiguration注解使用@Import(AutoConfigurationImportSelector.class)批量导入组件
- 加载 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports 文件中配置的所有组件
- 绑定配置文件的配置项
默认效果
默认配置:
- 包含了 ContentNegotiatingViewResolver 和 BeanNameViewResolver 组件,方便视图解析
- 默认的静态资源处理机制: 静态资源放在 static 文件夹下即可直接访问
- 自动注册了 Converter,GenericConverter,Formatter组件,适配常见数据类型转换和格式化需求
- 支持 HttpMessageConverters,可以方便返回json等数据类型
- 注册 MessageCodesResolver,方便国际化及错误消息处理
- 支持 静态 index.html
- 自动使用ConfigurableWebBindingInitializer,实现消息处理、数据绑定、类型转化、数据校验等功能
重要:
- 如果想保持 boot mvc 的默认配置,并且自定义更多的 mvc 配置,如:interceptors, formatters, view controllers 等。可以使用@Configuration注解添加一个 WebMvcConfigurer 类型的配置类,并不要标注 @EnableWebMvc
- 如果想保持 boot mvc 的默认配置,但要自定义核心组件实例,比如:RequestMappingHandlerMapping, RequestMappingHandlerAdapter, 或ExceptionHandlerExceptionResolver,给容器中放一个 WebMvcRegistrations 组件即可
- 如果想全面接管 Spring MVC,@Configuration 标注一个配置类,并加上 @EnableWebMvc注解,实现 WebMvcConfigurer 接口
静态资源
默认规则
静态资源映射
静态资源映射规则在 WebMvcAutoConfiguration 中进行了定义:
- /webjars/** 的所有路径 资源都在 classpath:/META-INF/resources/webjars/
- /** 的所有路径 资源都在 classpath:/META-INF/resources/、classpath:/resources/、classpath:/static/、classpath:/public/
- 所有静态资源都定义了缓存规则。【浏览器访问过一次,就会缓存一段时间】,但此功能参数无默认值
a. period: 缓存间隔。 默认 0S;
b. cacheControl:缓存控制。 默认无;
c. useLastModified:是否使用lastModified头。 默认 false;
静态资源缓存
如前面所述
- 所有静态资源都定义了缓存规则。【浏览器访问过一次,就会缓存一段时间】,但此功能参数无默认值
a. period: 缓存间隔。 默认 0S;
b. cacheControl:缓存控制。 默认无;
c. useLastModified:是否使用lastModified头。 默认 false;
欢迎页
欢迎页规则在 WebMvcAutoConfiguration 中进行了定义:
- 在静态资源目录下找 index.html
- 没有就在 templates下找index模板页
Favicon
- 在静态资源目录下找 favicon.ico
自定义静态资源规则
配置方式
#1、spring.web:
# 1.配置国际化的区域信息
# 2.静态资源策略(开启、处理链、缓存)
#开启静态资源映射规则
spring.web.resources.add-mappings=true
#设置缓存
spring.web.resources.cache.period=3600
##缓存详细合并项控制,覆盖period配置:
## 浏览器第一次请求服务器,服务器告诉浏览器此资源缓存7200秒,7200秒以内的所有此资源访问不用发给服务器请求,7200秒以后发请求给服务器
spring.web.resources.cache.cachecontrol.max-age=7200
## 共享缓存
spring.web.resources.cache.cachecontrol.cache-public=true
#使用资源 last-modified 时间,来对比服务器和浏览器的资源是否相同没有变化。相同返回 304
spring.web.resources.cache.use-last-modified=true
#自定义静态资源文件夹位置
spring.web.resources.static-locations=classpath:/a/,classpath:/b/,classpath:/static/
#2、 spring.mvc
## 2.1. 自定义webjars路径前缀
spring.mvc.webjars-path-pattern=/wj/**
## 2.2. 静态资源访问路径前缀
spring.mvc.static-path-pattern=/static/**
java代码方式
- 容器中只要有一个 WebMvcConfigurer 组件。配置的底层行为都会生效
- @EnableWebMvc //禁用boot的默认配置
@Configuration //这是一个配置类
public class MyConfig implements WebMvcConfigurer {
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
//保留以前规则
//自己写新的规则。
registry.addResourceHandler("/static/**")
.addResourceLocations("classpath:/a/","classpath:/b/")
.setCacheControl(CacheControl.maxAge(1180, TimeUnit.SECONDS));
}
}
@Configuration //这是一个配置类,给容器中放一个 WebMvcConfigurer 组件,就能自定义底层
public class MyConfig /*implements WebMvcConfigurer*/ {
@Bean
public WebMvcConfigurer webMvcConfigurer(){
return new WebMvcConfigurer() {
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/static/**")
.addResourceLocations("classpath:/a/", "classpath:/b/")
.setCacheControl(CacheControl.maxAge(1180, TimeUnit.SECONDS));
}
};
}
}
路径匹配
- Ant风格路径用法
Ant 风格的路径模式语法具有以下规则:
- *:表示任意数量的字符。
- ?:表示任意一个字符。
- **:表示任意数量的目录。
- {}:表示一个命名的模式占位符。
- []:表示字符集合,例如[a-z]表示小写字母。
例如: - *.html 匹配任意名称,扩展名为.html的文件。
- /folder1//.java 匹配在folder1目录下的任意两级目录下的.java文件。
- /folder2/**/*.jsp 匹配在folder2目录下任意目录深度的.jsp文件。
- /{type}/{id}.html 匹配任意文件名为{id}.html,在任意命名的{type}目录下的文件。
注意:Ant 风格的路径模式语法中的特殊字符需要转义,如: - 要匹配文件路径中的星号,则需要转义为\*。
- 要匹配文件路径中的问号,则需要转义为\?。
- 模式切换
AntPathMatcher 与 PathPatternParser
- PathPatternParser 兼容 AntPathMatcher语法,并支持更多类型的路径模式
- PathPatternParser “**” 多段匹配的支持仅允许在模式末尾使用
@GetMapping("/a*/b?/{p1:[a-f]+}")
public String hello(HttpServletRequest request,
@PathVariable("p1") String path) {
log.info("路径变量p1: {}", path);
//获取请求路径
String uri = request.getRequestURI();
return uri;
}
# 改变路径匹配策略:
# ant_path_matcher 老版策略;
# path_pattern_parser 新版策略;
spring.mvc.pathmatch.matching-strategy=ant_path_matcher
内容协商
多端内容适配
默认规则
- 基于请求头的内容协商(默认开启)
- 客户端向服务端发送请求,携带HTTP标准的Accept请求头
- Accept: application/json、text/xml、text/yaml
- 服务端根据客户端请求头期望的数据类型进行动态返回
- 客户端向服务端发送请求,携带HTTP标准的Accept请求头
- 基于请求参数内容协商:(需要开启)
- 发送请求 GET /projects/spring-boot?format=json
- 匹配到 @GetMapping(“/projects/spring-boot”)
- 根据参数协商,优先返回 json 类型数据【需要开启参数匹配设置】
- 发送请求 GET /projects/spring-boot?format=xml,优先返回 xml 类型数据
配置协商规则与支持类型
#修改内容协商方式
#使用参数进行内容协商
spring.mvc.contentnegotiation.favor-parameter=true
#自定义参数名,默认为format
spring.mvc.contentnegotiation.parameter-name=myparam
#自定义内容类型
spring.mvc.contentnegotiation.media-types.yaml=text/yaml
自定义内容返回
添加yaml返回支持
- 导入依赖
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-yaml</artifactId>
</dependency>
- 将对象写出成yaml
public static void main(String[] args) throws JsonProcessingException {
Person person = new Person();
person.setId(1L);
person.setUserName("张三");
person.setEmail("aaa@qq.com");
person.setAge(18);
YAMLFactory factory = new YAMLFactory().disable(YAMLGenerator.Feature.WRITE_DOC_START_MARKER);
ObjectMapper mapper = new ObjectMapper(factory);
String s = mapper.writeValueAsString(person);
System.out.println(s);
}
- 编写配置
#新增一种媒体类型
spring.mvc.contentnegotiation.media-types.yaml=text/yaml
- 添加HttpMessageConverter组件,专门负责把对象写出为yaml格式
@Bean
public WebMvcConfigurer webMvcConfigurer(){
return new WebMvcConfigurer() {
@Override //配置一个能把对象转为yaml的messageConverter
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.add(new MyYamlHttpMessageConverter());
}
};
}
内容协商原理
@ResponseBody由HttpMessageConverter处理
- 如果controller方法的返回值标注了 @ResponseBody 注解
1.1. 请求进来先来到DispatcherServlet的doDispatch()进行处理
1.2. 找到一个 HandlerAdapter 适配器。利用适配器执行目标方法
1.3. RequestMappingHandlerAdapter来执行,调用invokeHandlerMethod()来执行目标方法
1.4. 目标方法执行之前,准备好两个东西
1.4.1. HandlerMethodArgumentResolver:参数解析器,确定目标方法每个参数值
1.4.2. HandlerMethodReturnValueHandler:返回值处理器,确定目标方法的返回值改怎么处理
1.5. RequestMappingHandlerAdapter 里面的invokeAndHandle()真正执行目标方法
1.6. 目标方法执行完成,会返回返回值对象
1.7. 找到一个合适的返回值处理器 HandlerMethodReturnValueHandler
1.8. 最终找到 RequestResponseBodyMethodProcessor能处理 标注了 @ResponseBody注解的方法
1.9. RequestResponseBodyMethodProcessor 调用writeWithMessageConverters ,利用MessageConverter把返回值写出去 - HttpMessageConverter 会先进行内容协商
2.1. 遍历所有的MessageConverter看谁支持这种内容类型的数据
2.2. 最终因为要json所以MappingJackson2HttpMessageConverter支持写出json
2.3. jackson用ObjectMapper把对象写出去
WebMvcAutoConfiguration提供几种默认HttpMessageConverters
- EnableWebMvcConfiguration通过 addDefaultHttpMessageConverters添加了默认的MessageConverter;如下:
- ByteArrayHttpMessageConverter: 支持字节数据读写
- StringHttpMessageConverter: 支持字符串读写
- ResourceHttpMessageConverter:支持资源读写
- ResourceRegionHttpMessageConverter: 支持分区资源写出
- AllEncompassingFormHttpMessageConverter:支持表单xml/json读写
- MappingJackson2HttpMessageConverter: 支持请求响应体Json读写
模板引擎
模板引擎页面默认放在 src/main/resources/templates
SpringBoot 包含以下模板引擎的自动配置
- FreeMarker
- Groovy
- Thymeleaf
- Mustache
Thymeleaf整合
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
自动配置原理
- 开启了 org.springframework.boot.autoconfigure.thymeleaf.ThymeleafAutoConfiguration 自动配置
- 属性绑定在 ThymeleafProperties 中,对应配置文件 spring.thymeleaf 内容
- 所有的模板页面默认在 classpath:/templates文件夹下
- 默认效果
a. 所有的模板页面在 classpath:/templates/下面找
b. 找后缀名为.html的页面
基础语法
- 核心用法
th:xxx:动态渲染指定的 html 标签属性值、或者th指令(遍历、判断等)
- th:text:标签体内文本值渲染
- th:utext:不会转义,显示为html原本的样子。
- th:属性:标签指定属性渲染
- th:attr:标签任意属性渲染
- th:ifth:each…:其他th指令
表达式:用来动态取值
- :变量取值;使用 m o d e l 共享给页面的值都直接用 {}:变量取值;使用model共享给页面的值都直接用 :变量取值;使用model共享给页面的值都直接用{}
- @{}:url路径;
- #{}:国际化消息
- ~{}:片段引用
- *{}:变量选择:需要配合th:object绑定对象
系统工具&内置对象
- param:请求参数对象
- session:session对象
- application:application对象
- #execInfo:模板执行信息
- #messages:国际化消息
- #uris:uri/url工具
- #conversions:类型转换工具
- #dates:日期工具,是java.util.Date对象的工具类
- #calendars:类似#dates,只不过是java.util.Calendar对象的工具类
- #temporals: JDK8+ java.time API 工具类
- #numbers:数字操作工具
- #strings:字符串操作
- #objects:对象操作
- #bools:bool操作
- #arrays:array工具
- #lists:list工具
- #sets:set工具
- #maps:map工具
- #aggregates:集合聚合工具(sum、avg)
- #ids:id生成工具
<p th:text="${content}">原内容</p>
<a th:href="${url}">登录</a>
<img src="../../images/gtvglogo.png"
th:attr="src=@{/images/gtvglogo.png},title=#{logo},alt=#{logo}" />
- 属性设置
- th:href=“@{/product/list}”
- th:attr=“class=${active}”
- th:attr=“src=@{/images/gtvglogo.png},title=${logo},alt=#{logo}”
- th:checked=“${user.active}”
<p th:text="${content}">原内容</p>
<a th:href="${url}">登录</a>
<img src="../../images/gtvglogo.png"
th:attr="src=@{/images/gtvglogo.png},title=#{logo},alt=#{logo}" />
- 遍历
<tr th:each="prod : ${prods}">
<td th:text="${prod.name}">Onions</td>
<td th:text="${prod.price}">2.41</td>
<td th:text="${prod.inStock}? #{true} : #{false}">yes</td>
</tr>
<tr th:each="prod,iterStat : ${prods}" th:class="${iterStat.odd}? 'odd'">
<td th:text="${prod.name}">Onions</td>
<td th:text="${prod.price}">2.41</td>
<td th:text="${prod.inStock}? #{true} : #{false}">yes</td>
</tr>
iterStat 有以下属性:
- index:当前遍历元素的索引,从0开始
- count:当前遍历元素的索引,从1开始
- size:需要遍历元素的总数量
- current:当前正在遍历的元素对象
- even/odd:是否偶数/奇数行
- first:是否第一个元素
- last:是否最后一个元素
- 判断
th:if
<a
href="comments.html"
th:href="@{/product/comments(prodId=${prod.id})}"
th:if="${not #lists.isEmpty(prod.comments)}"
>view</a
th:switch
<div th:switch="${user.role}">
<p th:case="'admin'">User is an administrator</p>
<p th:case="#{roles.manager}">User is a manager</p>
<p th:case="*">User is some other thing</p>
</div>
- 行内写法
<p>Hello, [[${session.user.name}]]!</p>
- 变量选择
<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>
<!-- 等同于 -->
<div>
<p>Name: <span th:text="${session.user.firstName}">Sebastian</span>.</p>
<p>Surname: <span th:text="${session.user.lastName}">Pepper</span>.</p>
<p>Nationality: <span th:text="${session.user.nationality}">Saturn</span>.</p>
</div
- 模板布局
- 定义模板: th:fragment
- 引用模板:~{templatename::selector}
- 插入模板:th:insert、th:replace
<footer th:fragment="copy">© 2011 The Good Thymes Virtual Grocery</footer>
<body>
<div th:insert="~{footer :: copy}"></div>
<div th:replace="~{footer :: copy}"></div>
</body>
<body>
结果:
<body>
<div>
<footer>© 2011 The Good Thymes Virtual Grocery</footer>
</div>
<footer>© 2011 The Good Thymes Virtual Grocery</footer>
</body>
</body>
错误处理
默认机制
业务发生错误
|
@ExceptionHandler机制能否处理——否——>@ResponseStatus能否处理——否——>SpringMVC定义的默认错误响应能否处理
|能 |能 |能 |
|____________________________响应错误处理结果_____________________| |
|
均不能处理|
|
|
——————————————————————————————请求转发/error————————————————————————————————————————————————————————|
|
|
BaseErrorController
|
需要json数据———————内容协商————————要页面
| |
DefaultErrorAttributes |精确匹配[404,500]————————————|——先找templates/error
获取错误信息并返回 ______|等错误状态码对应的页面 | |匹配失败
| | |匹配失败 | 再找静态资源目录
| |______|匹配名为[4XX,5XX]的页面——————|
响应错误处理结果<—匹配成功——| |匹配失败
|______|匹配error的视图
| |匹配失败
|______|SpringBoot提供默认名为error的视图
BasicErrorController组件
SpringBoot底层专门用于处理转发给/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 = this.getStatus(request);
Map<String, Object> model = Collections.unmodifiableMap(this.getErrorAttributes(request, this.getErrorAttributeOptions(request, MediaType.TEXT_HTML)));
response.setStatus(status.value());
// 解析错误的自定义视图地址
ModelAndView modelAndView = this.resolveErrorView(request, response, status, model);
// 如果解析不到错误页面的地址,默认的错误页就是 error
return modelAndView != null ? modelAndView : new ModelAndView("error", model);
}
// 用于返回ResponseEntity,JSON
@RequestMapping
public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
HttpStatus status = this.getStatus(request);
if (status == HttpStatus.NO_CONTENT) {
return new ResponseEntity(status);
} else {
Map<String, Object> body = this.getErrorAttributes(request, this.getErrorAttributeOptions(request, MediaType.ALL));
return new ResponseEntity(body, status);
}
}
}
错误视图解析器
@Bean
@ConditionalOnBean({DispatcherServlet.class})
@ConditionalOnMissingBean({ErrorViewResolver.class})
DefaultErrorViewResolver conventionErrorViewResolver() {
return new DefaultErrorViewResolver(this.applicationContext, this.resources);
}
SpringBoot解析自定义错误页的默认规则
@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) {
String errorViewName = "error/" + viewName;
TemplateAvailabilityProvider provider = this.templateAvailabilityProviders.getProvider(errorViewName,
this.applicationContext);
if (provider != null) {
return new ModelAndView(errorViewName, model);
}
return resolveResource(errorViewName, model);
}
private ModelAndView resolveResource(String viewName, Map<String, Object> model) {
for (String location : this.resources.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;
}
SpringBoot默认提供的error的view
@Bean(name = "error")
@ConditionalOnMissingBean(name = "error")
public View defaultErrorView() {
return this.defaultErrorView;
}
封装了JSON格式的错误信息
@Bean
@ConditionalOnMissingBean(value = ErrorAttributes.class, search = SearchStrategy.CURRENT)
public DefaultErrorAttributes errorAttributes() {
return new DefaultErrorAttributes();
}
嵌入式容器
自动配置原理
- SpringBoot 默认嵌入Tomcat作为Servlet容器。
- 自动配置类是ServletWebServerFactoryAutoConfiguration,EmbeddedWebServerFactoryCustomizerAutoConfiguration
- 自动配置类开始分析功能。
xxxxAutoConfiguration
@AutoConfiguration
@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 })
public class ServletWebServerFactoryAutoConfiguration {
}
- ServletWebServerFactoryAutoConfiguration 自动配置了嵌入式容器场景
- 绑定了ServerProperties配置类,所有和服务器有关的配置 server
- ServletWebServerFactoryAutoConfiguration 导入了 嵌入式的三大服务器 Tomcat、Jetty、Undertow
a. 导入 Tomcat、Jetty、Undertow 都有条件注解。系统中有这个类才行(也就是导了包)
b. 默认 Tomcat配置生效。给容器中放 TomcatServletWebServerFactory
c. 都给容器中 ServletWebServerFactory放了一个 web服务器工厂(造web服务器的)
d. web服务器工厂 都有一个功能,getWebServer获取web服务器
e. TomcatServletWebServerFactory 创建了 tomcat。 - ServletWebServerFactory 什么时候会创建 webServer出来。
- ServletWebServerApplicationContextioc容器,启动的时候会调用创建web服务器
- Spring容器刷新(启动)的时候,会预留一个时机,刷新子容器。onRefresh()
- refresh() 容器刷新 十二大步的刷新子容器会调用 onRefresh();
@Override
protected void onRefresh() {
super.onRefresh();
try {
createWebServer();
}
catch (Throwable ex) {
throw new ApplicationContextException("Unable to start web server", ex);
}
}
Web场景的Spring容器启动,在onRefresh的时候,会调用创建web服务器的方法。
Web服务器的创建是通过WebServerFactory搞定的。容器中又会根据导了什么包条件注解,启动相关的 服务器配置,默认EmbeddedTomcat会给容器中放一个 TomcatServletWebServerFactory,导致项目启动,自动创建出Tomcat。
全手动配置SpringMVC
- SpringBoot 默认配置好了 SpringMVC 的所有常用特性。
- 如果我们需要全面接管SpringMVC的所有配置并禁用默认配置,仅需要编写一个WebMvcConfigurer配置类,并标注 @EnableWebMvc 即可
- 全手动模式
- @EnableWebMvc : 禁用默认配置
- WebMvcConfigurer组件:定义MVC的底层行为
WebMvcAutoConfiguration自动配置的规则
WebMvcAutoConfigurationweb场景的自动配置类
- 支持RESTful的filter:HiddenHttpMethodFilter
- 支持非POST请求,请求体携带数据:FormContentFilter
- 导入EnableWebMvcConfiguration:
- RequestMappingHandlerAdapter
- WelcomePageHandlerMapping: 欢迎页功能支持(模板引擎目录、静态资源目录放index.html),项目访问/ 就默认展示这个页面.
- RequestMappingHandlerMapping:找每个请求由谁处理的映射关系
- ExceptionHandlerExceptionResolver:默认的异常解析器
- LocaleResolver:国际化解析器
- ThemeResolver:主题解析器
- FlashMapManager:临时数据共享
- FormattingConversionService: 数据格式化 、类型转化
- Validator: 数据校验JSR303提供的数据校验功能
- WebBindingInitializer:请求参数的封装与绑定
- ContentNegotiationManager:内容协商管理器
- WebMvcAutoConfigurationAdapter配置生效,它是一个WebMvcConfigurer,定义mvc底层组件
- 定义好 WebMvcConfigurer 底层组件默认功能;所有功能详见列表
- 视图解析器:InternalResourceViewResolver
- 视图解析器:BeanNameViewResolver,视图名(controller方法的返回值字符串)就是组件名
- 内容协商解析器:ContentNegotiatingViewResolver
- 请求上下文过滤器:RequestContextFilter: 任意位置直接获取当前请求
- 静态资源链规则
- ProblemDetailsExceptionHandler:错误详情
- SpringMVC内部场景异常被它捕获:
- 定义了MVC默认的底层行为: WebMvcConfigurer
@EnableWebMvc禁用默认行为
- @EnableWebMvc给容器中导入 DelegatingWebMvcConfiguration组件,
他是 WebMvcConfigurationSupport - WebMvcAutoConfiguration有一个核心的条件注解, @ConditionalOnMissingBean(WebMvcConfigurationSupport.class),容器中没有WebMvcConfigurationSupport,WebMvcAutoConfiguration才生效.
- @EnableWebMvc 导入 WebMvcConfigurationSupport 导致 WebMvcAutoConfiguration 失效。导致禁用了默认行为
web新特性
函数式web
-
@Controller + @RequestMapping:耦合式 (路由、业务耦合)
-
函数式Web:分离式(路由、业务分离)
-
场景
场景:User RESTful - CRUD
- GET /user/1 获取1号用户
- GET /users 获取所有用户
- POST /user 请求体携带JSON,新增一个用户
- PUT /user/1 请求体携带JSON,修改1号用户
- DELETE /user/1 删除1号用户
- 核心类
- RouterFunction:定义路由信息
- RequestPredicate:请求谓语
- ServerRequest:请求
- ServerResponse:响应
- 实例
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.MediaType;
import org.springframework.web.servlet.function.RequestPredicate;
import org.springframework.web.servlet.function.RouterFunction;
import org.springframework.web.servlet.function.ServerResponse;
import static org.springframework.web.servlet.function.RequestPredicates.accept;
import static org.springframework.web.servlet.function.RouterFunctions.route;
@Configuration(proxyBeanMethods = false)
public class MyRoutingConfiguration {
private static final RequestPredicate ACCEPT_JSON = accept(MediaType.APPLICATION_JSON);
@Bean
public RouterFunction<ServerResponse> routerFunction(MyUserHandler userHandler) {
return route()
.GET("/{user}", ACCEPT_JSON, userHandler::getUser)
.GET("/{user}/customers", ACCEPT_JSON, userHandler::getUserCustomers)
.DELETE("/{user}", ACCEPT_JSON, userHandler::deleteUser)
.build();
}
}
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.function.ServerRequest;
import org.springframework.web.servlet.function.ServerResponse;
@Component
public class MyUserHandler {
public ServerResponse getUser(ServerRequest request) {
...
return ServerResponse.ok().build();
}
public ServerResponse getUserCustomers(ServerRequest request) {
...
return ServerResponse.ok().build();
}
public ServerResponse deleteUser(ServerRequest request) {
...
return ServerResponse.ok().build();
}
}
整合SSM场景
例子
- 创建Spring Boot项目,选择lombok,spring web,mybatis framework,mysql driver的依赖关系
- 在properties配置文件中配置数据源
- 编写数据库中实体类对象
- 创建mapper接口,并利用mybatisx生成mapper.xml文件
- 在生成的mapper.xml文件中编写sql
- 在启动类中使用@MapperScan注解指定mapper接口的包名
- 在properties配置文件中配置mapper映射文件(.xml文件)的位置
- 编写controller层和service层
application.properties
# 配置数据源
spring.datasource.url=jdbc:mysql://localhost:3306/bookstore
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.type=com.zaxxer.hikari.HikariDataSource
# 配置MyBatis
#指定mapper映射文件位置
mybatis.mapper-locations=classpath:/mapper/*.xml
#开启驼峰命名映射
mybatis.configuration.map-underscore-to-camel-case=true
实体类
package com.atli.springbootssm.pojo;
@Data
@Compoment
public class User {
private int id;
private String UserName;
private int capital;
}
mapper接口
package com.atli.springbootssm.mapper;
public interface UserMapper {
User getUserById(@Param("id") int id);
List<User> getUsers();
int insertUser(@Param("user") User user);
int updateUser(@Param("user") User user);
int deleteUser(@Param("id") int id);
}
mapper映射文件
<!-- classpath: mapper/UserMapper.xml -->
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.atli.springbootssm.mapper.UserMapper">
<insert id="insertUser">
insert into customer(id,user_name,capital) value(#{id},#{userName},#{capital})
</insert>
<update id="updateUser">
update customer set user_name=#{userName},capital=#{capital} where id=#{id}
</update>
<delete id="deleteUser">
delete from customer where id=#{id}
</delete>
<select id="getUserById" resultType="com.atli.springbootssm.pojo.User">
select * from customer where id=#{id}
</select>
<select id="getUsers" resultType="com.atli.springbootssm.pojo.User">
select * from customer
</select>
</mapper>
UserController
package com.atli.springbootssm.controller;
@RestController
public class UserController {
@Autowired
UserService userService;
@GetMapping("/user/{id}")
public User getUserById(@PathVariable("id") int id) {
return userService.getUserById(id);
}
@GetMapping("/user")
public List<User> getUsers() {
return userService.getUsers();
}
@PostMapping("/user")
public int insertUser(@RequestBody User user) {
return userService.insertUser(user);
}
@PutMapping("/user")
public int updateUser(@RequestBody User user) {
return userService.updateUser(user);
}
@DeleteMapping("/user/{id}")
public int deleteUser(@PathVariable("id") int id) {
return userService.deleteUser(id);
}
}
UserServiceImpl
package com.atli.springbootssm.service.impl;
@Service
public class UserServiceImpl implements UserService {
/*
*这里因为是springboot+mybatis,
* boot底层已经帮我们完成了创建SqlSessionFactory,SqlSession,mapper等的操作
* 我们只需要使用mapper点方法去调用即可
*/
@Autowired
private UserMapper userMapper;
@Override
public User getUserById(int id) {
return userMapper.getUserById(id);
}
@Override
public List<User> getUsers() {
return userMapper.getUsers();
}
@Override
public int insertUser(User user) {
return userMapper.insertUser(user);
}
@Override
public int updateUser(User user) {
return userMapper.updateUser(user);
}
@Override
public int deleteUser(int id) {
return userMapper.deleteUser(id);
}
}
jdbc+mybatis场景的自动配置
-
mybatis-spring-boot-starter导入 spring-boot-starter-jdbc,jdbc是操作数据库的场景
-
Jdbc场景的几个自动配置
- org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration
- 数据源的自动配置
- 所有和数据源有关的配置都绑定在DataSourceProperties
- 默认使用 HikariDataSource
- org.springframework.boot.autoconfigure.jdbc.JdbcTemplateAutoConfiguration
- 给容器中放了JdbcTemplate操作数据库
- org.springframework.boot.autoconfigure.jdbc.JndiDataSourceAutoConfiguration
- org.springframework.boot.autoconfigure.jdbc.XADataSourceAutoConfiguration
- 基于XA二阶提交协议的分布式事务数据源
- org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration
- 支持事务
- org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration
-
MyBatisAutoConfiguration:配置了MyBatis的整合流程
- mybatis-spring-boot-starter导入 mybatis-spring-boot-autoconfigure(mybatis的自动配置包),
- 默认加载两个自动配置类:
- org.mybatis.spring.boot.autoconfigure.MybatisLanguageDriverAutoConfiguration
- org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration
- 必须在数据源配置好之后才配置
- 给容器中SqlSessionFactory组件。创建和数据库的一次会话
- 给容器中SqlSessionTemplate组件。操作数据库
- MyBatis的所有配置绑定在MybatisProperties
- 每个Mapper接口的代理对象是怎么创建放到容器中。详见@MapperScan原理:
- 利用@Import(MapperScannerRegistrar.class)批量给容器中注册组件。解析指定的包路径里面的每一个类,为每一个Mapper接口类,创建Bean定义信息,注册到容器中。