一、Spring Web MVC的定义
Spring Web MVC 是 Spring 框架中的一个模块,它基于 MVC(Model-View-Controller)设计模式,用于构建灵活、松耦合的 Web 应用程序。
其核心定义可从以下几个方面理解:
- 是 Spring 框架提供的 Web 层解决方案,专注于处理 HTTP 请求和响应,实现 Web 应用的分层架构
- 遵循 MVC 模式:
- Model(模型):负责承载数据和业务逻辑
- View(视图):负责数据展示,如 JSP、Thymeleaf 等
- Controller(控制器):负责接收请求、协调模型和视图,处理用户交互
- 提供了 DispatcherServlet 作为前端控制器,统一接收所有请求并进行分发,实现了请求处理流程的标准化
- 支持多种视图技术、数据绑定、请求参数处理、国际化等 Web 开发常见需求
- 通过依赖注入等 Spring 核心特性,实现了组件间的低耦合,便于测试和维护
简单来说,Spring Web MVC 为开发者提供了一套完整的 Web 应用开发框架,简化了基于 Java 的 Web 应用开发过程,同时保持了良好的扩展性和灵活性。
二、学习Spring MVC
既然是 Web 框架,那么当用户在浏览器中输入了 url 之后,我们的 Spring MVC 项目就可以感知到用户的请求,并给予响应。
咱们学习 Spring MVC,重点也就是学习如何通过浏览器和用户程序进行交互。
主要分以下三个方面:
- 建立连接:将用户(浏览器)和 Java 程序连接起来,也就是访问一个地址能够调用到我们的 Spring 程序。
- 请求:用户请求的时候会带一些参数,在程序中要想办法获取到参数,所以请求这块主要是获取参数的功能。
- 响应:执行了业务逻辑之后,要把程序执行的结果返回给用户,也就是响应。
(一)创建项目
Spring MVC项目创建和Spring Boot创建项目相同,在创建时选择Spring Web就相当于创建了Spring MVC项目。

(二)建立连接
在main包的java文件夹中和启动类一起创建相关类:

下面是HelloController类中的代码内容:
java
package com.bite.demo;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/hello")
public class HelloController {
@RequestMapping("/m1")
public String m1(){
return "hello m1";
}
}
注解:@RestController
❓在一个项目中会有很多类,每个类中又会有很多方法。当项目执行时,Spring怎么知道要执行哪些类里的方法的呢?
Spring会对所有的类进行扫描,如果类加了注解@RestController,Spring才会去查看这个类里面的方法有没有加@RequestMapping这个注解,如果不加这个注解,就扫描不到了。
注解:@RequestMapping("/*")
是最常被用到的注解,用来注册接口的路由映射的。
路由映射:当用户访问一个URL时,将用户的请求对应到程序中的某个类的某个方法的过程就叫路由映射。
@Request Mapping 既可修饰类,也可以修饰方法,当修饰类和方法时,访问的地址是类路径 + 方法路径。
- @Request Mapping 标识一个类:设置映射请求的请求路径的初始信息
- @Request Mapping 标识一个方法:设置映射请求请求路径的具体信息
java
@RestController
@RequestMapping("/hello")
public class HelloController {
@RequestMapping("/m1")
public String m1(){
return "hello m1";
}
}
访问地址:http://127.0.0.1:8080/hello/m1
使用postman发起请求:

注意:@RequestMapping同时支持get和post请求。
我们也可以用另外的写法,来指定使用get或者post请求:
java
@RequestMapping(value = "/getRequest",method= RequestMethod.GET)
@RequestMapping(value = "/getRequest",method= RequestMethod.POST)
此外还有两种注解能指定get或者post请求:
- 只支持get:
java
@GetMapping("/m3")
public String m3(){
return "m3";
}
- 只支持post:
java
@PostMapping("/m5")
public String m5(){
return "m5";
}
(三)发起请求
访问不同的路径,就是发送不同的请求。在发送请求时,可能会携带一些参数,所以学习Spring的请求,主要是学习如何传递参数到后端以及后端如何接收。
传递单个参数
后端代码示例:
java
@RequestMapping("/request")
@RestController
public class RequestController {
@RequestMapping("/r1")
public String r1(String keyWord){
return "接收参数:"+keyWord;
}
@RequestMapping("/r3")
public String r3(Integer number){//Integer包装类,可以不传值,此时number=null
return "接收参数: "+number;
}
@RequestMapping("/r4")
public String r4(int number){//普通类型必须传值,否则报错
return "接收参数: "+number;
}
}
使用postman发起请求,以及结果:
以r1为例,在url中查询字符串中要传入keyWord这个参数,其参数名必须与后端代码一致,否则报错。

传递多个参数
当我们传递少量参数时,可以用多个形参来传递。如果变量很多,就把它们封装成一个对象来传递。
后端代码示例:
java
@RequestMapping("/request")
@RestController
public class RequestController {
@RequestMapping("/r2")
public String r2(String userName,String passWord){
return "用户名:"+userName+" "+"密码:"+passWord;
}
@RequestMapping("/r5")
public String r5(UserInfo userInfo){
return "接收参数:"+userInfo.toString();
}
}
其中UserInfo类的成员属性有:name、gender、age。
使用postman发起请求,以及结果:
传递对象时,将对象中的各个属性变量传进去即可。
参数重命名 @RequestParam
某些特殊的情况下,前端传递的参数 key 和我们后端接收的 key 可以不一致,比如前端传递了一个 time 给后端,而后端是使用 createtime 字段来接收的,这样就会出现参数接收不到的情况,如果出现这种情况,我们就可以使用 @RequestParam 来重命名前后端的参数值。
后端代码示例:
java
//从前端接收参数q,把q复制给keyword,这种写法默认情况下参数q是必须传入的,可以加false表示不一定
@RequestMapping("/r6")
public String r6(@RequestParam(value = "q",required = false) String keyword){
return "接收参数:"+keyword;
}
使用postman发起请求,以及结果:
这里从前端接收参数q,把q复制给keyword,这种写法默认情况下参数q是必须传入的,可以加false表示不一定。

传递数组
后端代码:
java
@RequestMapping("/r7")
public String r7(String[]arr){
return "接收参数:"+ Arrays.toString(arr);
}
使用postman发起请求,以及结果:
传入数组时,可以传入多个arr变量来表示数组的各个元素:

也可以一次性只传入一个arr变量,变量的值可以填多个,每个用逗号隔开来表示数组的各个元素:

传递集合 @RequestParam
集合参数:和数组类似,同一个请求参数名有为多个,且需要使用 @RequestParam 绑定参数关系。默认情况下,请求中参数名相同的多个值,是封装到数组。
如果要封装到集合,要使用 @RequestParam 绑定参数关系请求方式和数组类似。
后端代码示例:
java
@RequestMapping("/r8")
public String r8(@RequestParam List<Integer> list){
return "接收参数:list= "+list;
}
使用postman发起请求,以及结果:

传递json @RequestBody
json简介:
josn是一种数据格式,有自己的语法格式,说用文本来表示一个对象或者数组的信息,因此json的本质是字符串,主要负责在不同的语言中进行数据传递和交换。
在前端上传数据时,也可以上传json格式的数据来表示对象或者数组。
下面是简单的json数据,都可以表示一个对象:
java
{
"name":"zhangsan",
"gender":1,
"age":18
}
java
{
"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数据时,还在将要接收的对象前面加上**@RequestBody**注解,来表示将要接收json数据。
java
//传递json
@RequestMapping("/r9")
public String r9(@RequestBody UserInfo userInfo){
return userInfo.toString();
}
使用postman发起请求,以及结果:
josn数据在body中上传。

获取URL中参数 @PathVariable
path variable:路径变量
和字面意思一样,这个注解主要作用在请求url路径上的数据绑定,传递参数写在url上,SpringMVC就能获取到。
后端代码示例:
java
@RequestMapping("/r10/{type}/{id}")
public String r10(@PathVariable String type,@PathVariable("id") Integer articleId){//涉及到了重命名(参数绑定)
return "type: "+type+" "+"id: "+articleId;
}

使用postman发起请求,以及结果:

上传文件 @RequestPart
后端代码示例:
java
@RequestMapping("/r11")
public String r11(@RequestPart("file") MultipartFile file) throws IOException {
System.out.println(file.getOriginalFilename());
//文件上传
file.transferTo(new File("/Users/aa/Documents"+file.getOriginalFilename()));
return "文件获取成功";
}
使用postman发起请求,以及结果:

获取Cookie
后端代码示例:
java
//获取Cookie,写法一
@RequestMapping("r12")
public String r12(HttpServletRequest request){//获取到请求
Cookie[] cookies = request.getCookies();
//cookies是由键值对为元素组成的数组
if(cookies!=null){
for(Cookie x:cookies){
System.out.println(x.getName() + " " + x.getValue());
}
}
return "返回Cookie成功。";
}
java
//获取Cookie,写法二
@RequestMapping("r13")
public String r13(@CookieValue("java") String java){
return "获取到的Cookie值:"+java;
}
使用浏览器发起请求,以及结果:


可以看到页面上并没有打印出cookies数组,打开网页的开发者模式,可以查看到当前并未设置cookie。
接下来手动设置后:


设置Session
后端代码示例:
java
//设置Session
@RequestMapping("/setSession")
public String setSession(HttpServletRequest request){
//从cookie中获取sessionId, 根据sessionId 获取Session对象
//如果sessionId不存在, 则创建
HttpSession session = request.getSession();
//默认存储在内存中
//登录的用户名称
session.setAttribute("userName","zhangsan");
session.setAttribute("gender",1);
session.setAttribute("age",17);
return "session设置成功。";
}
获取session:
后端代码示例:
java
//获取session
//通过HttpServletRequest对象来获取
@RequestMapping("/getSession1")
public String getSession1(HttpServletRequest request){
//从cookie中获取sessionId, 根据sessionId 获取Session对象
HttpSession session = request.getSession(false);
//如果用户登录, session 有值, 未登录, session为null
if(session==null){
return "用户未登录";
}
//从session中获取登录用户的信息
String userName = (String)session.getAttribute("userName");
String gender = (String)session.getAttribute("gender");
String age = (String)session.getAttribute("age");
return "登录用户为: " + userName+"性别:"+gender+"年龄:"+age;
}
//获取Session
//通过@SessionAttribute注解来获取
@RequestMapping("/getSession2")
public String getSession2(@SessionAttribute(value = "userName",required = false) String userName){
return "userName:"+userName;
}
//获取Session
//通过Spring MVC中的内置对象HttpSession来获取
@RequestMapping("/getSession3")
public String getSession3(HttpSession httpSession){
String userName = (String)httpSession.getAttribute("userName");
return "userName:"+userName;
}
获取Header
后端代码示例:
java
//获取Header
//使用HttpServletRequest类
@RequestMapping("/getHeader1")
public String getHeader1(HttpServletRequest request){
String userName = request.getHeader("userName");
return "从Header中提取userName:"+userName;
}
//获取Header
//使用注解
@RequestMapping("/getHeader2")
public String getHeader2(@RequestHeader("userName") String userName){
return "从Header中提取userName:"+userName;
}
(四)返回响应
返回静态页面
先创建一个前端页面:
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Index页面</title>
</head>
<body>
Hello,Spring MVC,我是Index页面.
</body>
</html>
后端代码:
java
@RequestMapping("/resp")
@RestController
public class RespController {
@RequestMapping("/r1")
public String returnPage(){
return "/aa/index.html";
}
}
打开postman发起请求:

❓可以发现:为什么这里响应的仅仅是字符串,并没有返回页面呢。
⭐️原因是:
这段代码使用了@RestController注解,返回的就不是页面,而是数据。
@RestController = @Controller + @ResponseBody,当我们把@RestController改成了@Controller,就能正确的显示出页面。
- @Controller的作用相当于是让Spring MVC扫描该路径。
- @ResponseBody的作用相当于是将显示页面,改为显示数据,如果没有该注解,显示的就是页面了。
返回数据@ResponseBody
前面说到,加了@ResponseBody的方法相当于只显示数据。
@ResponseBody既是类注解,又是方法注解。
如果作用在类上,表示该类的所有方法,返回的都是数据;
如果作用在方法上,表示还方法返回的是数据。
返回HTML代码片段
后端代码示例:
java
//返回HTML
@ResponseBody
@RequestMapping("/r2")
public String returnHTML(){
return "<h1>这是一个HTML标题</h1>";
}
浏览器发起请求:
此时浏览器会自动将字符串中标签转化成HTMl格式。

返回text
后端代码示例:
java
//返回Text
@ResponseBody
@RequestMapping(value = "/r3",produces = "text/plain")
public String returnText(){
return "<h1>这是一个HTML标题</h1>";
}
浏览器发起请求:

java
@RequestMapping(value = "/r3",produces = "text/plain")
这个操作就相当于将Header中Content---Type的值改为text/html。
返回Json
后端代码示例:
java
//返回json
//json在前端中表示的就是对象,直接返回对象即可
@ResponseBody
@RequestMapping("/r4")
public UserInfo returnJson(){
UserInfo userInfo=new UserInfo("zhangsan",1,18);
return userInfo;
}
浏览器发起请求:

设置状态码
后端代码示例:
java
//设置状态码
@ResponseBody
@RequestMapping("/r5")
public UserInfo r5(HttpServletResponse response){
response.setStatus(404);
UserInfo userInfo=new UserInfo("张三",1,18);
return userInfo;
}
使用postman发起请求以及结果:
虽然响应的状态码显示为404,但是依然是能正常响应的。可以知道,响应码与真实运行情况不一定相符合,是可以修改的。

设置Header
后端代码示例:
java
//设置Header
@ResponseBody
@RequestMapping("/r6")
public String r6(HttpServletResponse response){
response.setHeader("java","java");
return "Header设置成功";
}
使用postman发起请求以及结果:

三、综合性练习
了解接口文档
在练习前端与后端交互时,先了解接口是什么。
不同于java基础所学的接口Interface,这里的接口是前端与后端交互的"说明书"。
现在 "前后端分离" 模式开发,前端和后端代码通常由不同的团队负责开发。双方团队在开发之前,会提前约定好交互的方式。客户端发起请求,服务器提供对应的服务。服务器提供的服务种类有很多,客户端按照双方约定指定选择哪一个服务。
接口,其实也就是我们前面网络模块讲的的 "应用层协议",把约定的内容写在文档上,就是 "接口文档",接口文档也可以理解为是应用程序的 "操作说明书"。
练习一:加法计算器
需求:输入两个数,计算出相加的结果。

接口定义:
请求路径:calc.html
请求方式:GET/POST
请求参数:
|------|---------|------|-----------|
| 参数名 | 类型 | 是否必须 | 备注 |
| num1 | Integer | 是 | 参与计算的第一个数 |
| num2 | Integer | 是 | 参与计算的第二个数 |
响应数据:
Content-Type:text/html
前端代码:
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</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>
后端代码:
java
package com.bite.demo;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RequestMapping("/calc")
@RestController
public class CalcController {
@RequestMapping("/sum")
public String sum(Integer num1,Integer num2){
if(num1==null||num2==null){
return "输入数字不合法";
}
Integer sum=num1+num2;
return "计算结果:"+sum;
}
}
练习二:用户登录
需求:用户输入账号和密码,后端进行校验密码是否正确。
- 如果不正确,前端进行用户告知。
- 如果正确,跳转到首页,首页显示当前登录用户。
- 后续再访问首页,可以获取到用户登录信息。

校验接口
请求路径:/user/login
请求方式:POST
接口描述:校验账号密码是否正确
请求参数
|----------|--------|------|-------|
| 参数名 | 类型 | 是否必须 | 备注 |
| userName | String | 是 | 校验的账号 |
| passWord | String | 是 | 校验的密码 |
响应数据
Content-Type:text/html
响应内容:
true:账号密码校验成功
false:账号密码校验失败
查询登录用户接口
请求路径:/user/getLoginUser
请求方式:GET
接口描述:查询当前登录的用户
请求参数
无
响应数据
Content-Type:text/html
响应内容:
zhangsan
前端代码:
⭐️本次前端代码没有使用from标签提交,而是使用了ajax:
登录界面:
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>登录页面</title>
</head>
<body>
<h1>用户登录</h1>
用户名:<input name="userName" type="text" id="userName"><br>
密码:<input name="password" type="password" id="password"><br>
<input type="button" value="登录" onclick="login()">
<script src="/jquery-3.7.1.min"></script>
<script>
function login() {
$.ajax({
type:"post",
url:"/user/login",
data:{
userName:$("#userName").val(),
passWord:$("#password").val()
},
//http请求成功
success:function(result){
//登录成功
if(result){
//跳转
location.href="index.html";
}else{
alert("密码错误,请确认");
}
}
});
}
</script>
</body>
</html>
显示登录人:
html
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>用户登录首页</title>
</head>
<body>
登录人: <span id="loginUser"></span>
<script src="/jquery-3.7.1.min"></script>
<script>
$.ajax({
type:"get",
url:"/user/getLoginUser",
success:function(userName){
$("#loginUser").text(userName);
}
});
</script>
</body>
</html>
后端代码:
java
package com.bite.demo;
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
@RequestMapping("/user")
public class UserController {
@RequestMapping("/login")
public boolean login(String userName, String passWord, HttpSession session){
//密码输入时错误
if(userName==null||userName==""||passWord==null||passWord==""){
return false;
}
//密码正确时
if("admin".equals(userName)&&"admin".equals(passWord)){
//将用户名存入session
session.setAttribute("userName",userName);
return true;
}
return false;
}
@RequestMapping("/getLoginUser")
public String getLoginUser(HttpSession session){
String userName=(String)session.getAttribute("userName");
return userName;
}
}
练习三:留言板
需求:
- 输入留言信息,点击提交,后端将数据保存起来。
- 页面展示留言的信息。

发表新留言接口
请求:
请求方式:POST
接口路径:/message/publish
请求正文body为json格式:
{
"from":"黑猫",
"to":"白猫",
"message":"喵"
}
响应:
响应正文为josn格式:
{
ok:1
}
获取全部留言接口
请求:
请求方式:GET
接口路径:/message/getList
响应:
响应正文为json格式:
java[ { "from": "黑猫", "to": "白猫", "message": "喵" }, { "from": "黑狗", "to": "白狗", "message": "汪" } //... ]
后端代码:
java
package com.bite.demo.controller;
import ch.qos.logback.core.util.StringUtil;
import com.bite.demo.model.MessageInfo;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.ArrayList;
import java.util.List;
@RequestMapping("/message")
@RestController
public class MessageController {
//发表新留言
//第一步:建立一个list存储留言信息
List<MessageInfo> messageInfoList=new ArrayList<>();
@PostMapping("/publish")
//第二步:拿到请求正文中的MessageInfo对象
public String publish(@RequestBody MessageInfo messageInfo){
//判断前端上传的数据是否合法
if(!StringUtils.hasLength(messageInfo.getFrom())
||!StringUtils.hasLength(messageInfo.getMessage())
||!StringUtils.hasLength(messageInfo.getTo())){
//返回json格式
return "{\"ok\": 0}";
}
//第三步:将MessageInfo对象存储到list中
messageInfoList.add(messageInfo);
return "{\"ok\": 1}";
}
//获取所有留言
@RequestMapping("getList")
public List<MessageInfo> getList(){
return messageInfoList;
}
}
前端代码:
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>留言板</title>
<style>
.container {
width: 350px;
height: 300px;
margin: 0 auto;
/* border: 1px black solid; */
text-align: center;
}
.grey {
color: grey;
}
.container .row {
width: 350px;
height: 40px;
display: flex;
justify-content: space-between;
align-items: center;
}
.container .row input {
width: 260px;
height: 30px;
}
#submit {
width: 350px;
height: 40px;
background-color: orange;
color: white;
border: none;
margin: 10px;
border-radius: 5px;
font-size: 20px;
}
</style>
</head>
<body>
<div class="container">
<h1>留言板</h1>
<p class="grey">输入后点击提交, 会将信息显示下方空白处</p>
<div class="row">
<span>谁:</span> <input type="text" name="" id="from">
</div>
<div class="row">
<span>对谁:</span> <input type="text" name="" id="to">
</div>
<div class="row">
<span>说什么:</span> <input type="text" name="" id="say">
</div>
<input type="button" value="提交" id="submit" onclick="submit()">
<!-- <div>A 对 B 说: hello</div> -->
</div>
<script src="/jquery-3.7.1.min"></script>
<script>
//显示所有留言
$.ajax({
type:"get",
url:"/message/getList",
success:function(messages){//这里接收到的值是存储message的列表
if(messages!=null&&messages.length>0){
var finalHtml = "";
for(var m of messages){
finalHtml += "<div>" + m.from + "对" + m.to + "说:" + m.message + "</div>";
}
$(".container").append(finalHtml);
}
}
});
//提交留言
function submit(){
//1. 获取留言的内容
var from = $('#from').val();
var to = $('#to').val();
var say = $('#say').val();
if (from== '' || to == '' || say == '') {
return;
}
var data={
"from":from,
"to":to,
"message":say
}
//传输到后端
$.ajax({
type:"post",
url:"/message/publish",
//传输的格式为json
contentType:"application/json",
data:JSON.stringify(data),
success:function(result){
//result是字符串,将result改为json
var jsonObj=JSON.parse(result);
//留言成功
if(jsonObj.ok==1){
//构造节点
var divE = "<div>"+from +"对" + to + "说:" + say+"</div>";
//将节点添加到页面上
$(".container").append(divE);
$('#from').val("");
$('#to').val("");
$('#say').val("");
}else{
alert("发送留言失败");
}
}
});
}
</script>
</body>
</html>