Spring Web MVC 从入门到实战
Spring Web MVC 核心概念、MVC 架构及与 Spring Boot 区别。内容包括项目搭建、核心注解应用(如@RequestMapping)、请求参数接收(基础、对象、数组、JSON)、文件上传、Cookie 与 Session 管理、响应处理及静态资源返回。结合加法计算器、用户登录、图书管理等实战案例,演示 Postman 接口测试与 Lombok 简化开发技巧,并提供企业级开发规范建议。

Spring Web MVC 核心概念、MVC 架构及与 Spring Boot 区别。内容包括项目搭建、核心注解应用(如@RequestMapping)、请求参数接收(基础、对象、数组、JSON)、文件上传、Cookie 与 Session 管理、响应处理及静态资源返回。结合加法计算器、用户登录、图书管理等实战案例,演示 Postman 接口测试与 Lombok 简化开发技巧,并提供企业级开发规范建议。

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 依赖自动引入,核心依赖代码如下:
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency>
该依赖包含 Spring MVC 核心组件、Servlet API、Tomcat 嵌入式容器等关键资源,无需额外配置即可实现 Web 功能开发。
@RequestMapping 是 Spring MVC 中最基础的路由映射注解,用于将 URL 请求与 Java 方法关联,支持修饰类和方法。
基础使用示例:
@RestController
@RequestMapping("/user") // 类级路由前缀
public class UserController {
// 方法级路由,完整访问路径:/user/sayHi
@RequestMapping("/sayHi")
public String sayHi() {
return "hello, Spring MVC";
}
}
/say/hi)。/不影响访问,Spring 会自动补全 (路径 + 请求方式是唯一的,可以相同的路径名字但是不同类型的请求)。用于解决前后端参数名称不一致问题,同时可设置参数是否必传:
@RequestMapping("/r6") // 此处@RequestParam("sa") 注解的作用是参数重命名,
public String r6(@RequestParam(value = "sa", required = false) String resouce) {
return "接收到 resouce = " + resouce;
}
用于从 URL 路径中直接提取参数,适用于 RESTful 风格接口:
@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 参数。
@Controller + @ResponseBody,类中所有方法默认返回数据。两者区别示例:
// 返回视图(需配合模板引擎)
@Controller
public class IndexController {
@RequestMapping("/index")
public String index() {
return "/index.html";
}
}
// 返回数据
@RestController
public class DataController {
@RequestMapping("/data")
public String getData() {
return "这是返回的数据内容";
}
}
直接通过方法参数接收前端传递的参数,参数名称需与请求参数一致:
@RequestMapping("/r1")
public String r1(String name) {
return "接收到参数 name = " + name;
}
通过 Postman 创建一个带有 name 参数请求访问示例:
http://127.0.0.1:8080/request/r1?name=zhangsan

支持多个参数同时接收,参数顺序不影响绑定结果(请求的参数名称保持一致即可):
@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 会自动按属性名绑定:
// 实体类
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)
@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

数组参数接收:
@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):
@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. 两种核心结构:
{ "userId": 1001, "userName": "zhangsan", "age": 25, "isStudent": false, "hobbies": ["reading", "coding"] }
[ {"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 核心类)提供的方法完成转换,示例代码如下:
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);
}
}
关键方法说明
List<Person> personList = objectMapper.readValue("[{\"id\":1,\"name\":\"zhangsan\"},{\"id\":2,\"name\":\"lisi\"}]", new TypeReference<List<Person>>() {});
1. 前端请求方式(以 Postman 为例)
Postman 是后端开发常用的接口测试工具,传递 JSON 参数步骤如下:
http://127.0.0.1:8080/request/r9;配置请求头:在 Headers 标签中添加Content-Type:application/json(必须设置,告知后端请求数据格式为 JSON,否则解析失败);

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

使用@RequestPart 注解实现文件上传功能:
@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:
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 成功";
}
}
方式二: 注解的方式
@RequestMapping("/getCookie2") // 要求客户端必须返回含 class 名称的 cookie
public String getCookie2(@CookieValue("class") String className) {
return "获取 Cookie 成功 className:" + className;
}
演示步骤
在 IDE 启动服务器,打开浏览器新标签页,输入http://127.0.0.1:8080/request2/getCookie,显示获取 Cookie 成功

按 F12 打开控制面板,选择 Application,在左侧栏有个 Cookies 选项,点击找到http://127.0.0.1:8080,可以通过手动添加或者修改 Cookie 的值

修改完后通过,进行页面刷新,回到 IDE 观察日志,或者通过 Fiddler 抓包观察


Session 存储与读取:
@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 直接获取 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;
}
@RequestMapping("/getSession3")
public String getSession3(@SessionAttribute("name") String name) {
// 获取用户信息
return "从 session 中获取数据,name:" + name;
}


当我们将 name 改为可自己输入的参数时,然后用设置不同的 cookie 的 name 参数值,那么它们如果交换 sessionid,保存的信息也会交换吗?🤔
使用代码
/**
* 读取 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(EDGE)

创建第一个 Session(Chrome)
http://127.0.0.1:8080/request2/setSession?name=wangwu


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

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

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

@Controller
public class IndexController {
@RequestMapping("/index")
public Object index() {
return "/index.html";
}
}
静态页面需放置在 resources/static 目录下,访问路径为:http://127.0.0.1:8080/index
Spring MVC 可自动将对象转换为 JSON 格式响应: 示例一
@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;
}
响应结果:
{"Java":"Java Value","MySQL":"MySQL Value","Redis":"Redis Value"}
示例二
@ResponseBody
@RequestMapping("/returnJSON")
public Person returnJSON() {
// 实例化数据模型对象
Person person = new Person();
// 设置对象属性
person.setName("zhangsan");
person.setAge(18);
person.setPassword("123456");
// 返回对象,由 @ResponseBody 自动转换为 JSON
return person;
}
响应结果:
{"name":"zhangsan","password":"123456","age":18}
设置响应状态码:
@RequestMapping("/setStatus")
@ResponseBody
public String setStatus(HttpServletResponse response) {
response.setStatus(401); // 未授权状态码
return "设置状态码成功";
}
设置自定义 Header:
@RequestMapping("/setHeader")
@ResponseBody
public String setHeader(HttpServletResponse response) {
response.setHeader("MyHeader", "MyHeaderValue");
return "设置 Header 成功";
}
@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="en">
<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>


@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 "";
}
}
图书列表接口:
@RestController
@RequestMapping("/book")
public class BookController {
@RequestMapping("/getList")
public List<BookInfo> getList() {
BookService bookService = new BookService();
return bookService.getBookList();
}
}
前端页面渲染:
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' + 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 + '">修改</a> | <a href="javascript:deleteBook(' + book.id + ')">删除</a></td>';
finalHtml += "</tr>";
}
$("tbody").html(finalHtml);
}
}
});
}
Postman 是后端开发常用的接口测试工具,支持多种请求方式、参数类型,无需编写前端代码即可完成接口测试。
核心使用步骤:
工作界面展示

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

微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online
JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online
使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online
Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online