Spring Web MVC介绍
Spring Web MVC 是基于 Servlet API 构建的原始 Web 框架,从一开始就包含在 Spring 框架中,通常被称为"SpringMVC"
什么是Servlet呢?
Servlet是一套 Java Web 开发的规范,或者说是一套 Java Web 开发的技术标准。只有规范并不能做任何事情,必须去实现它。所谓实现 Servlet 规范,就是真正编写代码去实现 Servlet 规范提到的各种功能
Servlet 规范是开放的,除了 Sun 公司,其它公司也可以实现 Servlet 规范,目前常见的实现了Servlet 规范的产品包括 Tomcat、Weblogic、Jetty、Jboss、WebSphere 等,它们都被称为"Servlet 容器"。Servlet 容器用来管理程序员编写的 Servlet 类
MVC定义
MVC 是 Model View Controller 的缩写,它是软件工程中的一种软件架构设计模式,它把软件系统分为模型、视图和控制器三个基本部分
- View(视图) 指在应用程序中专门用来与浏览器进行交互,展示数据的资源
- Model(模型) 是应用程序的主体部分,用来处理程序中数据逻辑的部分
- Controller(控制器)可以理解为一个分发器,用来决定对于视图发来的请求,需要用哪一个模型来处理,以及处理完后需要跳回到哪一个视图。即用来连接视图和模型
什么是Spring MVC ?
MVC 是一种架构设计模式, 也一种思想, 而 Spring MVC 是对 MVC 思想的具体实现。 除此之外, Spring MVC 还是一个Web框架
总结来说,Spring MVC 是一个实现了 MVC 模式的 Web 框架
其实在创建 Spring Boot 项目时,我们勾选的 Spring Web 框架就是 Spring MVC 框架
这时候可能就懵了,这创建的不是Spring Boot项目吗? 怎么又变成Spring MVC项目了? 他们之间到底有着什么样的关系?
Spring Boot 只是实现 Spring MVC 的一种方式而已。Spring Boot 可以添加很多依赖(不仅仅是 Spring MVC), 借助这些依赖能够实现不同的功能。 Spring Boot 通过添加 Spring MVC框架来实现web功能
Spring Boot 就好比现代厨房, 按下炉灶旋钮并逆时针旋转就能生火做饭(添加Spring MVC框架)了。在以前,手动配置 Spring MVC框架依赖就像烧木柴生火,会麻烦不少
Spring在实现MVC时, 也结合自身项目的特点做了一些改变, 相对而言下面这个图更加合适一些,不过核心没变
简单示例
既然是 Web 框架, 那么当用户在浏览器中输入了 url 之后,我们的 Spring MVC 项目就可以感知到用户的请求, 并给予响应
学习Spring MVC, 重点就是学习如何通过浏览器和程序进行交互
主要分以下三个方面:
- 建立连接:将用户(浏览器)和 Java 程序连接起来,也就是访问一个地址能够调用到我们的Spring 程序
- 请求: 用户请求的时候会带一些参数,在程序中要想办法获取到参数, 所以请求这块主要是获取参数的功能
- 响应: 执行了业务逻辑之后,要把程序执行的结果返回给用户, 也就是响应
使用 Spring Boot 的方式创建 Spring MVC 项目时, 勾选上 Spring Web 模块即可。并使用@RequestMapping
来实现 URL 路由映射 ,也就是浏览器连接程序的作用
路由映射:当用户访问一个 URL 时,将用户的请求对应到程序中某个类的某个方法的过程就叫路由映射
创建一个 UserController 类,实现用户通过浏览器和程序的交互,具体实现代码如下:
@RestController
public class UserController {
//路由器规则注册
@RequestMapping("/sayHi")
public String sayHi() {
return "hello Spring Boot!";
}
}
方法名和路径名无需一致,启动程序后访问http://127.0.0.1:8080/sayHi
, 就可以看到程序返回的数据了
服务器收到浏览器的请求时,路径为 /sayHi 的请求就会调用 sayHi 这个方法
@RequestMapping(路由映射) 注解介绍
@RequestMapping
是 Spring Web MVC 应用程序中最常用的注解之一,它用来注册接口的路由映射
- value: 指定映射的URL
- method: 指定请求的method类型, 如GET, POST等
- consumes: 指定请求的提交内容类型(Content-Type),例如application/json, text/html;
- produces: 指定返回的内容类型,仅当request请求头中的(Accept)类型中包含该指定类型才返回
- Params: 指定request中必须包含某些参数值时,才让该方法处理请求
- headers: 指定request中必须包含某些指定的header值,才让该方法处理请求
- 注解的参数只填一个时,key可以省略,并且会把这个值赋给
value
属性 - 如果有多个属性要进行赋值,需要加上 key,比如
@RequestMapping(value = "/sayHi", method = RequestMethod.POST)
- 多个注解同时修饰无先后顺序
- 同一个url,不同的请求方式,不报错
既然 @RequestMapping
已经可以达到我们的目的了,为什么还要加 @RestController
呢?
我们把 @RestController
去掉,再来访问一次:
可以看到,程序报了 404, 找不到该页面
一个项目中,会有很多类,每个类可能有很多的方法,Spring 程序怎么知道要执行哪个方法呢?
如果类没有被 @Component
或其派生注解标记,即使方法上有 @RequestMapping
,Spring 也不会处理这些方法
@RestController
是通过组合@Controller
和@ResponseBody
实现的复合注解@Controller
是通过元注解@Component
标记的
因此,@RestController
在逻辑上属于 @Component
的派生注解,但在 Java 语法层面,注解之间不存在类那样的显式继承关系(即没有 extends 关键字)
@RequestMapping 使用
@RequestMapping
既可修饰类,也可以修饰方法,同时修饰类和方法时,访问的地址是类路径 + 方法路径
添加类路径的好处:
- 提高程序的可读性
- 减少url的冲突
@RestController
@RequestMapping("/user")
public class UserController {
@RequestMapping("/sayHi")
public String sayHi(){
return "hello,Spring MVC";
}
}
注意要重启程序
访问地址:http://127.0.0.1:8080/user/sayHi
@RequestMapping
的 URL 路径也可以是多层路径,最终访问时,依然是类路径 + 方法路径
@RestController
@RequestMapping("user/aa")
public class UserController {
@RequestMapping("sayHi")
public String sayHi(){
return "hello,Spring MVC";
}
}
最前面的/
可以省略但不建议,访问路径http://127.0.0.1:8080/user/aa/sayHi
@RequestMapping支持接收的是 GET 还是 POST 请求?
上面展示的浏览器请求成功说明是支持GET请求的,接着我们可以通过 form 表单来构造POST请求,或者直接用Postman测试就行了
Postman下载链接 https://www.postman.com/downloads/
,下面我用表单的方式测试
创建test.html
:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<form action="/user/sayHi" method="post">
<input type="submit" value="提交">
</form>
</body>
</html>
注意把后端写的访问路径改回/user/sayHi
,还有前端代码要放在 static 目录下
访问方式为http://127.0.0.1:8080/test.html
从运行结果可以看出: @RequestMapping
既支持GET请求, 又支持POST请求。而且也支持其他所有 HTTP 请求方法
指定请求方法类型
我们可以显示的指定 @RequestMapping
只支持接收 POST和GET
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/user")
public class UserController {
@RequestMapping(value = "/sayHi", method = {RequestMethod.GET, RequestMethod.POST})
public String sayHi(){
return "hello,Spring MVC";
}
}
也可以使用@GetMapping
表示只支持接收GET,当然也有PostMapping
,PatchMapping
等
传递参数
传递单个参数
@RestController
@RequestMapping("/param")
public class ParamController {
@RequestMapping("/1")
public String func(String name){
return "接收到参数: " + name;
}
}
咱们使用浏览器发送请求并传参,访问http://127.0.0.1:8080/param/1?name=Spring
Spring MVC 会根据方法的参数名找到对应的参数并赋值
如果参数名不一致, 是获取不到参数的。比如请求URL: http://127.0.0.1:8080/param/1?name1=Spring
注意事项
使用基本类型来接收参数时, 参数必须传(除boolean类型默认为false), 否则会报500错误
类型不匹配时, 会报400错误
@RestController
@RequestMapping("/param")
public class ParamController {
@RequestMapping("/int")
public String func(int age){
return "接收到参数: " + age;
}
}
- 正常传递参数
http://127.0.0.1:8080/param/int?age=1
- 不传递age参数
http://127.0.0.1:8080/param/int
程序也会有错误日志
一般看日志堆栈信息的首行,报错信息显示:int 类型的参数 ‘age’,虽然为可选的,但由于被声明为基本类型而不能转换为空值。考虑将其声明为对应基本类型的包装类型
- 传递参数类型不匹配
http://127.0.0.1:8080/param/int?age=abc
对于包装类型,如果不传对应参数,Spring 接收到的数据为 null,建议使用包装类型
传递多个参数
@RestController
@RequestMapping("/param")
public class ParamController {
@RequestMapping("/2")
public String func(String name, int age){
return "姓名: " + name + "\n年龄: " + age;
}
}
使用浏览器发送请求并传参http://127.0.0.1:8080/param/2?name=%E5%BC%A0%E4%B8%89&age=18
有多个参数时,前后端进行参数匹配时,是以参数的名称进行匹配的,因此参数的位置是不影响后端获取的结果
比如访问 http://127.0.0.1:8080/param/2?age=18&name=%E5%BC%A0%E4%B8%89
同样可以拿到正确的结果
传递对象
如果参数比较多,方法声明就需要很多形参。并且每新增一个参数,就需要修改方法声明,我们不妨把这些参数封装为一个对象
Spring MVC 自动实现对象参数的赋值,注意要有setter方法:
public class Person {
private String name;
private int age;
public void setName(String name) {
this.name = name;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
传递对象代码实现:
@RestController
@RequestMapping("/param")
public class ParamController {
@RequestMapping("/object")
public String func(Person person){
return person.toString();
}
}
使用浏览器发送请求并传参:http://127.0.0.1:8080/param/object?name=%E5%BC%A0%E4%B8%89&age=18
Spring 会根据参数名称自动绑定到对象的各个属性上,如果包装类属性未传递,则赋值为null(基本类型则赋值为默认值,比如int类型的属性,会被赋值为0)
@RequestParam——后端参数重命名(后端参数映射)
如果前端传递的参数名和我们后端接收的参数名不一致,比如前端传递了一个 time 给后端,而后端使用 createTime 字段来接收,这样就会出现参数接收不到的情况,如果出现这种情况,我们可以使用 @RequestParam
来重命名后端的参数名
具体示例如下,后端实现代码:
@RestController
@RequestMapping("/param")
public class ParamController {
@RequestMapping("/requestParam")
public String func(@RequestParam("time") String createTime){
return "接收到参数: " + createTime;
}
}
使用浏览器发送请求并传参: http://127.0.0.1:8080/param/requestParam?time=2023-09-12
可以看到, Spring正确把浏览器传递的参数time绑定到了后端参数careteTime上
此时, 如果浏览器使用createTime进行参数传递呢?
访问URL: http://127.0.0.1:8080/param/requestParam?careteTime=2023-09-12
警告日志为:
控制台打印日志显示:请求参数 ‘time’ 不存在
可以得出结论:
- 使用
@RequestParam
进行参数重命名时, 请求参数只能和@RequestParam
声明的名称一致才能进行参数绑定和赋值,原来的参数名就不能用了 - 使用
@RequestParam
进行参数重命名时, 参数默认是必传参数
设置非必传参数
我们可以通过设置 @RequestParam
中的 required=false 来避免不传递时的报错
具体实现如下:
@RestController
@RequestMapping("/param")
public class ParamController {
@RequestMapping("/requestParam")
public String func(@RequestParam(value = "time", required = false) String createTime){
return "接收到参数: " + createTime;
}
}
访问URL: http://127.0.0.1:8080/param/requestParam?careteTime=2023-09-12
传递数组
后端实现代码:
@RestController
@RequestMapping("/param")
public class ParamController {
@RequestMapping("/arr")
public String func(String[] arrayParam){
return Arrays.toString(arrayParam);
}
}
使用浏览器发送请求并数组传参:请求参数名与形参数组名称相同且请求参数为多个, 后端定义数组类型的形参即可接收参数
第一种:http://127.0.0.1:8080/param/arr?arrayParam=zhangsan&arrayParam=lisi&arrayParam=wangwu
第二种: http://127.0.0.1:8080/param/arr?arrayParam=zhangsan,lisi,wangwu
浏览器响应结果:
可以看到后端对数组参数进行了正确的接收和响应
传递集合
默认情况下,请求中参数名相同的多个值,是封装到数组中。如果要封装到集合,要使用@RequestParam
绑定参数关系
后端接收代码:
@RestController
@RequestMapping("/param")
public class ParamController {
@RequestMapping("/list")
public String func(@RequestParam List<String> listParam){
return listParam.toString();
}
}
请求方式和数组类似,浏览器传参:
方式一:http://127.0.0.1:8080/param/list?listParam=zhangsan&listParam=lisi&listParam=wangwu
方式二:http://127.0.0.1:8080/param/list?listParam=zhangsan,lisi,wangwu
传递JSON(@RequestBody)
JSON 概念
JSON: JavaScript Object Notation 【JavaScript 对象表示法】
简单来说: JSON 就是一种数据格式,采用完全独立于编程语言的文本格式来存储和表示数据,使用文本表示一个对象或数组的信息,因此JSON 本质是字符串。主要负责在不同的语言中数据传递和交换
JSON 与 Javascript 没有关系,只是语法相似,js 开发者能更快的上手而已,但是他的语法本身比较简单,所以也很好学
JSON 语法
JSON 是一个字符串,其格式非常类似于 JavaScript 对象字面量的格式
我们先来看一段JSON数据
{
"squadName": "Super hero squad",
"homeTown": "Metro City",
"formed": 2016,
"secretBase": "Super tower",
"active": true,
"members": [
{
"name": "Molecule Man",
"age": 29,
"secretIdentity": "Dan Jukes",
"powers": [
"Radiation resistance",
"Turning tiny",
"Radiation blast"
]
},
{
"name": "Madame Uppercut",
"age": 39,
"secretIdentity": "Jane Wilson",
"powers": [
"Million tonne punch",
"Damage resistance",
"Superhuman reflexes"
]
},
{
"name": "Eternal Flame",
"age": 1000000,
"secretIdentity": "Unknown",
"powers": [
"Immortality",
"Heat Immunity",
"Inferno",
"Teleportation",
"Interdimensional travel"
]
}
]
}
可以压缩表示:
{"squadName":"Super hero squad","homeTown":"Metro City","formed":2016,"secretBase":"Super tower","active":true,"members":[{"name":"Molecule Man","age":29,"secretIdentity":"Dan Jukes","powers":["Radiation resistance","Turning tiny","Radiation blast"]},{"name":"Madame Uppercut","age":39,"secretIdentity":"Jane Wilson","powers":["Million tonne punch","Damage resistance","Superhuman reflexes"]},{"name":"Eternal Flame","age":1000000,"secretIdentity":"Unknown","powers":["Immortality","Heat Immunity","Inferno","Teleportation","Interdimensional travel"]}]}
可以在网上随便搜一个json在线解析判断格式是否正确
JSON 的语法:
- 数据保存在键值对 (Key/Value) 中
- 键和值用
:
分隔 - 键值对用
,
分割 - 对象用 {} 表示
- 数组用 [] 表示
- 值可以为对象,也可以为数组,数组中可以包含多个对象,对象中也可以有数组
JSON 字符串和 Java 对象互转
JSON 本质上是一个字符串,通过文本来存储和描述数据
Spring MVC 框架也集成了 JSON 的转换工具(jackson-databind工具包),我们可以直接使用,来完成 JSON 字符串和 Java 对象的互转。如果脱离 Spring MVC 使用,需要引入相关依赖
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.19.0</version>
</dependency>
使用ObjectMapper 对象提供的两个方法, 可以完成对象和JSON字符串的互转
- writeValueAsString: 把对象转为JSON字符串
- readValue: 把字符串转为对象
JSON优点
- 简单易用: 语法简单,易于理解和编写,可以快速地进行数据交换
- 跨平台支持: JSON可以被多种编程语言解析和生成, 可以在不同的平台和语言之间进行数据交换和传输
- 轻量级: 相较于XML格式, JSON数据格式更加轻量级, 传输数据时占用带宽较小, 可以提高数据传输速度
- 易于扩展: JSON的数据结构灵活,支持嵌套对象和数组等复杂的数据结构,便于扩展和使用
- 安全性: JSON数据格式是一种纯文本格式,不包含可执行代码, 不会执行恶意代码,因此具有较高的安全性
基于以上特点, JSON在Web应用程序中被广泛使用, 如前后端数据交互、API接口数据传输等.
传递JSON对象
如果 JSON 数据位于 请求体 中(如 POST 请求的 Body),必须使用 @RequestBody
注解,否则 Spring 无法识别请求体中的数据。@RequestBody
注解的作用是将 HTTP 请求的 正文(Body) 中的数据绑定到方法参数上
- 确保请求体格式与 Content-Type 一致
- 实体类的字段名和类型需与 JSON 匹配
后端实现:
@RestController
@RequestMapping("/param")
public class ParamController {
@RequestMapping("/JSON")
public String func(@RequestBody Person person){
return person.toString();
}
}
使用Postman来发送json请求参数:
Body 格式 | 适用场景 |
---|---|
raw | JSON、XML、纯文本等自定义格式 |
x-www-form-urlencoded | 普通表单提交(键值对) |
form-data | 文件上传、混合表单数据 |
binary | 直接上传二进制文件(如图片) |
可以看到, 后端正确接收参数并返回了,而且请求头自动加了 Content-Type
是 application/json
去掉 @RequestBody
再试试
未能成功给Person对象赋值
获取URL中的参数@PathVariable(翻译:路径变量)
和字面表达的意思一样, 这个注解主要作用在请求URL路径上的数据绑定
后端实现代码:
@RestController
@RequestMapping("/param")
public class ParamController {
@RequestMapping("/{name}/{age}")
public String func(@PathVariable String name, @PathVariable int age ){
return "name: " + name + ", age: " + age;
}
}
使用浏览器发送请求: http://127.0.0.1:8080/param/zhangsan/13
- 如果方法参数名和 URL 中的变量名一致,可以不用给
@PathVariable
的属性赋值 - 如果方法参数名和 URL 中的变量名不一致,需要给
@PathVariable
的属性 value 赋值
@RestController
@RequestMapping("/param")
public class ParamController {
@RequestMapping("/{name}/{age}")
public String func(@PathVariable("name") String userName, @PathVariable int age ){
return "name: " + userName + ", age: " + age;
}
}
传递文件 @RequestPart
后端代码实现:
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import java.io.File;
import java.io.IOException;
@RestController
@RequestMapping("/param")
public class ParamController {
@RequestMapping("/fileUpload")
public String func(@RequestParam("fileName") MultipartFile file) {
if (file.isEmpty()) {
return "上传失败,请选择文件";
}
try {
// 指定保存路径
String filePath = "D:/temp/";
String fileName = file.getOriginalFilename();
// 创建目标目录(如果不存在)
File directory = new File(filePath);
if (!directory.exists()) {
boolean created = directory.mkdirs();
if (!created) {
return "创建目录失败";
}
}
// 保存文件
File dest = new File(filePath + fileName);
file.transferTo(dest);
return "上传成功";
} catch (IOException e) {
e.printStackTrace();
return "上传失败: " + e.getMessage();
}
}
}
在Postman使用http://127.0.0.1:8080/param/fileUpload
这个URL发送POST请求:
注意KEY的名称要和@RequestParam
的value属性值一样
成功后可以看一下D:/temp/
目录下是否真的有这个文件
GET与POST的传参方式
上面的代码验证了能接收GET请求中URL的参数以及能接收POST请求中请求体的参数
GET + body(请求体)实际测试:请求体内容不会被解析,接口可能返回 400 或空内容。这里主要验证POST + params(URL参数)
@RestController
public class ParamController {
// POST + params(URL参数):可用但少见 ⚠️
@PostMapping("/post/params")
public String postWithParams(String name, Integer age) {
return "POST params: name=" + name + ", age=" + age;
}
}
其实这样也可以接收body中的form-data里的数据而且优先级比POST + params(URL参数)高
Cookie与Session
HTTP 协议自身属于 “无状态” 协议
“无状态” 的含义:默认情况下 客户端和服务器之间的这次通信, 和下次通信之间没有直接的联系,但是实际开发中, 很多时候我们是需要知道请求之间的关联关系的。例如登陆网站成功后, 第二次访问的时候服务器就能知道该请求是否已经登陆过了
理解Session(会话)
在计算机领域, 会话是一个客户与服务器之间的不中断的请求响应。对客户的每个请求,服务器能够识别出请求来自于同一个客户。当客户明确结束会话或服务器在一个时限内没有接受到客户的任何请求时,会话就结束了
服务器同一时刻收到的请求是很多的。服务器需要清楚的区分每个请求是从属于哪个用户, 也就是属于哪个会话, 就需要在服务器这边记录每个会话以及与用户的信息的对应关系。Session就是服务器为了保存用户信息而创建的一个特殊的对象
Session的本质就是一个 “哈希表”, 存储了一些键值对结构。Key 就是SessionID, Value 就是用户信息(用户信息可以根据需求灵活设计)
SessionId 是由服务器生成的一个 “唯一性字符串”, 从 Session 机制的角度来看, 这个唯一性字符串称为 “SessionId”;但是站在整个登录流程中看待, 也可以把这个唯一性字符串称为 “token”
- 当用户第一次登陆的时候,服务器在 Session 中新增一个记录,并把 sessionId 返回给客户端(通过 HTTP 响应中的 Set-Cookie 字段返回)
- 客户端后续再给服务器发送请求的时候,需要在请求中带上 sessionId(通过 HTTP 请求中的 Cookie 字段带上)
- 服务器收到请求之后,根据请求中的 sessionId 在 Session 信息中获取到对应的用户信息,再进行后续操作。找不到则重新创建 Session, 并把 SessionID 返回
Session 默认是保存在内存中的,如果重启服务器 Session 数据就会丢失
Cookie 和 Session
- Cookie 是客户端保存用户信息的一种机制;Session 是服务器端保存用户信息的一种机制
- Cookie 和 Session 之间主要通过 SessionId 关联,SessionId 是 Cookie 和 Session 之间的桥梁
- Cookie 和 Session 经常⼀起配合使用,但不是必须配合
- 完全可以用 Cookie 来保存⼀些数据在客户端。 这些数据不⼀定是用户身份信息, 也不⼀定是SessionId
- Session 中的sessionId 也不非得通过 Cookie/Set-Cookie 传递,比如通过URL传递
有两种比较相等方式,选哪个?
if(cookie.getName().equals("userName")) {}
if("userName".equals(cookie.getName())) {}
选下面这种,上面这种会有空指针异常,下面这种则不会
public class Test {
public static void main(String[] args) {
String str = null;
if (str.equals("abc")) {
System.out.println("相等");
} else {
System.out.println("不相等");
}
}
}
public class Test {
public static void main(String[] args) {
String str = null;
if ("abc".equals(str)) {
System.out.println("相等");
} else {
System.out.println("不相等");
}
}
}
获取Cookie
方法一(可以获取到所有请求信息,从中取出Cookie那部分)
先把下面代码运行起来
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class CookieSessionController {
@RequestMapping("/getCookie")
public String getCookie(HttpServletRequest request) {
//获取所有Cookie信息
Cookie[] cookies = request.getCookies();
if (cookies != null) {
System.out.println("获取Cookie信息");
for (Cookie cookie : cookies) {
System.out.println(cookie.getName()+":"+cookie.getValue());
}
}
return "获取Cookie成功";
}
}
Spring MVC 是基于 Servlet API 构建的 Web 框架。而HttpServletRequest ,HttpServletResponse 是Servlet提供的两个类, 是Spring MVC 方法的内置对象, 需要时直接在方法中添加声明即可
- HttpServletRequest 对象代表客户端的请求, 当客户端通过HTTP协议访问服务器时,通过这个对象提供的方法,可以获得客户端请求的绝大部分信息
- HttpServletResponse 对象代表服务器的响应, HTTP响应的大部分信息在这个对象中, 比如向客户端发送的数据, 响应头, 状态码等。
在网址中输入http://127.0.0.1:8080/getCookie
,该网页初始是没有Cookie的,所以控制台没有任何打印。现在我们设置Cookie:
按F12打开开发者模式,选中应用程序(Application)标签页,里面就有Cookie,选中domian是http://127.0.0.1:8080
直接以键值对的方式添加
添加完成之后刷新,控制台就会打印出来刚刚添加的Cookie了
从这个例子中可以看出Cookie是可以伪造的, 也就是不安全的, 所以使用Cookie时, 后端需要进行Cookie校验
方法二(使用注解)
import org.springframework.web.bind.annotation.CookieValue;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class CookieSessionController {
@RequestMapping("/getCookie")
public String getCookie(@CookieValue("name") String name) {
System.out.println("从Cookie中获取的name为: "+name);
return "获取Cookie成功";
}
}
在网址中输入http://127.0.0.1:8080/getCookie
设置Session
先把上面设置的Cookie删除,再运行下面代码:
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpSession;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class CookieSessionController {
@RequestMapping("/setSession")
public String setSession(HttpServletRequest request) {
//参数为true。如果Session对象不存在,则创建一个空的Session;如果存在,则直接拿到
HttpSession session = request.getSession(true);
session.setAttribute("name","张三");
session.setAttribute("studentId","100223");
return "设置Session成功";
}
}
在网址中输入http://127.0.0.1:8080/setSession
,通过抓包工具看到响应返回了一个Set-Cookie字段
可以看到,HTTP 响应通过 Set-Cookie 把SessionID告知客户端,客户端会把 SessionID 存储在 Cookie 中
之后只要是同一个客户端(不能是不同浏览器),并且访问的domain是http://127.0.0.1:8080
都会带上这个Cookie
可以通过Fiddler观察到
代码中看不到 SessionId 这样的概念,getSession 提取到 Cookie 里的 SessionId, 然后根据 SessionId 获取到对应的 Session 对象, Session 对象用 HttpSession 来描述
Session 存在内存中, 如果不做任何处理, 服务器重启后 Session 数据就丢失了
获取Session
- 第一种
运行下面代码:
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpSession;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class CookieSessionController {
@RequestMapping("/getSession")
public String getSession(HttpServletRequest request) {
//参数为false,如果不存在Session,则会返回null,不会自动创建
HttpSession session = request.getSession(false);
if (session != null) {
//Object getAttribute(String name): 返回在该 session 会话中具有指定名称的对象,如果不存在,则返回 null.
System.out.println(session.getAttribute("name"));
System.out.println(session.getAttribute("studentId"));
}
return "获取Session成功";
}
}
在网址中输入http://127.0.0.1:8080/getSession
,但没有看到任何的打印内容,这是因为重启了程序(服务器),所以设置Session和获取Session得在一个程序中
- 第二种
@RestController
public class CookieSessionController {
@RequestMapping("/getSession")
public String getSession(HttpSession session) {
//相当于调用了HttpSession session = request.getSession(true);
// Session 不存在的话, 会创建一个空的
System.out.println("name:"+session.getAttribute("name"));
System.out.println("studentId:"+session.getAttribute("studenId"));
return "获取Session成功";
}
}
- 第三种
@RestController
public class CookieSessionController {
@RequestMapping("/getSession")
public String getSession(@SessionAttribute(value = "name", required = false) String name) {
System.out.println("name:"+name);
return "获取Session成功";
}
}
请求
获取Header
- 第一种
使用 HttpServletRequest 提供的 getHeader 方法来获取 Header , 参数对应HTTP请求报头的Key
@RestController
@RequestMapping("/request")
public class RequestController {
@RequestMapping("/getHeader")
public String getHeader(HttpServletRequest request) {
String userAgent = request.getHeader("user-agent");
return "user-agent:"+userAgent;
}
}
- 第二种
@RequestHeader
注解的参数值为 HTTP 请求报头中的 Key
@RestController
@RequestMapping("/request")
public class RequestController {
@RequestMapping("/getHeader")
public String getHeader(@RequestHeader("user-agent") String userAgent) {
return "user-agent:"+userAgent;
}
}
获取body
上面传递JSON的@RequestBody
就是一种方式,也可以通过 HttpServletRequest 手动读取
响应
HTTP 的响应结果可以是数据,也可以是静态页面,还可以针对响应设置状态码等
返回静态页面
在resources目录下的static里面创建 index.html
(注意路径),内容如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>我是index页面</h1>
</body>
</html>
启动程序,在网址中输入http://127.0.0.1:8080/index.html
同时运行以下Java代码:
@RestController
@RequestMapping("/response")
public class ResponseController {
@RequestMapping("/index")
public String index() {
return "index.html";
}
}
在网址中输入http://127.0.0.1:8080/response/index
发现只是普通的字符串,而我们希望返回的是html页面
我们把@RestController
改成@Controller
,并且在index.html前面加上 /
//@RestController
@Controller
@RequestMapping("/response")
public class ResponseController {
@RequestMapping("/index")
public String index() {
return "/index.html";
}
}
重启程序,刷新浏览器页面
如果去掉index.html前面的 / 就会显示404(服务器找不到请求的网页),说明要改变文件目录
在static下面建一个response目录,把index.html移入里面
这样页面就能正常显示了
如果加了 / 就表示绝对路径,不加 / 则是相对路径。为了不搞混,以后统一加 /
@RestController与@Controller
@RestController
表示返回数据,并把这个对象交给Spring管理@Controller
表示返回视图,并把这个对象交给Spring管理@ResponseBody
表示返回数据
点进 @RestController
@RestController
=@Controller
+@ResponseBody
返回数据@ResponseBody
@ResponseBody
既是类注解, 又是方法注解。如果作用在类上, 表示该类的所有方法, 返回的都是数据, 如果作用在方法上, 表示该方法返回的是数据
也就是说,在类上添加@ResponseBody
就相当于在所有的方法上添加了@ResponseBody
同样, 如果类上有@RestController
就表示所有的方法上添加了@ResponseBody
, 也就是当前类下所有的方法的返回值都作为响应数据
如果一个类的方法里, 既有返回数据的, 又有返回页面的, 那么就把@Controller
加到类上,把@ResponseBody
添加到对应的方法上即可
@Controller
@RequestMapping("/response")
public class ResponseController {
@RequestMapping("/index")
public String returnHtml() {
return "/index.html";
}
@ResponseBody
@RequestMapping("/returnData")
public String returnData(){
return "该方法返回数据";
}
}
如果去掉 @ResponseBody 注解,程序会报 404 错误
@Controller
@RequestMapping("/response")
public class ResponseController {
@RequestMapping("/index")
public String returnHtml() {
return "/index.html";
}
@RequestMapping("/returnData")
public String returnData(){
return "该方法返回数据";
}
}
程序会认为返回的是视图,然后去查找文件,但是查询不到,路径不存在,报 404
返回HTML代码片段
后端响应数据时,如果数据中有 HTML 代码,也会被浏览器解析
@RestController
@RequestMapping("/response")
public class ResponseController {
@RequestMapping("/returnHtml")
public String returnHtml() {
return "<h1>我是HTML页面</h1>";
}
}
通过 Fiddler 观察响应结果,Content-Type 为 text/html
响应中的 Content-Type 常见取值有以下几种:
- text/html:body 数据格式是 HTML
- text/css:body 数据格式是 CSS
- application/javascript:body 数据格式是 JavaScript
- application/json:body 数据格式是 JSON
如果请求的是 js 文件,Spring MVC 会自动设置 Content-Type 为 application/javascript
如果请求的是 css 文件,Spring MVC 会自动设置 Content-Type 为 text/css
返回JSON
如果返回的是对象,会自动转成JSON,注意要有getter方法
@RestController
@RequestMapping("/response")
public class ResponseController {
@RequestMapping("/returnJSON")
public UserInfo returnJSON() {
return new UserInfo("zhangsan", 20);
}
}
class UserInfo {
private String name;
private int age;
public UserInfo(String name, int age) {
this.name = name;
this.age = age;
}
public int getAge() {
return age;
}
public String getName() {
return name;
}
}
@RestController
@RequestMapping("/response")
public class ResponseController {
@RequestMapping("/returnJSON")
public List<String> returnJSON() {
List<String> list = new ArrayList<>();
list.add("a");
list.add("b");
list.add("c");
return list;
}
}
通过Fiddler观察响应结果, Content-Type 为 application/json
设置状态码
Spring MVC 会根据我们方法的返回结果自动设置响应状态码,也可以通过 Spring MVC 的内置对象 HttpServletResponse 提供的方法来手动指定状态码
@RestController
@RequestMapping("/response")
public class ResponseController {
@RequestMapping("/setStatus")
public String setStatus(HttpServletResponse response) {
response.setStatus(401);
return "设置状态码成功";
}
}
虽然设置并返回的状态码是401,但是不影响界面显示(其实是没问题的请求和响应)
通过Fiddler来观察设置的结果:
设置Content-Type
因为下面这段代码返回的不是对象,所以默认返回的是字符串
@RestController
@RequestMapping("/response")
public class ResponseController {
@RequestMapping("/r1")
public String r1() {
return "{\"status\":400}";
}
}
我们通过设置@RequestMapping
的 produces 属性,来设置响应报头的 Content-Type
如果不设置produces , 方法返回结果为String时,Spring MVC默认返回类型是text/html,现在我们可以设置成JSON
设置返回类型时, 也可以同步设置响应编码
@RestController
@RequestMapping("/response")
public class ResponseController {
@RequestMapping(value = "/r1", produces = "application/json;charset=UTF-8")
public String r1() {
return "{\"status\":400}";
}
}
通过 Fiddler 来观察设置的结果:
下面这段代码效果一样
@RestController
@RequestMapping("/response")
public class ResponseController {
@RequestMapping(value = "/r1")
public String r1(HttpServletResponse response){
response.setContentType("application/json;charset=UTF-8");
return "{\"status\":400}";
}
}
增加Header字段
可以使用 Spring MVC 的内置对象 HttpServletResponse 提供的方法来进行设置
@RestController
@RequestMapping("/response")
public class ResponseController {
@RequestMapping("/setHeader")
public void setHeader(HttpServletResponse response) {
response.setHeader("MyHeader", "MyHeaderValue");
}
}
综合性练习(加法计算器)
主要掌握知识点:
- 理解前后端交互过程
- 接口传参, 数据返回, 以及页面展示
需求: 输入两个整数, 点击"点击相加"按钮, 显示计算结果
服务器代码:
@RestController
@RequestMapping("/calc")
public class CalcController {
@RequestMapping("/sum")
public String sum(Integer num1,Integer num2){
Integer sum = num1+num2;
return "<h1>计算结果: "+sum+"</h1>";
}
}
前端页面代码:
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>计算器</title>
</head>
<body>
<form action="calc/sum" method="post">
<h1>计算器</h1>
数字1:<input name="num1" type="text"><br>
数字2:<input name="num2" type="text"><br>
<input type="submit" value=" 点击相加">
</form>
</body>
</html>
总结
学习 Spring MVC, 其实就是学习各种 Web 开发用的到的注解
a. @RequestMapping
:路由映射
b. @RequestParam
:后端参数重命名
c. @RequestBody
:接收 JSON 类型的参数
d. @PathVariable
:接收路径参数
e. @RequestPart
:上传文件
f. @ResponseBody
:返回数据
g. @CookieValue
:从 Cookie 中获取值
h. @SessionAttribute
:从 Session 中获取值
i. @RequestHeader
:从 Header 中获取值
j. @Controller
: 定义一个控制器,Spring 框架启动时加载,把这个对象交给 Spring 管理。默认返回视图
k. @RestController
: @ResponseBody
+ @Controller