笔记 笔记
首页
  • 开发工具
  • Java Web
  • Java 进阶
  • 容器化技术
  • Java 专栏

    • Java 核心技术面试精讲
    • Java 业务开发常见错误 100 例
  • 数据库专栏

    • MySQL 实战 45 讲
    • Redis 核心技术与实战
  • 安全专栏

    • OAuth 2.0 实战课
  • 计算机系统
  • 程序设计语言
  • 数据结构
  • 知识产权
  • 数据库
  • 面向对象
  • UML
  • 设计模式
  • 操作系统
  • 结构化开发
  • 软件工程
  • 计算机网络
  • 上午题错题
在线工具 (opens new window)

EasT-Duan

Java 开发
首页
  • 开发工具
  • Java Web
  • Java 进阶
  • 容器化技术
  • Java 专栏

    • Java 核心技术面试精讲
    • Java 业务开发常见错误 100 例
  • 数据库专栏

    • MySQL 实战 45 讲
    • Redis 核心技术与实战
  • 安全专栏

    • OAuth 2.0 实战课
  • 计算机系统
  • 程序设计语言
  • 数据结构
  • 知识产权
  • 数据库
  • 面向对象
  • UML
  • 设计模式
  • 操作系统
  • 结构化开发
  • 软件工程
  • 计算机网络
  • 上午题错题
在线工具 (opens new window)

购买兑换码请添加

添加时候请写好备注,否则无法通过。

  • Maven

  • Bootstrap

  • Spring

  • Spring MVC

  • MyBatis

  • JUnit

  • GitFlow 工作流指南

  • SpringBoot

    • SpringBoot3-快速入门
    • SpringBoot3-Web开发
      • Web 自动装配原理
        • Filter
        • 自动装配
        • 手动配置
        • 为什么WebMvcConfigurer能配置底层行为?
        • WebMvcConfigurationSupport
        • 全面接管SpringMVC
        • WebMvcAutoConfiguration 到底自动配置了哪些规则
        • @EnableWebMvc 禁用默认行为
        • WebMvcConfigurer 功能
        • 总结
        • 三种方式
        • 两种模式
      • 静态资源
        • 默认规则
        • 静态资源映射
        • 静态资源缓存
        • 测试
        • 欢迎页
        • Favicon
        • 自定义静态资源规则
        • 配置方式
        • 代码方式
      • 路径匹配
        • Ant风格路径用法
        • 切换模式
      • 内容协议
        • 多端内容适配
        • 默认规则
        • 测试
        • 原理-HttpMessageConverter
        • @ResponseBody由HttpMessageConverter处理
        • 处理请求头中Content-Type
        • 自定义内容返回
      • 模板引擎
        • 整合Thymeleaf
        • 基础语法
        • 核心用法
        • 语法示例
        • 属性设置
        • 遍历
        • 判断
        • th:if
        • th:switch
        • 属性优先级
        • 行内写法
        • 变量选择
        • 模板布局
        • devtools
      • 国际化
      • 错误处理
        • 默认机制
        • 自定义错误视图解析
        • 什么都没有
        • 自定义错误响应
        • 自定义json响应
        • 自定义页面响应
        • 最佳实战
        • 前后分离
        • 服务端页面渲染
      • 嵌入式容器
        • 自动配置原理
        • 切换嵌入式容器
      • Web新特性
        • Problemdetails
        • 函数式Web
        • 场景
        • 核心类
        • 示例
    • SpringBoot3-数据访问
    • SpringBoot3-基础特性
    • SpringBoot3-核心原理
    • SpringBoot3-场景集成
  • Reactor

  • 微服务

  • Java Web
  • SpringBoot
EasT-Duan
2024-09-05
目录

SpringBoot3-Web开发

欢迎来到我的 ChatGPT 中转站,极具性价比,为付费不方便的朋友提供便利,有需求的可以添加左侧 QQ 二维码,另外,邀请新用户能获取余额哦!最后说一句,那啥:请自觉遵守《生成式人工智能服务管理暂行办法》。

# Web 自动装配原理

SpringBoot 的 WEB 开发能力,由 SpringMVC 提供。

WebMvcAutoConfiguration 类负责了 SpringMVC 的自动装配能力

@AutoConfiguration(after = { DispatcherServletAutoConfiguration.class, TaskExecutionAutoConfiguration.class,
		ValidationAutoConfiguration.class }) //在这些自动配置之后
@ConditionalOnWebApplication(type = Type.SERVLET) //如果是web应用就生效,类型SERVLET、REACTIVE 响应式web
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class })
@ConditionalOnMissingBean(WebMvcConfigurationSupport.class) //容器中没有这个Bean,才生效。默认就是没有
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)//优先级
@ImportRuntimeHints(WebResourcesRuntimeHints.class)
1
2
3
4
5
6
7

# Filter

类中放了两个 Filter:

  • HiddenHttpMethodFilter ;页面表单提交 Rest 请求(GET、POST、PUT、DELETE)

  • FormContentFilter : 表单内容 Filter,GET(数据放 URL 后面)、POST(数据放请求体)请求可以携带数据,PUT、DELETE 的请求体数据会被忽略

# 自动装配

WebMvcAutoConfigurationAdapter ,它的目的是提供 Spring Boot 自动配置的默认行为,包括注册必要的 Spring MVC 基础设施组件,如视图解析器、消息转换器等。它实现了 WebMvcConfigurer 接口,允许开发者通过扩展这个类来自定义 Spring MVC 的配置。

# 手动配置

在 WebMvcAutoConfiguration 类中有个内部类为 EnableWebMvcConfiguration 。这个 EnableWebMvcConfiguration 提供了所有 @EnableWebMvc 注解的实现,允许开发者完全控制 Spring MVC 的配置,但是一旦配置了开启了 @EnableWebMvc 注解,那么 WebMvcAutoConfiguration 类中的行为就失效了即丧失了 Spring Boot 的一些自动配置特性,通常用于需要覆盖或增强自动配置提供的默认行为的场景。

// 因为EnableWebMvcConfiguration继承了DelegatingWebMvcConfiguration,而DelegatingWebMvcConfiguration又是WebMvcConfigurationSupport的子类,而在WebMvcAutoConfiguration类上有注解@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)即容器中不存在WebMvcConfigurationSupport时WebMvcAutoConfiguration才会生效
public static class EnableWebMvcConfiguration extends DelegatingWebMvcConfiguration implements ResourceLoaderAware {
}
1
2
3

注意

所谓 “手动” 并非指的是完全需要开发者自己创建和配置 bean,或者明确地在配置类中定义各种组件。而是需要自定义 Spring MVC 的部分配置。

@Configuration
@EnableWebMvc
public class WebMvcConfig implements WebMvcConfigurer {
    // 配置静态资源处理
    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/resources/**").addResourceLocations("/resources/");
    }
    // 配置视图解析器
    @Override
    public void configureViewResolvers(ViewResolverRegistry registry) {
        registry.jsp("/WEB-INF/views/", ".jsp");
    }
    // 其他自定义的 Spring MVC 配置
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# 为什么 WebMvcConfigurer 能配置底层行为?

  • WebMvcAutoConfiguration 是一个自动配置类,它里面有一个 EnableWebMvcConfiguration
  • EnableWebMvcConfiguration 继承与 DelegatingWebMvcConfiguration,这两个都生效
  • DelegatingWebMvcConfiguration 利用 DI 把容器中 所有 WebMvcConfigurer 注入进来
  • 别人调用 DelegatingWebMvcConfiguration 的方法配置底层规则,而它调用所有 WebMvcConfigurer 的配置底层方法。

# WebMvcConfigurationSupport

WebMvcConfigurationSupport 是 Spring MVC 中一个非常重要的类,它在 Spring MVC 的配置中扮演着核心角色。它负责设置和管理 Spring MVC 的各种组件,如处理器映射、视图解析器、参数解析器等。提供了很多的默认设置。判断系统中是否有相应的类:如果有,就加入相应的 HttpMessageConverter 。

# 全面接管 SpringMVC

  • SpringBoot 默认配置好了 SpringMVC 的所有常用特性。

  • 如果我们需要全面接管 SpringMVC 的所有配置并禁用默认配置,仅需要编写一个 WebMvcConfigurer 配置类,并标注 @EnableWebMvc 即可

  • 全手动模式

    • @EnableWebMvc : 禁用默认配置
  • WebMvcConfigurer 组件:定义 MVC 的底层行为

# WebMvcAutoConfiguration 到底自动配置了哪些规则

支持 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 失效。导致禁用了默认行为

  • @EnableWebMVC 禁用了 Mvc 的自动配置
  • WebMvcConfigurer 定义 SpringMVC 底层组件的功能类

# WebMvcConfigurer 功能

定义扩展 SpringMVC 底层功能

提供方法 核心参数 功能 默认
addFormatters FormatterRegistry 格式化器:支持属性上@NumberFormat和@DatetimeFormat的数据类型转换 GenericConversionService
getValidator 无 数据校验:校验 Controller 上使用@Valid标注的参数合法性。需要导入starter-validator 无
addInterceptors InterceptorRegistry 拦截器:拦截收到的所有请求 无
configureContentNegotiation ContentNegotiationConfigurer 内容协商:支持多种数据格式返回。需要配合支持这种类型的HttpMessageConverter 支持 json
configureMessageConverters List<HttpMessageConverter<?>> 消息转换器:标注@ResponseBody的返回值会利用MessageConverter直接写出去 8 个,支持byte,string,multipart,resource,json
addViewControllers ViewControllerRegistry 视图映射:直接将请求路径与物理视图映射。用于无 java 业务逻辑的直接视图页渲染 无 <mvc:view-controller>
configureViewResolvers ViewResolverRegistry 视图解析器:逻辑视图转为物理视图 ViewResolverComposite
addResourceHandlers ResourceHandlerRegistry 静态资源处理:静态资源路径映射、缓存控制 ResourceHandlerRegistry
configureDefaultServletHandling DefaultServletHandlerConfigurer 默认 Servlet:可以覆盖 Tomcat 的DefaultServlet。让DispatcherServlet拦截/ 无
configurePathMatch PathMatchConfigurer 路径匹配:自定义 URL 路径匹配。可以自动为所有路径加上指定前缀,比如 /api 无
configureAsyncSupport AsyncSupportConfigurer 异步支持: TaskExecutionAutoConfiguration
addCorsMappings CorsRegistry 跨域: 无
addArgumentResolvers List<HandlerMethodArgumentResolver> 参数解析器: mvc 默认提供
addReturnValueHandlers List<HandlerMethodReturnValueHandler> 返回值解析器: mvc 默认提供
configureHandlerExceptionResolvers List<HandlerExceptionResolver> 异常处理器: 默认 3 个 ExceptionHandlerExceptionResolver ResponseStatusExceptionResolver DefaultHandlerExceptionResolver
getMessageCodesResolver 无 消息码解析器:国际化使用 无

# 总结

# 三种方式
方式 用法 效果
全自动 直接编写控制器逻辑 全部使用自动配置默认效果
手自一体 @Configuration + 配置 WebMvcConfigurer + 配置 WebMvcRegistrations 不要标注 @EnableWebMvc 保留自动配置效果 手动设置部分功能 定义 MVC 底层组件
全手动 @Configuration + 配置 WebMvcConfigurer 标注 @EnableWebMvc 禁用自动配置效果 全手动设置

总结:给容器中写一个配置类 @Configuration 实现 WebMvcConfigurer 但是不要标注 @EnableWebMvc 注解,实现手自一体的效果。

# 两种模式
  1. 前后分离模式: @RestController 响应 JSON 数据
  2. 前后不分离模式: @Controller + Thymeleaf模板引擎

# 静态资源

# 默认规则

# 静态资源映射

addResourceHandlers 是处理静态资源规则的方法,开发者可以通过重写这个方法来实现自定义静态资源处理

规则一:访问 /webjars/** 路径就去 classpath:/META-INF/resources/webjars/ 下找资源.

WebJars (opens new window) 是一个很神奇的东西,可以让以 jar 包的形式来使用前端的各种框架、组件。

规则二:访问 /** 路径就去静态资源默认的四个位置 classpath:/META-INF/resources/,classpath:/resources/, classpath:/static/, classpath:/public/ ` 找资源。这些都是源码中写死的。

# 静态资源缓存

静态资源默认都有缓存规则的设置

private void addResourceHandler(ResourceHandlerRegistry registry, String pattern,
        Consumer<ResourceHandlerRegistration> customizer) {
    if (registry.hasMappingForPattern(pattern)) {
        return;
    }
    ResourceHandlerRegistration registration = registry.addResourceHandler(pattern);
    customizer.accept(registration);
    registration.setCachePeriod(getSeconds(this.resourceProperties.getCache().getPeriod()));
    registration.setCacheControl(this.resourceProperties.getCache().getCachecontrol().toHttpCacheControl());
    registration.setUseLastModified(this.resourceProperties.getCache().isUseLastModified());
    customizeResourceHandlerRegistration(registration);
}
1
2
3
4
5
6
7
8
9
10
11
12
  • 所有缓存的设置,直接通过配置文件: spring.web 。
  • cachePeriod: 缓存周期; 多久不用找服务器要新的。 默认没有,以 s 为单位。
  • cacheControl: HTTP 缓存控制。
  • useLastModified:是否使用最后一次修改。配合 HTTP Cache 规则。

# 测试

#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
#使用资源 last-modified 时间,来对比服务器和浏览器的资源是否相同没有变化。相同返回 304
spring.web.resources.cache.use-last-modified=true
1
2
3
4
5
6
7
8
9
10
11
12
13
14

然后在对应的 static 或者 public 目录中放一个图片或者其他的静态资源,通过浏览器访问就能看到,第二次访问的时候返回的就是 304 状态码了。你好我什么

# 欢迎页

欢迎页规则在 WebMvcAutoConfiguration 中进行了定义:

  1. 在静态资源目录下找 index.html。
  2. 没有就在 templates 下找 index 模板页。

# Favicon

在静态资源目录下找 favicon.ico

# 自定义静态资源规则

自定义静态资源路径、自定义缓存规则

# 配置方式

spring.mvc : 静态资源访问前缀路径

spring.web :

  • 静态资源目录
  • 静态资源缓存策略
#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/**
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

# 代码方式

  • 容器中只要有一个 WebMvcConfigurer 组件。配置的底层行为都会生效
  • @EnableWebMvc // 禁用 boot 的默认配置
//方式一
@Configuration
@EnableWebMvc
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));
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
//方式二
@Configuration //这是一个配置类,给容器中放一个 WebMvcConfigurer 组件,就能自定义底层
@EnableWebMvc
public class MyConfig {

    @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));
            }
        };
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

# 路径匹配

Spring5.3 之后加入了更多的请求路径匹配的实现策略;以前只支持 AntPathMatcher 策略,现在提供了 PathPatternParser 策略。并且可以让我们指定到底使用那种策略。

# Ant 风格路径用法

Ant 风格的路径模式语法具有以下规则:

  • *:表示任意数量的字符。
  • ?:表示任意一个字符。
  • **:表示任意数量的目录。
  • {}:表示一个命名的模式占位符。
  • []:表示字符集合,例如 [a-z] 表示小写字母。

例如:

  • *.html 匹配任意名称,扩展名为.html 的文件。
  • /folder1/*/*.java 匹配在 folder1 目录下的任意两级目录下的.java 文件。
  • /folder2/**/*.jsp 匹配在 folder2 目录下任意目录深度的.jsp 文件。
  • /{type}/{id}.html 匹配任意文件名为 {id}.html,在任意命名的 {type} 目录下的文件。

注意:Ant 风格的路径模式语法中的特殊字符需要转义,如:

  • 要匹配文件路径中的星号,则需要转义为 \\* 。
  • 要匹配文件路径中的问号,则需要转义为 \\? 。
@GetMapping("/a*/ti*b/{fe:[g-s]+}/**")
public String test01(HttpServletRequest request,
                     @PathVariable("fe") String path){
    log.info("路径变量p1:{}", path);
    //获取请求路径
    String uri = request.getRequestURI();
    return uri;
}
1
2
3
4
5
6
7
8

# 切换模式

SpringMVC 中默认使用的是 AntPathMatcher 这种匹配方式

AntPathMatcher 与 PathPatternParser

  • PathPatternParser 在 jmh 基准测试下,有 6~8 倍吞吐量提升,降低 30%~40% 空间分配率
  • PathPatternParser 兼容 AntPathMatcher 语法,并支持更多类型的路径模式
  • PathPatternParser "*" 多段匹配的支持仅允许在模式末尾使用 *

首先需要再配置文件中进行配置

spring.mvc.pathmatch.matching-strategy=ant_path_matcher
1
@GetMapping("/a*/**/ti*b/{fe:[g-s]+}/**")
public String test01(HttpServletRequest request,
                     @PathVariable("fe") String path){
//...........
}
1
2
3
4
5

# 内容协议

# 多端内容适配

# 默认规则

SpringBoot 多端内容适配。

  • 基于请求头内容协商:(默认开启)

    • 客户端向服务端发送请求,携带 HTTP 标准的 Accept 请求头。
      1. Accept: application/json 、 text/xml 、 text/yaml
      2. 服务端根据客户端请求头期望的数据类型进行动态返回
  • 基于请求参数内容协商:(需要开启)

    • 发送请求 GET /projects/spring-boot?format=json
    • 匹配到 @GetMapping ("/projects/spring-boot")
    • 根据参数协商,优先返回 json 类型数据【需要开启参数匹配设置】
    • 发送请求 GET /projects/spring-boot?format=xml, 优先返回 xml 类型数据

# 测试

## 开启基于请求参数的内容协商功能。 默认参数名:format。 默认此功能不开启
spring.mvc.contentnegotiation.favor-parameter=true
## 指定内容协商时使用的参数名。默认是 format,可以修改
spring.mvc.contentnegotiation.parameter-name=type
1
2
3
4

spring.mvc.contentnegotiation.favor-parameter=true

  • 这个配置启用了基于请求参数的内容协商策略。
  • 当设置为 true 时,Spring MVC 将允许通过请求参数来指定期望的响应格式。
  • 这意味着客户端可以通过在 URL 中添加一个参数来请求特定格式的响应。

spring.mvc.contentnegotiation.parameter-name=type

  • 这个配置指定了用于内容协商的请求参数名。
  • 在这个例子中,参数名被设置为 "type"。
  • 这意味着客户端可以使用 "type" 参数来指定期望的响应格式。
@JacksonXmlRootElement  // 可以写出为xml文档
@Data
public class Person {
    private Long id;
    private String userName;
    private String email;
    private Integer age;
}
1
2
3
4
5
6
7
8
@GetMapping("person")
public Person getPerson() {
    Person person = new Person();
    person.setId(1L);
    person.setAge(20);
    person.setUserName("东东");
    person.setEmail("151561561@qq.com");
    return person;
}
1
2
3
4
5
6
7
8
9

通过访问 http://localhost:8080/person?type=xml 即可访问。

# 原理 - HttpMessageConverter

对于类型返回的处理主要是通过注解 @ResponseBody 来处理,而 @ResponseBody 由 HttpMessageConverter 处理。SpringMVC 的核心为 DispatcherServlet 类中的 doDispatch 方法。

# @ResponseBody 由 HttpMessageConverter 处理

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
	//.........................................
    // Determine handler adapter for the current request.
    HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler()); //寻找合适的是适配器
	//.........................................
    // Actually invoke the handler.
    mv = ha.handle(processedRequest, response, mappedHandler.getHandler());//调用适配器的处理方法
}
1
2
3
4
5
6
7
8
protected ModelAndView handleInternal(HttpServletRequest request,
			HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {

    ModelAndView mav;
	//.........................................
    mav = invokeHandlerMethod(request, response, handlerMethod);//调用处理方法
	//.........................................
    return mav;
}
1
2
3
4
5
6
7
8
9
protected ModelAndView invokeHandlerMethod(HttpServletRequest request,
			HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
    //.........................................
    if (this.argumentResolvers != null) {
        invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);// 参数解析器,确定目标方法每个参数值,放的是全部的参数解析器
    }
    if (this.returnValueHandlers != null) {
        invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);//返回值处理器,确定目标方法的返回值改怎么处理,同样也是放的全部返回值处理器
    }
	//.........................................
    invocableMethod.invokeAndHandle(webRequest, mavContainer);//处理方法,会在方法中找到符合的返回值处理器
}
1
2
3
4
5
6
7
8
9
10
11
12
public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer,
        Object... providedArgs) throws Exception {
    //.........................................
    this.returnValueHandlers.handleReturnValue(
            returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
	//.........................................
}
1
2
3
4
5
6
7
public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
        ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
    //.........................................
    HandlerMethodReturnValueHandler handler = selectHandler(returnValue, returnType); //找到合适返回值处理器,RequestResponseBodyMethodProcessor
    //.........................................
    handler.handleReturnValue(returnValue, returnType, mavContainer, webRequest);//处理返回值
}
private HandlerMethodReturnValueHandler selectHandler(@Nullable Object value, MethodParameter returnType) {
    boolean isAsyncValue = isAsyncReturnValue(value, returnType);
    for (HandlerMethodReturnValueHandler handler : this.returnValueHandlers) {
        if (isAsyncValue && !(handler instanceof AsyncHandlerMethodReturnValueHandler)) {
            continue;
        }
        if (handler.supportsReturnType(returnType)) {//通过这个方法就可以获取到合适的处理器
            return handler;
        }
    }
    return null;
}
//可以看到对@ResponseBody的处理
public boolean supportsReturnType(MethodParameter returnType) {
    return (AnnotatedElementUtils.hasAnnotation(returnType.getContainingClass(), ResponseBody.class) ||
            returnType.hasMethodAnnotation(ResponseBody.class));
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
        ModelAndViewContainer mavContainer, NativeWebRequest webRequest)
        throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {
	//这个方法就是处理返回值的逻辑
    writeWithMessageConverters(returnValue, returnType, inputMessage, outputMessage);
}
1
2
3
4
5
6

# 处理请求头中 Content-Type

在 writeWithMessageConverters 方法中获取请求头中的需要的返回值类型 selectedMediaType ,遍历所有的 MessageConverter 看谁支持这种内容类型的数据,最后找到 MappingJackson2XmlHttpMessageConverter 这个类进行处理。

# 自定义内容返回

  1. 首先需要配置 spring.mvc.contentnegotiation.media-types 属性,比如 yaml,这个 yaml 就指的是 url 中的 type=yaml ,而值为 Content-Type 传的值。
## 比如yaml类型
spring.mvc.contentnegotiation.media-types.yaml=text/yaml
1
2
//在源码中可以看到media-types是一个Map类型
public void setMediaTypes(Map<String, MediaType> mediaTypes) {
    this.mediaTypes = mediaTypes;
}
1
2
3
4
  1. 编写对应的 HttpMessageConverter ,要告诉 Boot 这个支持的媒体类型
<!--Jackson已经提供好了yaml格式转换的工具-->
<dependency>
    <groupId>com.fasterxml.jackson.dataformat</groupId>
    <artifactId>jackson-dataformat-yaml</artifactId>
</dependency>
1
2
3
4
5
/**
 * 自定义消息类型转换器
 */
@Configuration
public class MyYamlHttpMessageConverter extends AbstractHttpMessageConverter<Object> {
	
    private ObjectMapper objectMapper; //jackson提供的

    public MyYamlHttpMessageConverter() {
        //如果需要的类型没有提供需要自己手写,转换的逻辑
        //告诉SpringBoot这个MessageConverter支持哪种媒体类型  //媒体类型
        super(new MediaType("text", "yaml", StandardCharsets.UTF_8));
        YAMLFactory factory = new YAMLFactory()
            	//移除yaml中的(“---”)。
                .disable(YAMLGenerator.Feature.WRITE_DOC_START_MARKER);
        this.objectMapper = new ObjectMapper(factory);
    }
	
    @Override
    protected boolean supports(Class<?> clazz) {
        return !clazz.isPrimitive();
    }
	//将 HTTP 请求体的内容转换为 Java 对象。在这个方法中,你可以执行多种操作来处理和转换输入数据。
    @Override
    protected Object readInternal(Class<?> clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException {
        return null;
    }
	// 对输出的内容进行转换
    @Override
    protected void writeInternal(Object o, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {
        //try-with写法,自动关流
        try (OutputStream os = outputMessage.getBody()) {
            this.objectMapper.writeValue(os, o);
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
  1. 把 MessageConverter 组件加入到容器
@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));
    }

    @Override
    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
        WebMvcConfigurer.super.configureMessageConverters(converters);
        converters.add(new MyYamlHttpMessageConverter());
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# 模板引擎

由于 SpringBoot 使用了嵌入式 Servlet 容器。所以 JSP 默认是不能使用的。

如果需要服务端页面渲染,优先考虑使用模板引擎。

模板引擎页面默认放在 src/main/resources/templates 目录下。

SpringBoot 包含以下模板引擎的自动配置

  • FreeMarker
  • Groovy
  • Thymeleaf
  • Mustache

Thymeleaf 官网:https://www.thymeleaf.org/

# 整合 Thymeleaf

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
1
2
3
4

自动配置原理

  • 开启了 org.springframework.boot.autoconfigure.thymeleaf.ThymeleafAutoConfiguration 自动配置
  • 属性绑定在 ThymeleafProperties 中,对应配置文件 spring.thymeleaf 内容
  • 所有的模板页面默认在 classpath:/templates 文件夹下找后缀名为 .html 的页面

# 基础语法

# 核心用法

th:xxx:动态渲染指定的 html 标签属性值、或者 th 指令(遍历、判断等)

  • th:text :标签体内文本值渲染

    • th:utext :不会转义,显示为 html 原本的样子。
  • th:属性 :标签指定属性渲染

  • th:attr :标签任意属性渲染

  • th:if``th:each``... :其他 th 指令

  • 例如:

<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}" />
1
2
3

表达式:用来动态取值

  • ${} :变量取值;使用 model 共享给页面的值都直接用 ${}
  • @{} :url 路径;
  • #{} :国际化消息
  • ~{} :片段引用
  • *{} :变量选择:需要配合 th:object 绑定对象

系统工具 & 内置对象:详细文档 (opens new window)

  • 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 生成工具

# 语法示例

表达式:

  • 变量取值:${...}
  • url 取值:@{...}
  • 国际化消息:#{...}
  • 变量选择:*{...}
  • 片段引用: ~{...}

常见:

  • 文本: 'one text','another one!',...
  • 数字: 0,34,3.0,12.3,...
  • 布尔:true、false
  • null: null
  • 变量名: one,sometext,main...

文本操作:

  • 拼串: +
  • 文本替换:| The name is ${name} |

布尔操作:

  • 二进制运算:and,or
  • 取反:!,not

比较运算:

  • 比较:>,<,<=,>=(gt,lt,ge,le)
  • 等值运算:==,!=(eq,ne)

条件运算:

  • if-then:(if)?(then)
  • if-then-else:(if)?(then):(else)
  • default:(value)?:(defaultValue)

特殊语法:

  • 无操作:_

所有以上都可以嵌套组合

'User is of type ' + (${user.isAdmin()} ? 'Administrator' : (${user.type} ?: 'Unknown'))
1

# 属性设置

  1. th:href="@{/product/list}"
  2. th:attr="class=${active}"
  3. th:attr="src=@{/images/gtvglogo.png},title=${logo},alt=#{logo}"
  4. 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}" />
1
2
3

# 遍历

语法: th:each="元素名,迭代状态 : ${集合}"

<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>
1
2
3
4
5
6
7
8
9
10
11

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
1
2
3
4

# 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>
1
2
3
4
5

# 属性优先级

  • 片段
  • 遍历
  • 判断
<ul>
  <li th:each="item : ${items}" th:text="${item.description}">Item description here...</li>
</ul>
1
2
3
Order Feature Attributes
1 片段包含 th:insert th:replace
2 遍历 th:each
3 判断 th:if th:unless th:switch th:case
4 定义本地变量 th:object th:with
5 通用方式属性修改 th:attr th:attrprepend th:attrappend
6 指定属性修改 th:value th:href th:src ...
7 文本值 th:text th:utext
8 片段指定 th:fragment
9 片段移除 th:remove

# 行内写法

[[...]] or [(...)]
<p>Hello, [[${session.user.name}]]!</p>
1
2

# 变量选择

<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>
1
2
3
4
5

等同于

<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
1
2
3
4
5

# 模板布局

  • 定义模板: 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>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# devtools

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-devtools</artifactId>
</dependency>
1
2
3
4

修改页面后; ctrl+F9 刷新效果;

java 代码的修改,如果 devtools 热启动了,可能会引起一些 bug,难以排查

# 国际化

国际化的自动配置参照 MessageSourceAutoConfiguration

实现步骤:

  1. Spring Boot 在类路径根下查找 messages 资源绑定文件。文件名为:messages.properties

  2. 多语言可以定义多个消息文件,命名为 messages_区域代码.properties 。如:

    • messages.properties :默认
      • messages_zh_CN.properties :中文环境
      • messages_en_US.properties :英语环境
  3. 在程序中可以自动注入 MessageSource 组件,获取国际化的配置项值

  4. 在页面中可以使用表达式 #{} 获取国际化的配置项值

@Autowired  //国际化取消息用的组件
MessageSource messageSource;
@GetMapping("/haha")
public String haha(HttpServletRequest request){
    Locale locale = request.getLocale();
    //利用代码的方式获取国际化配置文件中指定的配置项的值
    String login = messageSource.getMessage("login", null, locale);
    return login;
}
1
2
3
4
5
6
7
8
9

# 错误处理

# 默认机制

错误处理的自动配置都在 ErrorMvcAutoConfiguration 中,两大核心机制:

  • SpringBoot 会自适应处理错误,响应页面或 JSON 数据
  • SpringMVC 的错误处理机制依然保留,MVC 处理不了,才会交给 boot 进行处理

  • 发生错误以后,转发给 /error 路径,SpringBoot 在底层写好一个 BasicErrorController 的组件,专门处理这个请求
@RequestMapping(produces = MediaType.TEXT_HTML_VALUE)
public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) {
    HttpStatus status = getStatus(request);
    Map<String, Object> model = Collections
        .unmodifiableMap(getErrorAttributes(request, getErrorAttributeOptions(request, MediaType.TEXT_HTML)));
    response.setStatus(status.value());
    //1、解析错误的自定义视图地址
    ModelAndView modelAndView = resolveErrorView(request, response, status, model);
    //2、如果解析不到错误页面的地址,默认的错误页就是 error
    return (modelAndView != null) ? modelAndView : new ModelAndView("error", model);
}

@RequestMapping
public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
    HttpStatus status = getStatus(request);
    if (status == HttpStatus.NO_CONTENT) {
        return new ResponseEntity<>(status);
    }
    Map<String, Object> body = getErrorAttributes(request, getErrorAttributeOptions(request, MediaType.ALL));
    return new ResponseEntity<>(body, status);
}
//遍历所有resolveErrorView来处理错误
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;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33

# 自定义错误视图解析

SpringBoot 提供了一个默认的错误视图解析器,在 ErrorMvcAutoConfiguration 中的内部类 DefaultErrorViewResolverConfiguration 。

@Bean
@ConditionalOnBean(DispatcherServlet.class)
@ConditionalOnMissingBean(ErrorViewResolver.class)
DefaultErrorViewResolver conventionErrorViewResolver() {
    return new DefaultErrorViewResolver(this.applicationContext, this.resources);
}
1
2
3
4
5
6

在 DefaultErrorViewResolver 中提供了默认的资源解析规则

@Override
public ModelAndView resolveErrorView(HttpServletRequest request, HttpStatus status, Map<String, Object> model) {
    //首先尝试根据具体的 HTTP 状态码解析视图(如 "404"、"500" 等)。
    ModelAndView modelAndView = resolve(String.valueOf(status.value()), model);
    //如果失败,则尝试根据 HTTP 状态码系列解析视图(如 "4xx"、"5xx" 等)。
    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) {
    //构造错误视图的名称,格式为 "error/[状态码]"
    String errorViewName = "error/" + viewName;
    //通过templateAvailabilityProviders模板提供者来处理这个视图名称
    TemplateAvailabilityProvider provider = this.templateAvailabilityProviders.getProvider(errorViewName,
          this.applicationContext);
    //如果有,返回对应的 ModelAndView。
    if (provider != null) {
       return new ModelAndView(errorViewName, model);
    }
    //如果没有,尝试解析为静态资源。
    return resolveResource(errorViewName, model);
}

private ModelAndView resolveResource(String viewName, Map<String, Object> model) {
    //遍历静态资源位置的"状态码.html",位置在{ "classpath:/META-INF/resources/","classpath:/resources/", "classpath:/static/", "classpath:/public/" };
    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) {
          // Ignore
       }
    }
    return null;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41

# 什么都没有

根据上面所述,没有自定义视图解析,就会去找 /error 页面,但是即使没有 /error 页面也能显示错误的页面信息。

//1、解析错误的自定义视图地址
ModelAndView modelAndView = resolveErrorView(request, response, status, model);
//2、如果解析不到错误页面的地址,默认的错误页就是 error
return (modelAndView != null) ? modelAndView : new ModelAndView("error", model);
1
2
3
4

因为在 ErrorMvcAutoConfiguration 类中内部类 WhitelabelErrorViewConfiguration 中定义了默认的 error 页面显示信息。

@Bean(name = "error")
@ConditionalOnMissingBean(name = "error")
public View defaultErrorView() {
    return this.defaultErrorView;
}

private static final class StaticView implements View {

    private static final MediaType TEXT_HTML_UTF8 = new MediaType("text", "html", StandardCharsets.UTF_8);

    private static final Log logger = LogFactory.getLog(StaticView.class);

    //这就是默认看到springboot的错误页
    @Override
    public void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response)
            throws Exception {
        if (response.isCommitted()) {
            String message = getMessage(model);
            logger.error(message);
            return;
        }
        response.setContentType(TEXT_HTML_UTF8.toString());
        StringBuilder builder = new StringBuilder();
        Object timestamp = model.get("timestamp");
        Object message = model.get("message");
        Object trace = model.get("trace");
        if (response.getContentType() == null) {
            response.setContentType(getContentType());
        }
        builder.append("<html><body><h1>Whitelabel Error Page</h1>")
            .append("<p>This application has no explicit mapping for /error, so you are seeing this as a fallback.</p>")
            .append("<div id='created'>")
            .append(timestamp)
            .append("</div>")
            .append("<div>There was an unexpected error (type=")
            .append(htmlEscape(model.get("error")))
            .append(", status=")
            .append(htmlEscape(model.get("status")))
            .append(").</div>");
        if (message != null) {
            builder.append("<div>").append(htmlEscape(message)).append("</div>");
        }
        if (trace != null) {
            builder.append("<div style='white-space:pre-wrap;'>").append(htmlEscape(trace)).append("</div>");
        }
        builder.append("</body></html>");
        response.getWriter().append(builder.toString());
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49

# 自定义错误响应

# 自定义 json 响应

使用 @ControllerAdvice + @ExceptionHandler 进行统一异常处理

@ControllerAdvice
public class GlobalExceptionHandler {
	// 自定义异常类型
    @ExceptionHandler(CustomException.class)
    public ResponseEntity<ApiResponse> handleCustomException(CustomException ex) {
        ApiResponse apiResponse = new ApiResponse("error", ex.getMessage(), null);
        return new ResponseEntity<>(apiResponse, HttpStatus.BAD_REQUEST);
    }

    @ExceptionHandler(Exception.class)
    public ResponseEntity<ApiResponse> handleGenericException(Exception ex) {
        ApiResponse apiResponse = new ApiResponse("error", "An unexpected error occurred", null);
        return new ResponseEntity<>(apiResponse, HttpStatus.INTERNAL_SERVER_ERROR);
    }
}
// 自定义异常
public class CustomException extends RuntimeException {
    public CustomException(String message) {
        super(message);
    }
}
@Data
@AllArgsConstructor
// API 响应类
public class ApiResponse {
    private String status;
    private String message;
    private Object data;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

# 自定义页面响应

上面已经写过了如何去自定义页面响应。

# 最佳实战

# 前后分离

后台发生的所有错误, @ControllerAdvice + @ExceptionHandler 进行统一异常处理。

# 服务端页面渲染

不可预知的一些,HTTP 码表示的服务器或客户端错误

  • 给 classpath:/templates/error/ 下面,放常用精确的错误码页面。 500.html , 404.html
  • 给 classpath:/templates/error/ 下面,放通用模糊匹配的错误码页面。 5xx.html , 4xx.html

发生业务错误

  • 核心业务,每一种错误,都应该代码控制,跳转到自己定制的错误页。
  • 通用业务, classpath:/templates/error.html 页面,显示错误信息。

页面,JSON,可用的 Model 数据如下

# 嵌入式容器

Servlet 容器:管理、运行 Servlet 组件(Servlet、Filter、Listener)的环境,一般指服务器

# 自动配置原理

  • SpringBoot 默认嵌入 Tomcat 作为 Servlet 容器。
  • 自动配置类是 ServletWebServerFactoryAutoConfiguration , EmbeddedWebServerFactoryCustomizerAutoConfiguration
  • 自动配置类开始分析功能。 xxxxAutoConfiguration
@AutoConfiguration
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@ConditionalOnClass(ServletRequest.class)
@ConditionalOnWebApplication(type = Type.SERVLET)
@EnableConfigurationProperties(ServerProperties.class)
//导入了嵌入式的三大服务器 `Tomcat`、`Jetty`、`Undertow`
@Import({ ServletWebServerFactoryAutoConfiguration.BeanPostProcessorsRegistrar.class,
		ServletWebServerFactoryConfiguration.EmbeddedTomcat.class,
		ServletWebServerFactoryConfiguration.EmbeddedJetty.class,
		ServletWebServerFactoryConfiguration.EmbeddedUndertow.class })
public class ServletWebServerFactoryAutoConfiguration {}
1
2
3
4
5
6
7
8
9
10
11
  1. ServletWebServerFactoryAutoConfiguration 自动配置了嵌入式容器场景

  2. 绑定了 ServerProperties 配置类,所有和服务器有关的配置 server

  3. ServletWebServerFactoryAutoConfiguration 导入了嵌入式的三大服务器 Tomcat 、 Jetty 、 Undertow

  4. 导入 Tomcat 、 Jetty 、 Undertow 都有条件注解。系统中有这个类才行(也就是导了包)

    • 默认 Tomcat 配置生效。给容器中放 TomcatServletWebServerFactory。

    • 都给容器中 ServletWebServerFactory 放了一个 web 服务器工厂(造 web 服务器的)。

    • web 服务器工厂都有一个功能, getWebServer 获取 web 服务器。

    • TomcatServletWebServerFactory 创建了 tomcat。

    • ServletWebServerFactory 什么时候会创建 webServer 出来。
      1. ServletWebServerApplicationContext ioc 容器,启动的时候会调用创建 web 服务器
      2. Spring** 容器刷新(启动)** 的时候,会预留一个时机,刷新子容器: onRefresh()
      3. refresh () 容器刷新十二大步的刷新子容器会调用 onRefresh() ;
@Override
protected void onRefresh() {
	super.onRefresh();
	try {
		createWebServer();
	}
	catch (Throwable ex) {
		throw new ApplicationContextException("Unable to start web server", ex);
	}
}
1
2
3
4
5
6
7
8
9
10

Web 场景的 Spring 容器启动,在 onRefresh 的时候,会调用创建 web 服务器的方法。

Web 服务器的创建是通过 WebServerFactory 搞定的。容器中又会根据导了什么包条件注解,启动相关的 服务器配置,默认 EmbeddedTomcat 会给容器中放一个 TomcatServletWebServerFactory ,导致项目启动,自动创建出 Tomcat。

# 切换嵌入式容器

由于 web 模块默认导入了 Tomcat 服务器,所以需要把 Tomcat 从 web 模块中排除,然后再导入其他的服务器

<properties>
    <servlet-api.version>3.1.0</servlet-api.version>
</properties>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <exclusions>
        <!-- Exclude the Tomcat dependency -->
        <exclusion>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-tomcat</artifactId>
        </exclusion>
    </exclusions>
</dependency>
<!-- Use Jetty instead -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-jetty</artifactId>
</dependency>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

# Web 新特性

# Problemdetails

RFC 7807 (opens new window): 错误信息新的返回格式

在源码中

@Configuration(proxyBeanMethods = false)
//配置过一个属性 spring.mvc.problemdetails.enabled=true
@ConditionalOnProperty(prefix = "spring.mvc.problemdetails", name = "enabled", havingValue = "true")
static class ProblemDetailsErrorHandlingConfiguration {
    @Bean
    @ConditionalOnMissingBean(ResponseEntityExceptionHandler.class)
    ProblemDetailsExceptionHandler problemDetailsExceptionHandler() {
        return new ProblemDetailsExceptionHandler();
    }
}
1
2
3
4
5
6
7
8
9
10
  1. ProblemDetailsExceptionHandler 是一个 @ControllerAdvice 集中处理系统异常
  2. 处理以下异常。如果系统出现以下异常,会被 SpringBoot 支持以 RFC 7807 规范方式返回错误数据
@ExceptionHandler({
        HttpRequestMethodNotSupportedException.class, //请求方式不支持
        HttpMediaTypeNotSupportedException.class,
        HttpMediaTypeNotAcceptableException.class,
        MissingPathVariableException.class,
        MissingServletRequestParameterException.class,
        MissingServletRequestPartException.class,
        ServletRequestBindingException.class,
        MethodArgumentNotValidException.class,
        NoHandlerFoundException.class,
        AsyncRequestTimeoutException.class,
        ErrorResponseException.class,
        ConversionNotSupportedException.class,
        TypeMismatchException.class,
        HttpMessageNotReadableException.class,
        HttpMessageNotWritableException.class,
        BindException.class
    })
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

默认的返回是

{
    "timestamp": "2024-07-16T09:05:48.689+00:00",
    "status": 405,
    "error": "Method Not Allowed",
    "path": "/person"
}
1
2
3
4
5
6

开启了 spring.mvc.problemdetails.enabled=true

{
    "type": "about:blank",
    "title": "Method Not Allowed",
    "status": 405,
    "detail": "Method 'POST' is not supported.",
    "instance": "/person"
}
1
2
3
4
5
6
7

# 函数式 Web

SpringMVC 5.2 以后 允许我们使用函数式的方式,定义 Web 的请求处理流程。

函数式接口

Web 请求处理的方式:

  1. @Controller + @RequestMapping :耦合式 (路由、业务耦合)
  2. 函数式 Web:分离式(路由、业务分离)

# 场景

场景:User RESTful - CRUD

  • GET /user/1 获取 1 号用户
  • GET /users 获取所有用户
  • POST /user 请求体携带 JSON,新增一个用户
  • PUT /user/1 请求体携带 JSON,修改 1 号用户
  • DELETE /user/1 删除 1 号用户

# 核心类

  • RouterFunction:定义路由信息。发什么请求,谁来处理。
  • RequestPredicate:请求谓语也指请求方式如 GET、POST、请求参数。
  • ServerRequest:封装完整请求数据
  • ServerResponse:封装完整的响应数据

# 示例

import static org.springframework.web.servlet.function.RequestPredicates.accept;
import static org.springframework.web.servlet.function.RouterFunctions.route;

/**
 * Spring Web 新特性函数式Web,路由和业务分开处理,这里配置路由
 */
@Configuration
public class MyRoutingConfig {
    private static final RequestPredicate ACCEPT_JSON = accept(MediaType.APPLICATION_JSON);

    @Bean
    public RouterFunction<ServerResponse> routerFunction(UserHandler userHandler) {
        return route()
                .GET("/user/{id}", ACCEPT_JSON, userHandler::getUser)
                .POST("user/add",ACCEPT_JSON,userHandler::addUser)
                .build();
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/**
 * 用户模块实际的业务处理逻辑
 */
@Component
@Slf4j
public class UserHandler {
	//模拟查询用户方法
    public ServerResponse getUser(ServerRequest request) {
        String userId = request.pathVariable("id");

        // 使用userId进行后续处理,例如从数据库获取用户
        User user = new User(Long.parseLong(userId), "东东", 20);
        return ServerResponse.ok()
                .contentType(MediaType.APPLICATION_JSON)
                .body(user);
    }
	//模拟添加用户方法
    public ServerResponse addUser(ServerRequest request) {
        try {
            User user = request.body(User.class);
            log.info("user:{}", user);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return ServerResponse.ok().body(ResponseEntity.ok("OK"));
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#SpringBoot
上次更新: 2025/04/12, 05:37:39
SpringBoot3-快速入门
SpringBoot3-数据访问

← SpringBoot3-快速入门 SpringBoot3-数据访问→

最近更新
01
Reactor 核心
02-24
02
前置条件
10-30
03
计算机网络
09-13
更多文章>
Theme by Vdoing | Copyright © 2019-2025 powered by Vdoing
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式