Spring MVC

1. MVC 定义

MVC 是 Model View Controller 的缩写,它是软件工程中的⼀种软件架构设计模式,它把软件系统分为模型、视图和控制器三个基本部分。

  • View(视图) 指在应用程序中专门用来与浏览器进行交互,展示数据的资源
  • Model(模型) 是应用程序的主体部分,用来处理程序中数据逻辑的部分
  • Controller(控制器)可以理解为⼀个分发器,用来决定对于视图发来的请求,需要用哪⼀个模型 来处理,以及处理完后需要跳回到哪⼀个视图。即用来连接视图和模型

2. 什么是 Spring MVC

MVC 是⼀种架构设计模式, 也是⼀种思想,而 Spring MVC 是对 MVC 思想的具体实现. 除此之外, Spring MVC还是⼀个Web框架.,是基于 Servlet API 构建的原始 Web 框架,从⼀开始就包含在 Spring 框架中。 总结来说,Spring MVC 是⼀个实现了 MVC 模式的 Web 框架.(Spring MVC 全称是 Spring Web MVC )

【Servlet是⼀套 Java Web 开发的规范,实现 Servlet 规范,就是真正编写代码去实现 Servlet 规范提到的各种功能,包括类、方法法、属性等,目前常见的实现了 Servlet 规范的产品包括Tomcat 、Weblogic、Jetty、Jboss、WebSphere 等,它们都被称 为"Servlet 容器". Servlet 容器用来管理程序员编写的 Servlet 类,Spring 内置是 Tomcat 容器,可以直接使用】

所以, Spring MVC主要关注有两个点:

  1. MVC
  2. Web框架

其实, Spring MVC 我们在前面已经用过了, 在创建 Spring Boot 项目时,我们勾选的 Spring Web 框架 其实就是 Spring MVC 框架:

即Spring MVC 项目创建和 Spring Boot 创建项目相同,在创建的时候选择 Spring Web 就相当于创建了 Spring MVC 的项目。

那么此时就有一个问题:SpringBoot 和 SpringMVC 之间有什么关系?

Spring Boot 只是实现Spring MVC的其中⼀种方式。Spring Boot 可以添加很多依赖,借助这些依赖实现不同的功能。Spring Boot 通过添加Spring Web MVC 框架,来实现web功能。

3. 学习 Spring MVC

既然是 Web 框架, 那么当用户在浏览器中输入了 url 之后,我们的 Spring MVC 项目就可以感知到用户的请求,并给予响应。

学习Spring MVC,重点也就是学习如何通过浏览器和用户程序进行交互。

主要分以下三个方面:

  1. 建立连接:将用户(浏览器)和 Java 程序连接起来,也就是访问⼀个地址能够调用到我们的 Spring 程序。
  2. 请求:用户请求的时候会带⼀些参数,在程序中要想办法获取到参数, 所以请求这块主要是 获取参数的功能
  3. 响应: 执行了业务逻辑之后,要把程序执行的结果返回给用户, 也就是响应。

3.1 建立连接

在 Spring MVC 中使用 @RequestMapping 来实现 URL 路由映射 ,也就是浏览器连接程序的作用

示例:创建⼀个 UserController 类,实现用户通过浏览器和程序的交互,具体实现代码如下:

接下来访问: http://127.0.0.1:8080/m1,就可以看到程序返回的数据了:

① @RequestMapping 和 @RestController 注解

@RequestMapping 是 Spring Web MVC 应用程序中最常被用到的注解之⼀,它是用来注册接口的 路由映射的。表示服务收到请求时,路径为 /m1 的请求就会调用 m1 这个方法的代码。

路由映射: 当用户访问⼀个 URL 时, 将用户的请求对应到程序中某个类的某个方法的过程就叫路由映射。


既然 @RequestMapping 已经可以达到我们的目的了, 我们为什么还要加 @RestController 呢?

我们把 @RestController 去掉, 再来访问⼀次:

可以看到, 程序报了404, 找不到该页面,这就是 @RestController 起到的作用。

⼀个项目中, 会有很多类, 每个类可能有很多的方法, Spring程序怎么知道要执行哪个方法呢?

Spring会对所有的类进行扫描, 如果类加了注解 @RestController, Spring才会去看这个类里面的方法有没有加 @RequestMapping 这个注解, 当然他的作用不止这⼀点, 咱们先用, 后面再详细讲。

② @RequestMapping 使用

@RequestMapping 既可修饰类,也可以修饰方法 ,当修饰类和方法时,

访问的地址是类路径 + 方法路径。

  • @RequestMapping标识⼀个类:设置映射请求的请求路径的初始信息
  • @RequestMapping标识⼀个方法:设置映射请求请求路径的具体信息
  • 建议给所有的类都加上类路径,这样做能避免发生错误以及提高可读性。

访问地址:http://127.0.0.1:8080/user/m1

注意: @RequestMapping 的URL 路径最前面加不加 / (斜杠) 都可以, Spring程序启动时, 会进行判断, 如果前面没有加 / , Spring会拼接上⼀个 / 。

访问地址还是可以正常响应。

通常情况下, 我们加上 / @RequestMapping 的URL路径也可以是多层路径, 最终访问时, 依然是 类路径 + 方法路径:

访问路径:http://127.0.0.1:8080/user/u1/m1/m

③ @RequestMapping 是 GET 还是 POST 请求

测试一下就知道结论了

GET 请求:

浏览器发送的请求类型都是get, 通过以上案例, 可以看出来 @RequestMapping 支持get请求.

POST 请求:

我们通过 form 表单来构造请求: 创建 test.html, html代码:

前端代码放在static目录下:访问方式为:http://127.0.0.1:8080/test.html


从上述的运行结果可以看出: @RequestMapping 既支持Get请求, 又支持Post请求。同理, 也支持其他的请求方式。


④ Postman 介绍

从上面的案例中, 也发现了⼀个新的问题, 就是我们测试后端方法时, 还需要去写前端代码(想要发送一个 POST 请求时). 这对我们来说, 是⼀件麻烦的事情。

如何测试自己的程序呢? -- 使用专业的接口测试工具 Postman

界面介绍

1️⃣传参介绍

1. 普通传参, 也就是通过查询字符串来传参

学习HTTP环节时, 我们通过URL来访问互联网上的某⼀个资源 URL的格式如下:

其中, 查询字符串就是请求的参数:

2. form-data(完整表示为: multipart/form-data)

表单提交的数据, 在 form 标签中加上 enctyped="multipart/form-data" , 通常用于提交图片/文件,对应 Content-Type: multipart/form-data 。

3. x-www-form-urlencoded

form表单, 对应 Content-Type: application/x-www-from-urlencoded

4. raw

可以上传任意格式的文本,可以上传text、json、xml、html等

⑤ @RequestMapping 指定 GET/POST 请求

我们可以显示的指定 @RequestMapping 来接收 GET / POST的情况,如下所示:

显示指定只支持 GET 方法:

记得重新运行,此时通过 Postman 测试,发送 get 方法成功得到响应:

此时改成发送 POST 方法,就会失败:

显示指定只支持 POST 方法同理:

除了上述通过显示指定 @RequestMapping 注解的方式来指定发送 GET/POST,还可以通过 @GetMapping 和 @PostMapping 注解来单独发送,也就是说相当于 @RequestMapping 的显示指示。

3.2 请求

访问不同的路径, 就是发送不同的请求. 在发送请求时, 可能会带⼀些参数, 所以学习Spring的请求, 主要 是学习如何传递参数到后端以及后端如何接收. 传递参数, 主要是使用浏览器和Postman来模拟.

① 传递单个参数

接收单个参数, 在 Spring MVC 中直接用方法中的参数就可以,比如以下代码:

使用浏览器发送请求并传参:http://127.0.0.1:8080/request/r1?name=张三

使用 Postman :

注意请求参数必须与写的代码的参数名称一致:

注意事项: 使用基本类型来接收参数时, 参数必须传(除boolean类型), 否则会报500错误,类型不匹配时,会报400错误。

示例:

由于 age 是基本类型,此时没有对其进行传参,没有传参的话,报500错误:

与 int 类型不匹配,报400错误:

以下这种也是类型不匹配:基本数据类型不能为 null,Spring 尝试将空字符串 "" 解析为数字,解析失败,直接抛出类型转换异常,返回 400。

包装类(如Integer等)可以为 null。Spring 内置的转换器会将空字符串 "" 识别为"空值",直接赋值为 null。这种情况下接口能正常进入,但 age 是 null。

② 传递多个参数

和接收单个参数⼀样, 直接使用方法的参数接收即可,使用多个形参

当然,传递的参数是用户名以及密码,如果不想在 URL 中显示出来,可以将其传在正文表单中进行发送:

通过 fiddler 观察:参数部分在body中

当然,也可以是 GET 方法,只不过它返回的响应中,参数值是 null:

原因:

  • GET 请求:参数必须放在 URL 地址栏(?key=value)里。
  • POST 请求:参数可以放在 Body(x-www-form-urlencoded) 里,也可以放在 URL 里。

③ 传递对象

如果参数比较多时,方法声明就需要有很多形参. 并且后续每次新增⼀个参数, 也需要修改方法声明。我们不妨把这些参数封装为⼀个对象. Spring MVC 也可以自动实现对象参数的赋值,比如

通过 Postman 测试:

Spring 会根据参数名称自动绑定到对象的各个属性上, 如果某个属性未传递, 则**赋值为null(基本类型则赋值为默认初识值,**比如int类型的属性, 会被赋值为0),示例:

此时基本类型由于是成员变量/属性,有默认的初始值:


Lombok 介绍

介绍⼀个新的工具包 lombok,Lombok是⼀个Java工具库,通过添加注解的方式,简化Java的开发。

上述我们在写UserInfo对象的时候,想要获取或者修改需要通过get和set方法来获取,使用这个Lombok 工具的话,就可以通过注解的方式来实现 get, set 方法,甚至是 toString 等方法,可以帮助我们消除⼀些冗长代码,使得代码看起来更简洁。

引入依赖:

去Maven中央仓库中寻找 :https://mvnrepository.com/artifact/org.projectlombok/lombok/versions

XML 复制代码
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.30</version>
</dependency>

UserInfo 对象,就可以写成:

@Data 注解会帮助我们自动⼀些方法, 包含getter/setter, equals, toString等。

如果只想要加 get ,set 方法或者其他,可以使用 @Getter 和 @Setter注解。(这些方法可以在类上,也可以在方法上)

更快捷引入依赖

上述引入lombok依赖, 需要去找lombok的坐标,接下来介绍更简单引入依赖的方式

1)安装插件EditStarter, 重启Idea

在pom.xml文件中, 单击右键, 选择Generate, 操作如下图所示

进⼊EditStarters的编辑界⾯, 添加对应依赖即可.

之后会自动添加Lombok坐标:

注意: 不是所有依赖都可以在这里添加的, 这个界面和SpringBoot创建项目界⾯⼀样,依赖不在这里的,还需要去Maven仓库查找坐标,添加依赖。

④ 后端参数重命名(后端参数映射)

某些特殊的情况下,前端传递的参数 key 和我们后端接收的 key 可以不⼀致,比如前端传递了⼀个 time 给后端,而后端是使用 createtime 字段来接收的,这样就会出现参数接收不到的情况,如果出现这种情况,我们就可以使用 @RequestParam 注解来重命名前后端的参数值

  • @RequestParam 的核心作用 :将 HTTP 请求中携带的参数(比如 URL 问号后面的 ?name=xxx,或者表单提交的数据),绑定到 后端 Controller 方法的形参上。

例如,我们随便搜索一些东西:以下这段 URL 中的查询字符串的参数就是自定义的,我们看不懂这些参数表达的意思,这就是采用了重命名的方式。

示例:将参数 keyword 映射成 q 参数,即从前端接收参数q,赋值给keyword:

意味着前端在传递参数的时候,只能传递 q 参数,

如果传递的是 keyword,就会报错,无法完成映射关系:

结论

  1. 使用 @RequestParam 进行参数重命名时,请求参数只能和 @RequestParam 声明的名称一致,才能进行参数绑定和赋值.
  2. 使用 @RequestParam 进行参数重命名时,参数就变成了必传参数.

非必传参数设置

如果我们的实际业务前端的参数是⼀个非必传的参数, 针对上述问题, 如何解决呢?

先来了解下参数必传的原因, 我们查看 @RequestParam 注解的实现细节就可以发现端倪,注解实现如下:

可以看到 required 的默认值为true, 表示含义就是:该注解修饰的参数默认为必传。

既然如此,我们可以通过设置 @RequestParam 中的 required=false 来避免不传递时报错, 具体实现如下:

这样从前端传keyword,就不会报错,但是不能通过映射得到值

⑤ 传递数组

Spring MVC 可以自动绑定数组参数的赋值。

后端实现代码:

查询字符串 传参可以这样传:

也可以这样:

⑥ 传递集合

集合参数:和数组类似, 同⼀个请求参数名有为多个, 且需要使用 @RequestParam 绑定参数关系

  • 默认情况下,请求中参数名相同的多个值,是封装到数组. 如果要封装到集合,要使用 @RequestParam 绑定参数关系

参数传递的方式和数组类似:

⑦ JSON

JSON就是客户端和服务端进行交互的⼀种数据格式, 有自己的格式和语法, 使用文本表示⼀个对象或数组的信息, 因此 JSON本质是字符串。主要负责在不同的语言中数据传递和交换。

JSON 语法

JSON 是⼀个字符串,其格式非常类似于 JavaScript 对象字面量的格式,使用字符串来表示对象或者数组等,如以下的一段 JSON 数据:

  1. 数据在 键值对(Key/Value) 中
  2. 数据由逗号 , 分隔
  3. 对象用 {} 表示
  4. 数组用 \[\] 表示
  5. 值可以为对象, 也可以为数组, 数组中可以包含多个对象

JSON 的两种结构

  • 对象 :大括号 {} 保存的对象是⼀个无序的 键值对 集合,⼀个对象以左括号 { 开始, 右括号 } 结束。每个"键"后跟⼀个冒号 : ,键值对使用逗号 , 分隔
  • 数组: 中括号 \[\] 保存的数组是值(value)的有序集合。⼀个数组以左中括号 开始,右中括号 结束,值之间使用逗号, 分隔。

JSON字符串和Java对象互转

常见的JSON和对象的转换工具:

  1. gson
  2. fastjson
  3. jackson

JSON本质上是⼀个字符串, 通过文本来存储和描述数据

Spring MVC框架也集成了JSON的转换工具(Jackson),我们可以直接使用,来完成JSON字符串和Java对象的互转。

本质上是 jackson-databind 提供的功能, Spring MVC框架中已经把该工具包引入了进来,咱们直接使用即可, 如果脱离Spring MVC使用, 需要引入相关依赖,类似于:

XML 复制代码
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.13.5</version>
</dependency>

接下来通过 Java 代码实现转化,首先我们创建一个测试类:

在 Java中,main 方法是程序运行的主通道,且一个类中只有一个main 方法:

如果使用 @Test 注解,可以脱离main直接运行,且可以运行多个:

  • @Test 的核心作用:它告诉 JUnit(Java 最流行的单元测试框架),这是一个"测试方法",可以脱离 main 方法直接独立运行。

(启动类在运行的同时,可以运行测试类,不冲突,测试类运行完毕就结束了)

示例:


ObjectMapper 类

ObjectMapper 是 Jackson 库 的核心类,SpringBoot 默认 JSON 工具就是 Jackson。Spring Boot 在底层调用 ObjectMapper 把这个对象变成 JSON 字符串,再发回给前端。

使用 ObjectMapper 对象提供的两个方法,可以完成对象和JSON字符串的互转 :

  • writeValueAsString: 把对象转为JSON字符串
  • readValue: 把字符串转为对象

1️⃣Java对象转JSON字符串

2️⃣JSON字符串转Java对象

⑧ 传递JSON对象

接收JSON对象, 需要使用 @RequestBody 注解

  • RequestBody: 请求正文,意思是这个注解作用在请求正文的数据绑定,请求参数必须在写在请求正文中
  • JSON 数据放在HTTP 请求体(Request Body),不是 URL 参数;
  • @RequestBody告诉 SpringMVC:去读取请求体;
  • 消息转换器拿到 body 里的 JSON 字符串,调用ObjectMapper反序列化为 Java 对象;
  • 不加该注解,程序只会去 URL 上找参数,拿不到 JSON 的数据,字段全部 null。

通过 Fiddler 观察:

如果去掉 @RequestBody ,那么就获取不到数据,因为JSON数据并没有在 URL 中,而是在正文中,如果没有进行绑定,Spring不知道要去正文中找数据:

⑨ 获取URL中参数@PathVariable

path variable: 路径变量

和字面表达的意思⼀样, 这个注解主要作用在请求URL路径上的数据绑定。默认传递参数写在URL上,SpringMVC就可以获取到。

如以下的两段URL,文章标识就是 URL路径参数:

对于URL中路径参数的获取,在 @RequestMapping 注解中,需要获取的那个参数需要加上{},根本原因在于:{} 是 Spring MVC 定义的"路径占位符(Path Variable)",它告诉 Spring,URL 的这个位置不是固定字符串,而是一个动态的、需要被提取到代码中的变量。

记得,需要加上 @PathVariable 注解,使用该注解绑定要获取的使用 {} 占位的路径参数,传递给后端,然后返回结果给前端。

如果方法参数名和 {} 里的占位符名字完全一致,Spring 支持自动映射,你可以把 @PathVariable("articleId") 简写成 @PathVariable,即可以重命名。

后端代码实现:

或者使用浏览器访问:http://127.0.0.1:8080/request/r11/article/11256984,此时会获取到URL中的参数:

也可以获取多个参数:

@PathVariable 、@RequestBody 和 @RequestParam 对比:

  • @PathVariable : URL 路径本身,例如 /user/{id} 中的 {id} 位置
  • @RequestParam : URL 问号后面,例如 ?page=1&size=10
  • @RequestBody : HTTP 请求体 ,Body 里的 JSON/XML 流

⑩ 上传文件@RequestPart

@RequestPart 专门用于 multipart/form‑data 类型的多部分表单请求,多用于同时上传文件 + JSON 对象的场景。

MultipartFile 接口 是 Spring 对上传文件的封装接口,代表 multipart/form-data 请求里的文件 part。

  • 请求头必须是:Content-Type: multipart/form-data,文件上传才生效。
  • 其中,**getOriginalFilename()**方法的作用:获取上传文件在客户端(用户电脑)上的原始文件名(包含后缀)
  • transferTo() 方法的作用:把内存中接收到的上传文件,写入保存到服务器磁盘的目标文件。

后端代码实现:获取上传的文件:

通过Postman测试选择如下:

测试结果:

通过 Postman 观察:

将获取到的文件保存到指定的磁盘路径下:

获取 Cookie/Session

🅰️Cookie

HTTP 协议自身是属于 "无状态" 协议(无记忆)

"无状态" 的含义指的是: 默认情况下 HTTP 协议的客户端和服务器之间的这次通信, 和下次通信之间没有直接的联系.

但是实际开发中, 我们很多时候是需要知道请求之间的关联关系的。例如登陆网站成功后,第二次访问的时候服务器就能知道该请求是否是已经登陆过了。

上述图中的 "令牌" 通常就存储在 Cookie 字段中. 比如去医院挂号

  1. 看病之前先挂号,挂号时候需要提供身份证号,同时得到了⼀张 "就诊卡", 这个就诊卡就相当于患 者的 "令牌"
  2. 后续去各个科室进行检查, 诊断, 开药等操作, 都不必再出示身份证了, 只要凭就诊卡即可识别出当 前患者的身份
  3. 看完病了之后, 不想要就诊卡了, 就可以注销这个卡. 此时患者的身份和就诊卡的关联就销毁了. (类 似于网站的注销操作)
  4. 又来看病, 可以办⼀张新的就诊卡, 此时就得到了⼀个新的 "令牌"

此时在服务器这边就需要记录"令牌"信息, 以及令牌对应的用户信息, 这个就是 Session 机制所做的工作。

即 Cookie 存"卡号"(Session ID),Session 存"档案"(用户信息)。

🅱️Session

会话: 对话的意思

在计算机领域, 会话是⼀个客户与服务器之间的不中断的请求响应. 对客户的每个请求,服务器能够识别出请求来自于同⼀个客户. 当⼀个未知的客户向Web应用程序发送第⼀个请求时就开始了⼀个会话.当客户明确结束会话或服务器在⼀个时限内没有接受到客户的任何请求时,会话就结束了。

服务器同⼀时刻收到的请求是很多的. 服务器需要清楚的区分每个请求是属于哪个用户, 也就是属于哪个会话, 就需要在服务器这边记录每个会话以及与用户的信息的对应关系.

Session是服务器为了保存用户信息而创建的⼀个特殊的对象

Session的本质就是⼀个 "哈希表", 存储了⼀些键值对结构.

Key 就是SessionID, Value 就是用户信息(用户信息可以根据需求灵活设计).

  1. 当用户登陆的时候, 服务器在 Session 中新增⼀个新记录, 并把 sessionId 返回给客户端. (通过 HTTP 响应中的 Set-Cookie 字段返回).
  2. 客户端后续再给服务器发送请求的时候, 需要在请求中带上 sessionId. (通过 HTTP 请求中的 Cookie 字段带上,Cookie中存储了SessionId).
  3. 服务器收到请求之后, 根据请求中的 sessionId在 Session 信息中获取到对应的用户信息, 再进行后 续操作.找不到则重新创建Session, 并把SessionID返回

Session 默认是保存在内存中的. 如果重启服务器则 Session 数据就会丢失.


总结

  • Cookie
    • Cookie 是客户端(浏览器)存储的小型文本数据,由服务器通过 HTTP 响应头(Set-Cookie)发送给客户端,客户端(浏览器)会自动保存,并在后续向该服务器的所有请求中,通过 HTTP 请求头(Cookie)将数据带回服务器,实现 "状态传递"。
    • 流程:客户端首次请求 → 服务器生成 Cookie(如 user=test; Max-Age=86400)→ 通过 Set-Cookie 响应头返回 → 浏览器保存 Cookie → 后续请求通过 Cookie 请求头携带 Cookie → 服务器解析 Cookie 识别用户。
  • Session
    • Session 是服务器端为每个用户创建的 "会话对象",用于存储用户的核心状态数据(如登录信息、购物车)。服务器会为每个 Session 分配唯一的 SessionID(随机字符串 ),并将 SessionID 通过 Cookie 发送给客户端;客户端后续请求仅需携带 SessionID,服务器通过 SessionID 找到对应的会话对象,即可获取用户状态。
    • 流程:客户端首次请求 → 服务器创建 Session(含唯一 SessionID)→ 存储用户数据到 Session → 通过 Set-Cookie 把 SessionID=xxx 发给客户端 → 浏览器保存 SessionID Cookie → 后续请求携带 SessionID → 服务器通过 SessionID 查找对应的 Session 数据。
  • Cookie 和 Session之间主要是通过 SessionId 关联起来的, SessionId 是 Cookie 和 Session 之间的桥梁
  • Cookie 和 Session 经常会在⼀起配合使用. 但是不是必须配合
    • 完全可以用 Cookie 来保存⼀些数据在客户端. 这些数据不⼀定是用户身份信息, 也不⼀定是 SessionId
    • Session 中的sessionId 也不需要非得通过 Cookie/Set-Cookie 传递,比如通过URL传递.

获取Cookie

①传统方式获取Cookie

Spring MVC是基于 Servlet API 构建的原始 Web 框架,也是在Servlet的基础上实现的 HttpServletRequest , HttpServletResponse 是Servlet提供的两个类, 是Spring MVC方法的内置对象. 需要时直接在方法中添加声明即可.

  • HttpServletRequest 对象代表客户端的请求, 当客户端通过HTTP协议访问服务器时,HTTP请 求头中的所有信息都封装在这个对象中,通过这个对象提供的方法,可以获得客户端请求的所有信息
  • HttpServletResponse 对象代表服务器的响应. HTTP响应的信息都在这个对象中,比如向客户 端发送的数据, 响应头, 状态码等. 通过这个对象提供的方法, 可以获得服务器响应的所有内容 Spring MVC在这两个对象的基础上进行了封装, 给我们提供更加简单的使用方法。
  • 即 用户希望从http请求中获得什么,都可以从HttpServletRequest 中获取。用户希望从客户端返回什么,都可以通过 HttpServletResponse 来返回。

例如看以下的 HttpServletRequest 类,可以获取到请求头中的任何信息,只要是请求中有的。

获取Cookie 信息:

接下来我们可以通过Postman设置Cookie信息,Cookie的key值必须唯一:

重新运行启动类,可以看到它打印出了Cookie的信息:

通过Postman测试:

通过 Fiddler 观察:

② 简洁方式获取Cookie

使用 @CookieValue 注解获取Cookie,但是这种不常见。

获取Session

Session是服务器端的机制, 我们需要先存储, 才能再获取

Session 也是基于HttpServletRequest 来存储和获取的

①Session 的存储

getSession 操作内部提取到请求中的Cookie 里的 SessionId, 然后根据SessionId获取到对应的Session 对象, Session 对象用 HttpSession 来描述。

获取Session有两种方式:

java 复制代码
HttpSession getSession(boolean create);
HttpSession getSession();
  • HttpSession getSession(boolean create) : 参数如果为 true, 则当不存在会话时新建会话; 参数如果 为 false, 则当不存在会话时返回 null
  • HttpSession getSession(): 和getSession(true) 含义⼀样, 默认值为true.

存储Session对象使用:

  • void setAttribute(String name, Object value): 使用指定的名称绑定⼀个对象到该 session 会话

②Session 的 获取

读取 Session 可以使用 HttpServletRequest

  • Object getAttribute(String name): 返回在该 session 会话中具有指定名称的对象,如果没有指定名称的对象,则返回 null 。

运行:

先设置Session:

通过 Fiddler 观察:

可以看到, Http响应中, 通过Set-Cookie告知客户端, 把SessionID存储在Cookie中,通过浏览器, 可以观察到运行结果:

服务器内存中会存储很多的用户信息,如上述使用的是Edge浏览器进行访问,那么它设置的Cookie值是上述的一串字符,如果通过谷歌浏览器,又是另一串字符:

同样的,通过Postman又是另一串字符。

用过上述,设置完了Session,接下来就是获取Session:

通过 Fiddler 观察:

可以看到, Http请求时, 把SessionId通过Cookie传递到了服务器(以Edge为例)。


上述获取 Session,是通过 HttpServletRequest 类来获取,下面介绍简介获取方式

通过Spring MVC内置对象HttpSession 来获取

首先还是需要先设置Session:

然后获取Session:

通过 @SessionAttribute 注解来获取

对比:

获取 Header

① 传统获取Header

获取Header也是从 HttpServletRequest 中获取 ,使用 HttpServletRequest 提供的getHeader方法来获取, 参数对应HTTP请求报头的"Key"。

示例:从 Header 中获取 User-Agent:

通过浏览器访问地址获得的User-Agent:

观察 Fiddler 获取到的 User-Agent 是否正确:

通过 Postman:

② 简洁获取Header

通过 @RequestHeader 注解获取

3.3 响应

在我们前面的代码例子中,都已经设置了响应数据, Http响应结果可以是数据, 也可以是静态页面,也可以针对响应设置状态码, Header信息等。

①返回静态页面

创建前端页面 index.html (注意路径)

HTML 代码如下:

后端代码:

运行结果发现:

页面未正确返回, http响应把 "/index.html" 当做了http响应正文的数据,

那Spring MVC如何才能识别出来 index.html 是⼀个静态页面, 并进行返回呢?

如果想返回视图的话, 需要把 @RestController 改为 @Controller

通过Postman观察:

发现页面正确展示了,原因:类上写的是 @Controller 注解,这时候 return "/index.html" 返回的并不是普通的字符串,而是"视图名称(View Name)"。它的底层执行逻辑不是"把字符串写回浏览器",而是 "服务器内部转发(Forward)到 /index.html 这个路径"。


@RestController 和 @Controller 有着什么样的关联和区别呢?

咱们前面讲了MVC模式, 后端会返回视图, 这是早期时的概念。

随着互联网的发展,目前项目开发流行"前后端分离"模式,Java主要是用来做后端项目的开发,所以也就不再处理前端相关的内容了。MVC的概念也逐渐发生了变化,View不再返回视图, 而是返回显示视图时需要的数据,所以前面使用的 @RestController 其实是返回的数据.

所以,如果类上写的是 @RestController 注解:Spring 会把 return 的字符串直接作为 HTTP 响应体(Body),也就是数据,写回给前端。此时看到的是浏览器屏幕上赤裸裸地显示 /index.html 这串字符。

@RestController =@Controller+@ResponseBody

可以查看 @RestController 注解的源码看出:

也就是说,@RestController 返回的是数据,而@Controller是返回视图的 ,@ResponseBody 也是返回数据的,如果想要表示返回数据@RestController的效果,就得@Controller+@ResponseBody,改返回视图为数据。

作用总结

  • @Controller

    • 作用:标记这个类是一个 控制器(Controller),具备接收 HTTP 请求的能力。

    • 默认行为:它内部方法的返回值(比如 String)默认被当作 视图名称(View Name)。Spring 会去**templates/ 或 static/**目录下寻找对应的模板文件(如 JSP、HTML),进行页面跳转(转发)。

  • @ResponseBody

    • 作用:加在方法或类上,强行修改 @Controller 的默认行为。

      • 如果作用在类上, 表示该类的所有方法返回的都是数据,如果作用在方法上,表示该方法返回的是数据,如果⼀个类的方法里, 既有返回数据的,又有返回页面的,就把 @ResponseBody 注解添加到对应的方法上即可。
    • 效果:告诉 Spring "不要去找页面了,直接把方法的返回值(Java 对象)交给消息转换器(HttpMessageConverter),转换成 JSON/XML 字符串,塞进 HTTP 响应体(Body)里写给前端。"

  • @RestController(合二为一的快捷方式)

    • 作用:它是 @Controller 和 @ResponseBody 的组合注解。

    • 效果:相当于在类上偷偷加了 @ResponseBody。这意味着这个类里的所有方法,默认都返回 JSON/XML 数据,而不是跳转页面。

细节:

  • 当HTML 放在 src/main/resources**/static/**index.html 路径下时,
  • 如果写 return "/index.html";(带 /):转发路径以 / 开头,在 Servlet 规范中代表绝对路径,即相对于应用根目录(http://localhost:8080/)。Spring 会精准地找到 static/index.html。
  • 如果写 return "index.html";(不带 /):转发路径不以 / 开头,代表相对路径,即相对于当前请求的 URI 路径。
  • 假设你当前请求的 URL 是 http://localhost:8080/response/r1。
  • 如果你写 return "/index.html";,转发目标是 /index.html → 找到根目录下的 static 文件 → 成功。
  • 如果你写 return "index.html";,转发目标是 /response/index.html(因为当前路径是 /response/r1,相对路径会拼接在 /response/ 下)→ Spring 会去找 static/response/index.html → 这个文件不存在,返回 404。
  • 所以结论: 为了不让页面路径受当前 Controller 的 @RequestMapping 影响,访问静态资源时务必加上前导 /

②返回数据@ResponseBody

如以下的代码,类上的注解是 @Controller ,意味着类中的方法,返回的响应都是一个静态页面:

如 returnData() 方法,想要返回的是数据,但是由于该注解,程序会认为需要返回的是视图, 根据内容去查找文件,但是查询不到,路径不存在,报404

想要正确返回数据,在该方法上加上 @RequestBody 注解即可:

这样 returnData 方法就能正确返回数据:

而 returnPage 方法也能正确返回页面:

③返回HTML代码段

后端返回数据时, 如果数据中有HTML代码, 也会被浏览器解析

如以下,浏览器解析了HTML:

通过Fiddler观察响应结果, Content-Type 为 text/html:

响应中的 Content-Type 常见取值有以下几种:

  • text/html : body 数据格式是 HTML
  • text/css : body 数据格式是 CSS
  • application/javascript : body 数据格式是 JavaScript
  • application/json : body 数据格式是 JSON

④返回JSON

Spring MVC 也可以返回JSON, 后端方法返回结果为对象

通过Fiddler观察响应结果, Content-Type 为 application/json

⑤设置状态码

Spring MVC会根据我们方法的返回结果自动设置响应状态码, 程序员也可以手动指定状态码

通过Spring MVC的内置对象HttpServletResponse 提供的方法来进行设置

通过Fiddler来观察设置的结果:

⑥设置Header

Http响应报头也会向客户端传递⼀些附加信息,比如服务程序的名称,请求的资源已移动到新地址等,

如: Content-Type, Local等,这些信息通过 @RequestMapping 注解的属性来实现

先来看 @RequestMapping 的源码:

java 复制代码
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Mapping
public @interface RequestMapping {
    String name() default "";
    @AliasFor("path")
    String[] value() default {};
    @AliasFor("value")
    String[] path() default {};
    RequestMethod[] method() default {};
    String[] params() default {};
    String[] headers() default {};
    String[] consumes() default {};
    String[] produces() default {};
}
  1. value: 指定映射的URL
  2. method: 指定请求的method类型, 如GET, POST等
  3. consumes: 指定处理请求(request)的提交内容类型(Content-Type),例如application/json, text/html;
  4. produces: 指定返回的内容类型,还可以同时设置返回值的字符编码
  5. Params: 指定request中必须包含某些参数值时,才让该方法处理
  6. headers: 指定request中必须包含某些指定的header值,才能让该方法处理请求

设置Content-Type

我们通过设置 produces属性的值, 设置响应的报头Content-Type:

如果不设置produces ,方法返回结果为String时, Spring MVC默认返回类型, 是text/html:

设置其他Header

设置其他Header的话, 需要使用Spring MVC的内置对象HttpServletResponse 提供的方法来进行设置。


注解总结:

  • @RequestMapping: 路由映射
  • @RequestParam: 后端参数重命名
  • @RequestBody: 接收JSON类型的参数
  • @PathVariable: 接收路径参数
  • @RequestPart: 上传文件
  • @ResponseBody: 返回数据
  • @CookieValue: 从Cookie中获取值
  • @SessionAttribute: 从Session中获取值
  • @RequestHeader: 从Header中获取值
  • @Controller: 定义⼀个控制器, Spring 框架启动时加载, 把这个对象交给Spring管理. 默认返回视图.
  • @RestController: @ResponseBody + @Controller 返回数据

约定前后端交互接口

约定 "前后端交互接口" 是进行 Web 开发中的关键环节。 接口又叫 API (Application Programming Interface), 我们⼀般讲到接口或者 API,指的都是同⼀个东西,是指应用程序对外提供的服务的描述,用于交换信息和执行任务(与JavaSE阶段学习的类和接口中的接口是两回事)

简单来说, 就是允许客户端给服务器发送哪些 HTTP 请求, 并且每种请求预期获取什么样的 HTTP 响应。

现在"前后端分离"模式开发,前端和后端代码通常由不同的团队负责开发. 双方团队在开发之前,会提前约定好交互的方式. 客户端发起请求, 服务器提供对应的服务. 服务器提供的服务种类有很多, 客户端按照双方约定指定选择哪⼀个服务.

接口 , 其实也就是我们前面网络模块讲的的"应用层协议 "。把约定的内容写在文档上, 就是"接口文档" ,接口文档也可以理解为是 应用程序的"操作说明书"。

相关推荐
cfm_29141 小时前
Spring核心设计模式
java·spring·设计模式
Java小白笔记1 小时前
Java 实现阿里云 OSS 文件上传链路:普通上传、秒传、分片与断点续传
java·开发语言·数据库·spring·阿里云
2333!!!!!1 小时前
rocket新手一些常见问题
java·开发语言
骇客野人1 小时前
Springboot 的 配置文件 application.yml 和 bootrap.yml 的由来和作用
java·spring boot·后端
wuminyu1 小时前
JVM锁膨胀与Futex源码解析
java·linux·c语言·jvm·c++
CoderYanger1 小时前
A.每日一题:3622. 判断整除性
java·程序人生·算法·leetcode·面试·职场和发展·蓝桥杯
吴声子夜歌1 小时前
ApacheCommons——commons-lang3(Java 基础语言增强)(二)
java·开发语言·apache
xcl09251 小时前
幼儿托管系统开发实战:从需求分析到部署上线全指南
java·spring boot·需求分析
我不会起名字3221 小时前
一天一道算法题(26):栈的简单应用
java·数据结构·python·算法·leetcode·golang·