SpringBoot3-web开发-静态资源-内容协商

web场景

1.自动配置

1.整合web场景

2.引入 autoconfigure 功能

WebMvcConfigurer接口

1.参数解析器是什么,为什么要解析请求传进来的参数跟controller对应?

参数解析器 HandlerMethodArgumentResolver 一句话:SpringMVC 的工具,负责把前端发来的请求数据(url 参数、json 请求体),转换成 Controller 方法上的 Java 参数对象。

原理: 前端发请求,数据是字符串 /json 流。但是 Controller 方法写的是 User user、Integer id 这种 Java 类型。 Spring 不知道怎么把 http 请求里的数据,塞到你方法的参数上,参数解析器就是干这个映射转换的。

@RequestBody 就是由对应的参数解析器,读取 json 请求体,转成 User 对象

为什么要做? HTTP 传输只有字符串,Java 是强类型。必须有解析器做转换、绑定,否则你 Controller 拿不到参数。 你还可以自定义参数解析器,比如自动把 token 转换成当前登录用户对象。

2.跨域是什么意思?

跨域 = 浏览器的同源安全策略限制 同源:协议 + 域名 + 端口号,三个全部一样才叫同源。只要有一个不一样,就是跨域。

例子: 前端页面 http://localhost:8080,去请求后端接口 http://localhost:8888 端口不一样 → 跨域。 浏览器会拦截这个响应,不让前端拿到返回数据(请求其实已经发给后端了,是浏览器拦住返回结果)。

为什么会有跨域:浏览器安全机制,防止恶意网站偷取别的网站 cookie、数据。 解决方式:后端配置 CORS(就是 WebMvcConfigurer 里面addCorsMappings),告诉浏览器允许这个前端域名访问。

⚠️ 重点:跨域限制只存在浏览器端!Postman、后端代码调用接口,不存在跨域。

Cookie:服务器发给浏览器,浏览器自动保存在本地的一小段文本信息 。 之后每次访问这个网站,浏览器会自动带上 Cookie 发给后端,不需要前端手动写代码。

举个生活化例子: 你登录淘宝,登录成功,淘宝后端给你的浏览器下发一个 Cookie,内容类似userId=1001。 以后你每次打开淘宝页面,浏览器自动把这个 Cookie 发给淘宝服务器。后端一看 Cookie,就知道:哦,是这个用户,不用重复登录。

特点:

  1. 存在浏览器本地(保存在电脑)
  2. 访问对应网站,自动随请求带上
  3. 有过期时间,可以后端设置

3.为什么webmvcconfigurer接口里面含有拦截器,拦截器不是属于servlet的规范吗?

❌ 误区纠正:拦截器(HandlerInterceptor)不属于 Servlet 规范! Servlet 规范只有 Filter 过滤器!!

  • Filter:Servlet 原生,Tomcat 定义,和 Spring 无关。
  • HandlerInterceptor 拦截器:SpringMVC 自己造的组件,不属于 Servlet,只在 SpringMVC 框架内部生效。

那WebMvcConfigurer#addInterceptors()是干嘛的? WebMvcConfigurer 是 SpringMVC 的扩展配置接口,用来注册、配置你写好的拦截器,交给 SpringMVC 管理 。 不是说拦截器写在这个接口里面,而是通过这个方法注册拦截器。

一句话总结: 拦截器是 SpringMVC 的,不是 Servlet;WebMvcConfigurer 提供方法,把拦截器注册进 SpringMVC。

4.在springmvc里面有一个配置是打开视图控制器的,要是不开视图控制器然后想要跳转HTML页面是不是要写很多controller?

视图控制器:addViewControllers(),就是你截图 WebMvcConfigurer 里的方法。 作用:不用写 Controller 方法,直接绑定 URL → 页面。

举例子:

复制代码
@Override
public void addViewControllers(ViewControllerRegistry registry) {
    registry.addViewController("/login").setViewName("login.html");
}

访问/login直接跳 login 页面,不用写 Controller 类。

👉 如果不使用 addViewControllers: 想跳转页面,就必须写 Controller 方法:

复制代码
@GetMapping("/login")
public String toLogin(){
    return "login.html";
}

如果页面很多,就要写一堆这种空方法,非常重复。

5.异步支持是啥?

普通同步请求: 浏览器发请求 → Tomcat 线程卡住,一直占用这个线程,直到业务处理完返回结果。 Tomcat 线程池数量有限,如果慢请求很多,线程会被占满,新请求进不来,服务卡死。

异步请求: 请求进来,Tomcat 工作线程立刻释放,还给线程池。业务逻辑丢到另外一个自定义异步线程去跑。等业务处理完成,再返回给浏览器。

configureAsyncSupport就是 WebMvcConfigurer 里面,用来配置异步请求的线程池、超时时间。

适用场景:长耗时接口,比如导出大文件、远程调用很慢的第三方接口。

简单理解: 同步:服务员一直守着你这桌,等菜上完才能接待下一桌。 异步:服务员先离开去接待别的客人,后厨做好菜再通知服务员送过来。

静态资源映射方法

复制代码
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
    if (!this.resourceProperties.isAddMappings()) {
        logger.debug("Default resource handling disabled");
        return;
    }
    // 规则1:webjars静态资源映射
    addResourceHandler(registry, this.mvcProperties.getWebjarsPathPattern(),
            "classpath:/META-INF/resources/webjars/");
    // 规则2:通用静态资源映射
    addResourceHandler(registry, this.mvcProperties.getStaticPathPattern(), (registration) -> {
        registration.addResourceLocations(this.resourceProperties.getStaticLocations());
        if (this.servletContext != null) {
            ServletContextResource resource = new ServletContextResource(this.servletContext, "/");
            registration.addResourceLocations(resource);
        }
    });
}

规则

静态资源

静态资源映射

静态资源缓存

3.欢迎页

4.Favicon

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

5.缓存实验

自定义静态资源规则

静态资源目录 和 静态资源缓存策略

访问直接写wj/什么 然后访问就行,不用写全称去找

容器中只要有一个WebMvcConfigurer就行

法1:

法2:SpringMVC 启动时,寻找容器中所有 WebMvcConfigurer 类型的 Bean,回调里面的配置方法,完成 MVC 自定义配置。

因为de那个类它是springmvc原生的,所以不能与SpringBoot融合,所以不能读yml,只能通过enable那个子类继承,重写遍历,然后利用 webmvcProperties 将配置给到mvc组件

  • 你:WebConfig implements WebMvcConfigurer,重写 addResourceHandlers,写好添加资源映射的代码
  • Spring 把 WebConfig 放进 IOC 容器
  • De 通过 @Autowired 收集到 List<WebMvcConfigurer>(拿到你的 WebConfig)
  • MVC 底层启动,调用 De 的 addResourceHandlers (registry)
  • De for 循环:c.addResourceHandlers(registry)
  • 执行你写的代码 → 往 registry 注册规则
  • MVC 拿 registry 里面全部规则正式生效

3.路径匹配

模式转换

不能在路径中间加双**

spring.mvc.pathmatch.matching-strategy=path_pattern_parser

使用默认的路径的匹配功能就行由PathPatternParser

如果路径中间需要有双⭐,那就换成ant风格

内容协商

第三方发请求想要的数据不一样,假如接口多,不能只用请求头/person那种请求,所以说很麻烦,用一套系统如何适配多种,

多端内容适配

1.默认规则

请求头默认开启

2.效果演示

引入转换为xml格式的依赖 导包 ->@JacksonXmlRootElement标注解(在根标签例如:Person)

自定义内容返回

1.增加yaml返回支持

1. 增加 yaml 返回支持

导入依赖

xml

复制代码
<dependency>
    <groupId>com.fasterxml.jackson.dataformat</groupId>
    <artifactId>jackson-dataformat-yaml</artifactId>
</dependency>

编写配置

properties

复制代码
spring.mvc.contentnegotiation.media-types.yaml=text/yaml

增加 HttpMessageConverter 组件,专门把对象写成yaml格式

2.思考:如何增加其他

3.内容协商原理 HttpMessageConverter

定制 HttpMessageConverter 来适配多端内容 协商

编写webMvcconfigurer提供的 configureMessageConverte来实现多端内容协商

1.@ResponseBody

2.webMvcconfigurer提供几种默认httpmessageconverters

复制代码
那为什么ObjectMapper能写出来yaml
我给你拆得清清楚楚,一句话先点明:
ObjectMapper 本身不是只能写 JSON,它只是一个「序列化调度外壳」;真正决定输出是 JSON 还是 YAML 的,是传给它的 Factory。
1. 两个 Factory
JsonFactory:Jackson 默认的。负责生成 JSON 的字节
YAMLFactory(来自 jackson-dataformat-yaml):负责生成 YAML 的字节
java
运行
// JSON版本(默认)
ObjectMapper jsonMapper = new ObjectMapper(new JsonFactory());

// YAML版本
ObjectMapper yamlMapper = new ObjectMapper(new YAMLFactory());
ObjectMapper 的代码不用改,writeValueAsString() 这个方法一模一样。
ObjectMapper 只负责:遍历你的 Java 对象、读取字段、调用 Factory 去输出字符。
Factory 才知道语法规则:什么时候加大括号{}、什么时候加冒号、换行(yaml)。
你可以这么理解:
ObjectMapper = 一个通用打印机
JsonFactory = JSON 墨盒,打出来是 JSON 格式
YAMLFactory = YAML 墨盒,打出来是 YAML 格式
打印机本身不变,换墨盒,输出格式就变了。
2. 重点:ObjectMapper 不懂 yaml 语法!
ObjectMapper 完全不知道 yaml 长啥样
是 YAMLFactory + YAMLGenerator 里面写好了 yaml 的格式规则:
用 : 分隔 key value
缩进表示层级
开头可选 ---
这些逻辑全在 YAMLFactory 里面。
ObjectMapper 只负责把「对象里面有哪些字段、值是多少」交给这个 Factory。
3. 和 HTTP、Content-Type 分开(承接刚才说的)
⚠️ 这个序列化,和 HTTP 没有半点关系!
你用 YAMLFactory 得到 yaml 字符串,只是一段文本:
yaml
name: 小明
age: 18
ObjectMapper + YAMLFactory 只负责生产这段文字。
至于 HTTP 响应头写不写 Content-Type: application/yaml,是 HttpMessageConverter 的事情,和它俩无关。
4. 延伸:还能换别的 Factory
jackson 还有别的扩展:
CBORFactory:二进制格式 CBOR
SmileFactory:另一种二进制
它们全都可以丢给 ObjectMapper,一套 API 通用。
这就是 Jackson 的设计:API 统一,格式由底层 Factory 插拔切换
极简一句话总结
ObjectMapper 是通用框架,不绑定 JSON;
传入不同的 Factory,就切换成不同格式;
YAMLFactory 里面内置 yaml 语法规则,所以它就能输出 yaml。
要不要我给你对比一下,同一个 User 对象,JsonFactory 和 YAMLFactory 输出结果?
代码里ObjectMapper怎么跟工厂联合的
先看源码构造方法
java
运行
// ObjectMapper 有一个构造,接收 JsonFactory(父类!YAMLFactory 是它的子类)
public ObjectMapper(JsonFactory factory) {
    this(factory, null, null);
}
类继承关系:
YAMLFactory extends JsonFactory
所以可以直接传给 ObjectMapper。
1. 联合的代码(最简单写法)
java
运行
// 1. 先造工厂(YAML工厂)
JsonFactory factory = new YAMLFactory();
// 2. 把工厂传给ObjectMapper,绑定在一起
ObjectMapper mapper = new ObjectMapper(factory);

User user = new User();
user.setName("张三");

// 3. 调用writeValueAsString
String yaml = mapper.writeValueAsString(user);
System.out.println(yaml);
2. 内部到底怎么联动(核心流程)
当你调用 mapper.writeValueAsString(user) 的时候,内部步骤:
ObjectMapper 拿到你的 Java 对象,开始反射,拿到类、字段、值
ObjectMapper 去找自己持有的那个 JsonFactory(就是你传进来的 YAMLFactory)
调用 factory.createGenerator() 创建一个 Generator(生成器)
JsonFactory → JsonGenerator(生成 json 符号:{ } , :)
YAMLFactory → YAMLGenerator(生成 yaml 符号:换行、缩进、:)
ObjectMapper 把字段名、字段值,一句一句丢给 Generator
比如告诉生成器:key 是 name,value 是张三
Generator 按照自己格式的语法,拼出文本
最后返回拼好的字符串
✅ 关键点:
ObjectMapper 只负责【解析对象、遍历属性】
Generator 负责【按格式输出字符】
Factory 负责【生产对应的 Generator】
画图理解调用链:
plaintext
mapper.writeValueAsString(对象)
    ↓
ObjectMapper(反射拿字段)
    ↓
this._factory.createGenerator()
    ↓
YAMLFactory 造出 YAMLGenerator
    ↓
ObjectMapper 循环调用 generator.writeFieldName / writeString
    ↓
YAMLGenerator 输出 yaml缩进、冒号、换行
3. 对比默认 JSON 版本
java
运行
// 默认无参构造,底层自动 new JsonFactory()
ObjectMapper jsonMapper = new ObjectMapper();
// 等价于
ObjectMapper jsonMapper = new ObjectMapper(new JsonFactory());
4. 你还可以提前给工厂配置,再传给 Mapper
java
运行
// 先配置工厂
YAMLFactory yamlFactory = new YAMLFactory()
    .disable(YAMLGenerator.Feature.WRITE_DOC_START_MARKER);
// 再绑定
ObjectMapper mapper = new ObjectMapper(yamlFactory);
工厂的配置,会被它产出的 Generator 继承。
5. 一句话总结联合关系
ObjectMapper 持有一个 JsonFactory 的成员变量;创建 mapper 的时候把工厂传进去,两者就绑定了。序列化的时候 mapper 调用这个工厂来产出对应的 Generator,由 Generator 输出对应格式文本。
补充区分(别混)
Factory:生产 Generator
Generator:真正输出符号(json /yaml 语法)
ObjectMapper:调度,反射读取 Java 对象的数据,喂给 Generator
要不要我把这一套,套进之前说的 HttpMessageConverter,写一个返回 YAML 的消息转换器完整代码?
YAMLFactory怎么传的
1、最简单直接传(核心写法)
YAMLFactory 是 JsonFactory 的子类,而 ObjectMapper 的构造方法接收 JsonFactory,所以直接把 new YAMLFactory() 塞进去就行:
java
运行
// 第一步:创建YAML工厂
YAMLFactory yamlFactory = new YAMLFactory();
// 第二步:传给ObjectMapper的构造方法,绑定
ObjectMapper mapper = new ObjectMapper(yamlFactory);
等价简写一行:
java
运行
ObjectMapper mapper = new ObjectMapper(new YAMLFactory());
底层源码:
java
运行
public ObjectMapper(JsonFactory factory) {
    this(factory, null, null);
}
因为 YAMLFactory extends JsonFactory,符合参数类型,编译通过。
2、带配置再传递(常用,关掉 yaml 开头的 ---)
先配置工厂,再传给 mapper:
java
运行
// 先new工厂,设置特性
YAMLFactory yamlFactory = new YAMLFactory()
        // 关闭yaml默认的 --- 文档标记
        .disable(YAMLGenerator.Feature.WRITE_DOC_START_MARKER);

// 把配置好的工厂传进去
ObjectMapper mapper = new ObjectMapper(yamlFactory);
3、❌ 错误写法(不能后面 set,这个坑很多人踩)
ObjectMapper 没有 setFactory 方法!
工厂只能在构造的时候传,一旦 mapper 创建完成,工厂就固定了,不能中途替换。
java
运行
// 不行!没有这个方法
mapper.setFactory(new YAMLFactory());
👉 想要换格式,只能重新 new 一个 ObjectMapper,传入新工厂。
4、传进去之后,内部怎么存的
ObjectMapper 内部有一个成员变量:
java
运行
protected final JsonFactory _jsonFactory;
你传进去的 YAMLFactory,就赋值给这个 _jsonFactory。
后面调用 writeValueAsString 的时候,mapper 就拿这个 _jsonFactory 去创建 YAMLGenerator。
5、对比默认 JSON 的情况
java
运行
// 无参构造,底层自动 new JsonFactory()
ObjectMapper jsonMapper = new ObjectMapper();
// 等价
ObjectMapper jsonMapper = new ObjectMapper(new JsonFactory());
一句话总结
YAMLFactory 在 new ObjectMapper 的时候,作为构造参数传进去;工厂一旦传入绑定,就不能修改。

.3.http写法

webconfig重写

配置文件

4.WebMvcConfiguartionSupport

提供了很多默认设置

判断系统中是否有相应的类,如果有就加入相应的HttpMessageConverter

相关推荐
deli0071 小时前
PDF 合并别再用在线工具:12 份文件浏览器里合完,实测不上传
前端
kiros_wang1 小时前
RDB关系数据库实战:增删改查、事务、分页、版本迁移
前端
用户1733598075371 小时前
纯前端 PDF 双面打印避坑指南:翻页顺序错了整个白印?
前端·javascript·vue.js
庖丁解牛1 小时前
第四课:循环,让电脑重复做事(4)
前端
数据掘金1 小时前
用户路径分析的会话切分与节点聚合实现
前端
代码猎人1 小时前
前端怎么搭建项目基础架构
前端
lichenyang4531 小时前
Promise 基础与进阶:状态规则、链式错误处理和异步执行顺序
前端
Amos_Web1 小时前
Rspack 源码解析(二十五):CssParserAndGenerator 如何把 CSS 变成可 Tree Shaking 的模块
前端·rust·前端框架
wespaul2 小时前
VeapAI 自然语言操作实战|第 06 篇:AiConsole 前端运行时——一个 Hub,八个注册表
前端·人工智能·milvus