
Spring Web MVC是基于Servlet API构建的原始Web框架,从一开始就包含在Spring框架中,其正式名称来源于源模块名称(spring-webmvc),通常简称为Spring MVC。
官方定义:Spring Web MVC is the original web framework built on the Servlet API and has been included in the Spring Framework from the very beginning.
Servlet是Java Web开发的规范,定义了动态页面开发的技术标准,而Tomcat、Weblogic等Servlet容器则是该规范的具体实现,负责管理开发者编写的Servlet类。Spring MVC基于Servlet规范实现,为Web应用开发提供了完整的解决方案。
MVC是Model View Controller的缩写,是软件工程中的一种软件架构设计模式,将软件系统分为模型、视图和控制器三个基本部分,实现业务逻辑与界面展示的解耦。
MVC定义图

Spring MVC定义图

以饭店就餐场景为例,直观理解MVC组件的分工:
Spring Boot并非替代Spring MVC的框架,而是实现Spring MVC的一种便捷方式。两者的关系可通过下表清晰区分:
| 特性 | Spring MVC | Spring Boot |
|---|---|---|
| 本质 | Web框架,实现MVC模式 | 快速开发脚手架 |
| 核心作用 | 处理Web请求与响应 | 简化Spring应用配置与部署 |
| 依赖关系 | 可独立使用 | 可集成Spring MVC实现Web功能 |
| 发布时间 | 2004年 | 2014年 |
类比理解:Spring Boot如同一个功能完备的厨房,而Spring MVC则是厨房中实现烹饪功能的燃气灶和厨具。厨房(Spring Boot)可通过配置不同工具实现多种功能,而烹饪(Web开发)功能的核心实现依赖于燃气灶(Spring MVC)。
Spring MVC项目通过Spring Boot方式创建,步骤如下:
创建后的项目pom.xml文件中,Spring Web依赖自动引入,核心依赖代码如下:
|
1 2 3 4 |
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> |
该依赖包含Spring MVC核心组件、Servlet API、Tomcat嵌入式容器等关键资源,无需额外配置即可实现Web功能开发。
@RequestMapping是Spring MVC中最基础的路由映射注解,用于将URL请求与Java方法关联,支持修饰类和方法。
基础使用示例:
|
1 2 3 4 5 6 7 8 9 |
@RestController @RequestMapping("/user") // 类级路由前缀 public class UserController { // 方法级路由,完整访问路径:/user/sayHi @RequestMapping("/sayHi") public String sayHi() { return "hello, Spring MVC"; } } |
用于解决前后端参数名称不一致问题,同时可设置参数是否必传:
|
1 2 3 4 5 |
@RequestMapping("/r6") // 此处@RequestParam("sa")注解的作用是参数重命名, public String r6(@RequestParam(value = "sa", required = false) String resouce){ return "接收到 resouce = " + resouce; } |
用于从URL路径中直接提取参数,适用于RESTful风格接口:
|
1 2 3 4 |
@RequestMapping("/r11/{articleId}/{type}") public String r11(@PathVariable("articleId") Integer id, @PathVariable String type){ return "接收到的参数 articleId = " + id + ", type = " + type; } |
访问路径示例:http://127.0.0.1:8080/param/m8/5/zhangsan,其中5和zhangsan分别映射到id和userName参数。
两者区别示例:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
// 返回视图(需配合模板引擎) @Controller public class IndexController { @RequestMapping("/index") public String index() { return "/index.html"; } } // 返回数据 @RestController public class DataController { @RequestMapping("/data") public String getData() { return "这是返回的数据内容"; } } |
直接通过方法参数接收前端传递的参数,参数名称需与请求参数一致:
|
1 2 3 4 |
@RequestMapping("/r1") public String r1(String name){ return "接收到参数 name = " + name; } |
通过postman创建一个带有name参数请求访问示例:
http://127.0.0.1:8080/request/r1?name=zhangsan

支持多个参数同时接收,参数顺序不影响绑定结果(请求的参数名称保持一致即可):
|
1 2 3 4 5 |
@RequestMapping("/r4") public String r4(String name, String password, Integer age){ // 此处字符串拼接不考虑性能 return "接收到参数 name = " + name + ", password = " + password + ", age = " + age; } |
通过postman创建一个带有age,password,name参数请求访问示例:
http://127.0.0.1:8080/request/r4?age=20&name=zhangsan&password=123456

当参数较多时,可封装为实体类接收,Spring会自动按属性名绑定:
|
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 |
// 实体类 public class Person { private String name; private String password; private int age; public String getName() { return name; } public String getPassword() { return password; } public int getAge() { return age; } public void setName(String name) { this.name = name; } public void setPassword(String password) { this.password = password; } public void setAge(int age) { this.age = age; } @Override public String toString() { return "Person{" + "name='" + name + '\'' + ", password='" + password + '\'' + ", age=" + age + '}'; } } // 接口方法 @RequestMapping("/r5") public String r4(Person person){ return "接收到参数 person = " + person; } |
创建post请求访问示例:http://127.0.0.1:8080/request/r5

通过@RequestParam()注解使得参数进行重命名与前端发的请求保持一致
且加了这个注解,此参数就为必传参数,如果不传则会报出400状态,除非将@RequestParam("sa")修改为@RequestParam(value = "sa", required = false)
|
1 2 3 4 5 |
@RequestMapping("/r6") // 此处@RequestParam()注解的作用是参数重命名 public String r6(@RequestParam("sa") String resouce){ return "接收到 resouce = " + resouce; } |
访问示例:http://127.0.0.1:8080/request/r6?sa=fyb_n_homepage

数组参数接收:
|
1 2 3 4 |
@RequestMapping("/r7") public String r7(String[] names){ return "接收到参数 names = " + Arrays.toString(names); } |
访问示例:
http://127.0.0.1:8080/request/r7?names=zhangsan&names=lisi&names=wangwu

也可以使用
http://127.0.0.1:8080/request/r7?names=zhangsan,lisi,wangwu

集合参数接收(需配合@RequestParam):
|
1 2 3 4 5 |
@RequestMapping("/r8") // @RequestParam 在这里的作用是将请求中的参数绑定到方法的 List<String> names 参数上 public String r8(@RequestParam List<String> names){ return "接收到的参数 names = " + names; } |
未使用@RequestPram绑定如图会报出500错误

绑定@RequestPram则正常状态

JSON(JavaScript Object Notation,JavaScript 对象表示法)是一种轻量级的文本数据交互格式 ,基于 ECMAScript 规范的子集,采用独立于编程语言的语法存储和描述数据,核心作用是实现不同语言、不同系统间的数据传递与交换。
其核心优势如下:
JSON 语法遵循严格规则,错误语法会导致解析失败,需重点关注以下要点:
1. 基础语法规则:
2. 两种核心结构:
|
1 2 3 4 5 6 7 |
{ "userId": 1001, "userName": "zhangsan", "age": 25, "isStudent": false, "hobbies": ["reading", "coding"] } |
数组结构:用中括号[]包裹,存储有序值列表,值类型可混合(如字符串、数字、对象),适用于描述多个同类实体(如用户列表、商品列表),示例:
|
1 2 3 4 5 6 |
[ {"bookId": 1, "bookName": "Spring MVC实战"}, {"bookId": 2, "bookName": "Java核心技术"}, 3.14, "JSON教程" ] |
3. 合法 JSON 示例:
单个对象:{"name":"admin","age":18}
纯数组:["hello", 3.14, true]
对象数组:[{"name":"admin","age":18},{"name":"root","age":20}]
可通过在线 JSON 校验工具(如)验证语法正确性,避免因格式错误导致接口解析失败。
Spring MVC 框架默认集成jackson-databind工具包(Spring Web 依赖已包含),无需额外引入,可直接实现 JSON 字符串与 Java 对象的 “序列化”(对象转 JSON)和 “反序列化”(JSON 转对象)。
转换工具类实现
通过ObjectMapper类(jackson-databind 核心类)提供的方法完成转换,示例代码如下:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
package cn.overthinker.springboot; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import org.junit.jupiter.api.Test; public class JSONTests { private static ObjectMapper objectMapper = new ObjectMapper(); public static void main(String[] args) throws JsonProcessingException { Person person = new Person(); person.setName("zhangsan"); person.setAge(18); person.setPassword("123456"); // 对象转json字符串 String s = objectMapper.writeValueAsString(person); System.out.println(s); //json字符串转对象 String json = "{\"name\":\"zhangsan\",\"age\":18,\"password\":\"123456\"}"; Person person1 = objectMapper.readValue(json, Person.class); System.out.println(person1); } } |
关键方法说明
|
1 2 3 4 |
List<Person> personList = objectMapper.readValue( "[{\"id\":1,\"name\":\"zhangsan\"},{\"id\":2,\"name\":\"lisi\"}]", new TypeReference<List<Person>>() {} ); |
1. 前端请求方式(以 Postman 为例)
Postman 是后端开发常用的接口测试工具,传递 JSON 参数步骤如下:

2. 后端接口实现
后端需通过@RequestBody注解绑定请求正文,触发 Spring 的 JSON 解析逻辑,实现参数自动封装。
|
1 2 3 4 |
@RequestMapping("/r9") public String r9(@RequestBody Person person){ return "接收到的参数 person = " + person; } |
3. Fiddler抓包结果

使用@RequestPart注解实现文件上传功能:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
@RequestMapping("/r12") // 处理文件上传的请求方法,MultipartFile是Spring提供的文件上传封装类 // throws IOException:声明可能抛出的IO异常(文件读写时可能发生) public String r12(MultipartFile file) throws IOException{ // 1. 获取表单中文件参数的名称(即前端<input type="file" name="file">中的name值) // 注意:此名称是前端表单控件的name属性值,而非实际文件名 System.out.println("表单文件参数名:" + file.getName()); // 2. 获取上传文件的原始名称(包含文件名和扩展名,如"test.jpg") // 该名称来自客户端本地文件的名称,可能包含路径(不同浏览器处理不同,需注意) System.out.println("文件原始名称:" + file.getOriginalFilename()); // 3. 获取文件的MIME类型(如图片为"image/jpeg",文本为"text/plain") // 用于判断文件类型,可用于限制上传文件格式(如只允许图片上传) System.out.println("文件MIME类型:" + file.getContentType()); // 4. 定义文件保存路径和文件名 // 这里将文件保存到本地"D:/temp/"目录,文件名使用原始文件名 // 注意:需确保"D:/temp/"目录已存在,否则会抛出文件找不到异常 File destFile = new File("D:/temp/" + file.getOriginalFilename()); // 5. 将上传的文件内容转移到目标文件中 // transferTo()是Spring提供的便捷方法,内部处理了文件流的读写和关闭 // 替代了传统的InputStream/OutputStream手动读写逻辑,更安全高效 file.transferTo(destFile); // 6. 返回上传成功的提示信息 return "接受到文件,已保存至D:/temp目录"; } |

下述图中的"令牌"通常就存储在 Cookie 字段中,

方式一:
通过HttpServletRequest 和HttpServletResponse 获取Cookie:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
import jakarta.servlet.http.Cookie; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController @RequestMapping("/request2") public class RequestController2 { /*** * 读取cookie */ @RequestMapping("/getCookie") public String getCookie(HttpServletRequest request, HttpServletResponse response) { Cookie[] cookies = request.getCookies(); if(cookies != null){ for(Cookie cookie : cookies){ System.out.println(cookie.getName() + ":" + cookie.getValue()); } } return "获取Cookie成功"; } } |
方式二:
注解的方式
|
1 2 3 4 5 |
@RequestMapping("/getCookie2") // 要求客户端必须返回含class名称的cookie public String getCookie2(@CookieValue("class") String className) { return "获取Cookie成功 className:" + className; } |
演示步骤




Session存储与读取:
方案一
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
@RequestMapping("/setSession") public String setSession(HttpServletRequest request){ HttpSession session = request.getSession(); session.setAttribute("name", "zhangsan"); session.setAttribute("age", 20); return "设置Session成功"; } @RequestMapping("/getSession") public String getSession(HttpServletRequest request){ HttpSession session = request.getSession(); String name = (String) session.getAttribute("name"); Integer age = (Integer) session.getAttribute("age"); return "从session中获取数据, name:" + name + ", age:" + age; } |
因为Session 是存储在服务器端的会话数据, 客户端(浏览器)无法直接访问服务器内存里的 Session 内容,只能通过后端接口 “间接读取”——getSession接口的作用就是把服务器内存里的 Session 数据返回给客户端,让前端能拿到这些信息(比如展示用户名称)。
在浏览器输入http://127.0.0.1:8080/request2/setSession刷新页面观察到session已经设置成功,同时打开f12,发现有一行数据就是sessionID

打开另一标签页输入http://127.0.0.1:8080/request2/getSession,可以观察到保存在session里面的信息

方案二:使用HttpSession
|
1 2 3 4 5 6 7 8 |
// 通过HttpSession直接获取session @RequestMapping("/getSession") public String getSession(HttpSession session){ // 获取用户信息 String name = (String) session.getAttribute("name"); Integer age = (Integer) session.getAttribute("age"); return "从session中获取数据, name:" + name + ", age:" + age; } |
方案三:通过@SessionAttribute注解
|
1 2 3 4 5 |
@RequestMapping("/getSession3") public String getSession3(@SessionAttribute("name") String name){ // 获取用户信息 return "从session中获取数据, name:" + name; } |


提问与思考
当我们将name改为可自己输入的参数时,然后用设置不同的cookie的name参数值,那么它们如果交换sessionid,保存的信息也会交换吗?????
使用代码
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
/*** * 读取session * @param request * @return */ @RequestMapping("/setSession") public String setSession(HttpServletRequest request, String name){ HttpSession session = request.getSession(); session.setAttribute("name", name); session.setAttribute("age", 20); return "设置Session成功"; } // 通过HttpSession直接获取session @RequestMapping("/getSession2") public String getSession(HttpSession session){ // 获取用户信息 String name = (String) session.getAttribute("name"); Integer age = (Integer) session.getAttribute("age"); return "从session中获取数据, name:" + name + ", age:" + age; } |
创建第一个Session(Chrome)
http://127.0.0.1:8080/request2/setSession?name=wangwu


得到的第一个SessionID为B76C84D38B4C522CAD16AE849F23414F
创建第二个Session(EDGE)


得到的第一个SessionID为FFC3B941551E94012D87BBBF3C95BC8A
将edge的sessionid替换为chrome的sessionid,观察现象

很明显的看到name值已经发生了改变,说明了Session 是基于服务器端的会话标识(SessionID)来区分用户的,不同客户端(浏览器)通过 SessionID 关联到对应的 Session 数据,同时服务器完全依赖 SessionID 来定位对应的 Session 数据
方案一
|
1 2 3 4 5 |
@RequestMapping("/getHeader") public String getHeader(HttpServletRequest request){ String userAgent = request.getHeader("User-Agent"); return "从Header获取数据, userAgent" + userAgent; } |
方案二
使用@RequestHeader注解
|
1 2 3 4 5 |
//使用注解 @RequestMapping("/getHeader2") public String getHeader(@RequestHeader("User-Agent") String userAgent){ return "从Header获取数据, userAgent" + userAgent; } |
运行结果(方案一)

返回静态页面需使用@Controller注解(而非@RestController):

|
1 2 3 4 5 6 7 |
@Controller public class IndexController { @RequestMapping("/index") public Object index() { return "/index.html"; } } |
静态页面需放置在resources/static目录下,访问路径为:http://127.0.0.1:8080/index
Spring MVC可自动将对象转换为JSON格式响应:
示例一
|
1 2 3 4 5 6 7 8 9 |
@RequestMapping("/returnJson") @ResponseBody public HashMap<String, String> returnJson() { HashMap<String, String> map = new HashMap<>(); map.put("Java", "Java Value"); map.put("MySQL", "MySQL Value"); map.put("Redis", "Redis Value"); return map; } |
响应结果:
|
1 |
{"Java":"Java Value","MySQL":"MySQL Value","Redis":"Redis Value"} |
示例二
|
1 2 3 4 5 6 7 8 9 10 11 12 |
@ResponseBody @RequestMapping("/returnJSON") public Person returnJSON(){ // 实例化数据模型对象 Person person = new Person(); // 设置对象属性 person.setName("zhangsan"); person.setAge(18); person.setPassword("123456"); // 返回对象,由 @ResponseBody 自动转换为 JSON return person; } |
响应结果:
|
1 |
{"name":"zhangsan","password":"123456","age":18} |
设置响应状态码:
|
1 2 3 4 5 6 |
@RequestMapping("/setStatus") @ResponseBody public String setStatus(HttpServletResponse response) { response.setStatus(401); // 未授权状态码 return "设置状态码成功"; } |
设置自定义Header:
|
1 2 3 4 5 6 |
@RequestMapping("/setHeader") @ResponseBody public String setHeader(HttpServletResponse response) { response.setHeader("MyHeader", "MyHeaderValue"); return "设置Header成功"; } |
|
1 2 3 4 5 6 7 8 9 |
@RestController @RequestMapping("/calc") public class CalcController { @RequestMapping("/sum") public String sum(Integer num1, Integer num2) { Integer sum = num1 + num2; return "<h1>计算机计算结果: " + sum + "</h1>"; } } |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>加法计算器</title> </head> <body> <form action="calc/sum" method="post"> <h1>计算器</h1> 数字1:<input name="num1" type="text"> 数字2:<input name="num2" type="text"> <input type="submit" value="点击相加"> </form> </body> </html> |


|
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 |
@RestController @RequestMapping("/user") public class LoginController { // 登录校验 @RequestMapping("/login") public boolean login(String userName, String password, HttpSession session) { if (!StringUtils.hasLength(userName) || !StringUtils.hasLength(password)) { return false; } // 模拟数据库校验 if ("zhangsan".equals(userName) && "123456".equals(password)) { session.setAttribute("userName", userName); return true; } return false; } // 获取当前登录用户 @RequestMapping("/getLoginUser") public String getLoginUser(HttpSession session) { String userName = (String) session.getAttribute("userName"); if (StringUtils.hasLength(userName)) { return userName; } return ""; } } |
图书列表接口:
|
1 2 3 4 5 6 7 8 9 |
@RestController @RequestMapping("/book") public class BookController { @RequestMapping("/getList") public List<BookInfo> getList() { BookService bookService = new BookService(); return bookService.getBookList(); } } |
前端页面渲染:
|
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 |
function getBookList() { $.ajax({ type: "get", url: "/book/getList", success: function (result) { if (result != null) { var finalHtml = ""; for (var book of result) { finalHtml += '<tr>'; finalHtml += '<td><input type="checkbox" name="selectBook" value="' + book.id + '"></td>'; finalHtml += '<td>' + book.id + '</td>'; finalHtml += '<td>' + book.bookName + '</td>'; finalHtml += '<td>' + book.author + '</td>'; finalHtml += '<td>' + book.count + '</td>'; finalHtml += '<td>' + book.price + '</td>'; finalHtml += '<td>' + book.publish + '</td>'; finalHtml += '<td>' + book.statusCN + '</td>'; finalHtml += '<td><a href="book_update.html?bookId=' + book.id + '" rel="external nofollow" >修改</a> | <a href="javascript:deleteBook(' + book.id + ')" rel="external nofollow" >删除</a></td>'; finalHtml += "</tr>"; } $("tbody").html(finalHtml); } } }); } |
Postman是后端开发常用的接口测试工具,支持多种请求方式、参数类型,无需编写前端代码即可完成接口测试。
核心使用步骤:
工作界面展示

Lombok是Java开发工具库,通过注解自动生成getter/setter、toString等冗余代码,简化开发流程。
|
1 2 3 4 5 |
<dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> |
| 注解 | 作用 |
|---|---|
| @Data | 生成getter/setter、toString、equals等方法 |
| @Getter | 仅生成getter方法 |
| @Setter | 仅生成setter方法 |
| @NoArgsConstructor | 生成无参构造方法 |
| @AllArgsConstructor | 生成全参构造方法 |
使用示例:
|
1 2 3 4 5 6 7 8 |
@Data @NoArgsConstructor @AllArgsConstructor public class Person { private int id; private String name; private String password; } |
遵循以下开发规范可提升代码可读性和维护性,符合企业级开发标准: