目录
[1. 什么是Spring Web MVC](#1. 什么是Spring Web MVC)
[2. 学习Spring MVC](#2. 学习Spring MVC)
[2.1 项目准备](#2.1 项目准备)
[2.2 建立连接](#2.2 建立连接)
[2.2.1 @RequestMapping注解介绍](#2.2.1 @RequestMapping注解介绍)
[2.3 Postman介绍](#2.3 Postman介绍)
[2.4 请求](#2.4 请求)
[2.4.1 @RequestMapping注解接收特定方法的请求](#2.4.1 @RequestMapping注解接收特定方法的请求)
[2.4.2 请求传递参数](#2.4.2 请求传递参数)
[2.4.3 请求参数传递对象](#2.4.3 请求参数传递对象)
[2.4.4 后端参数的重命名](#2.4.4 后端参数的重命名)
[2.4.5 传递数组](#2.4.5 传递数组)
[2.4.6 传递集合](#2.4.6 传递集合)
[2.4.7 传递JSON数据](#2.4.7 传递JSON数据)
[2.4.8 获取URL中参数@PathVariable](#2.4.8 获取URL中参数@PathVariable)
[2.4.9 上传文件@RequestPart](#2.4.9 上传文件@RequestPart)
[2.4.10 获取Cookie/Session](#2.4.10 获取Cookie/Session)
[2.4.11 获取Header](#2.4.11 获取Header)
[2.5 响应](#2.5 响应)
[2.5.1 返回静态页面 和 返回数据@ResponseBody](#2.5.1 返回静态页面 和 返回数据@ResponseBody)
[2.5.2 返回HTML代码片段](#2.5.2 返回HTML代码片段)
[2.5.3 返回Json](#2.5.3 返回Json)
[2.5.4 设置状态码](#2.5.4 设置状态码)
[2.5.5 设置Header](#2.5.5 设置Header)
[2.5.6 设置Content-Type](#2.5.6 设置Content-Type)
[3. 综合性练习](#3. 综合性练习)
[3.1 加法计算器](#3.1 加法计算器)
[3.1.1 约定前后端交互接口](#3.1.1 约定前后端交互接口)
[3.1.2 服务器代码](#3.1.2 服务器代码)
[3.1.3 前端代码](#3.1.3 前端代码)
[3.1.4 运行测试](#3.1.4 运行测试)
[3.2 用户登录](#3.2 用户登录)
[3.2.1 约定前后端交互接口](#3.2.1 约定前后端交互接口)
[3.2.2 服务器代码](#3.2.2 服务器代码)
[3.2.3 前端代码](#3.2.3 前端代码)
[3.2.4 运行测试](#3.2.4 运行测试)
[3.3 留言板](#3.3 留言板)
[3.3.1 约定前后端交互接口](#3.3.1 约定前后端交互接口)
[3.3.2 服务器代码](#3.3.2 服务器代码)
[3.3.3 前端代码](#3.3.3 前端代码)
[3.3.4 运行测试](#3.3.4 运行测试)
[3.4 图书管理系统](#3.4 图书管理系统)
[3.4.1 生成前端页面的工具](#3.4.1 生成前端页面的工具)
[3.4.2 约定前后端交互接口](#3.4.2 约定前后端交互接口)
[3.4.3 服务器代码](#3.4.3 服务器代码)
[3.4.4 前端代码](#3.4.4 前端代码)
[3.4.5 运行测试](#3.4.5 运行测试)
[4. 应用分层](#4. 应用分层)
[5. 总结](#5. 总结)
1. 什么是Spring Web MVC
Spring Web MVC 是基于 servlet API 构建的原始Web框架,从一开始就包含在spring框架里面,但通常称为:Spring MVC,也就是说 Spring Web MVC 最开始就包含在Spring FrameWork框架里面。
servlet:就是用来处理客户端请求,处理业务逻辑,生成响应内容的。
而Tomcat这样的servlet容器是用来接收客户端请求,管理servlet的执行,并将响应返回给客户端。
Spring Web MVC则是一个Web框架,它是基于servlet实现的一个框架,可以更加高效的处理请求。
MVC:
MVC是一种软件设计的架构模式,把软件系统分为模型,视图,控制器三层:

View(视图)指在应用程序中专门用来与浏览器进行交互,展示数据的资源.。
Model(模型)是应用程序的主体部分,用来处理程序中数据逻辑的部分。
Controller(控制器)可以理解为一个分发器,用来决定对于视图发来的请求,需要用哪一个模型 来处理,以及处理完后需要跳回到哪一个视图。即用来连接视图和模型。
Spring MVC就是一个采用MVC架构的Web框架。
Spring Boot 项目里面包含了Spring MVC框架,使用这个框架来实现Web功能,对Spring MVC框架做出了一些修改,如图所示:

Spring Boot框架相当于集成了很多功能,类似于12306,Spring Framework相当于火车,而Spring MVC相当于售票站。
2. 学习Spring MVC
2.1 项目准备
我们学习Spring MVC 框架主要学习三个方面:
建立连接:将浏览器和服务器建立连接,使得用户能通过浏览器访问服务器的资源。
请求:用户请求时候会有一些参数,服务器能够获取到这些参数。
响应:服务器处理好请求后得到响应,然后将响应发送给浏览器。
我们创建Spring Boot项目时候,框选了Spring Web就相当于创建了Spring MVC项目:

我们需要自己在idea创建一个Spring Boot项目。
2.2 建立连接
先创建一个UserController类,来实现浏览器和服务器的连接,代码如下:
java
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RequestMapping("/user")
@RestController
public class UserController {
@RequestMapping("/m1")
public String m1() {
return "m1";
}
}
然后启动程序,访问http://127.0.0.1:8080/user/m1这个网址,就可以在网页上看到返回的数据了。

2.2.1 @RequestMapping注解介绍
这个注解相当于路径映射,可以修饰类,也可以修饰方法,URL中有资源路径,这个资源路径就是类路径+方法路径。
使用注意:
- 我们在使用这个注解时候,路径里面的/可以省略,Spring会帮我们检查。
- 注解里面可以是多层路径,第一个路径可以省略/,后面的不能省略。
- 类路径和方法路径可以重复,需要保证资源路径是唯一的,但是平常使用都不会重名。
这时候用户输入一个URL后,服务器能通过这个URL访问到具体的某个类某个方法,但是Spring是如果找到这个具体的类或者方法呢?
Spring通过扫描所有加了@RestController注解的类,查看里面有没有@RequestMapping注解,从而找到对应的方法,没有加@RestController注解的类不会扫描。
@RequestMapping注解既可以接收get请求又可以接收post请求。
浏览器一般发送的都是get请求,我们如何去构造一个post请求呢?
2.3 Postman介绍
我们可以使用Postman这个软件来构造不同方法的请求,测试客户端和服务器的连接,我们使用postman发送的请求给客户端 和 浏览器给客户端发送的请求是一样的效果的。
下载地址:https://www.postman.com/downloads/,可能需要注册一个账号,才能使用。
创建请求:


我们就可以使用Postman这个软件来构造请求,发送给服务器,比如我们构造一个post请求,使用fiddler抓包看是不是一个post请求:
我们使用postman构造一个post请求,发送给服务器,返回了m1。

使用fiddler抓包到请求的信息:
POST http://127.0.0.1:8080/user/m1 HTTP/1.1
User-Agent: PostmanRuntime/7.49.1
Accept: */*
Postman-Token: 47008be0-e53d-4db9-8fe4-10c06dfff6fa
Host: 127.0.0.1:8080
Accept-Encoding: gzip, deflate, br
Connection: keep-alive
Content-Length: 0
显示是一个post请求。
2.4 请求
2.4.1 @RequestMapping注解接收特定方法的请求
此时的@RuquestMapping注解既支持get请求,也支持post请求,其它请求也支持。
如果想让@RequestMapping注解只支持某一种请求,该如何去编写代码:
java
@RequestMapping("/user")
@RestController
public class UserController {
//get和post请求都支持
@RequestMapping("/m1")
public String m1() {
return "m1";
}
//只支持get请请求
@RequestMapping(value = "/m2",method = RequestMethod.GET)
public String m2() {
return "m2";
}
//只支持post请求
@RequestMapping(value = "/m3",method = RequestMethod.POST)
public String m3() {
return "m3";
}
}
这里访问http://127.0.0.1:8080/user/m2,这个路径只支持get请求,其他请求会报405错误。
访问http://127.0.0.1:8080/user/m3,这个路径只支持post请求。
上面的代码还有另外一种写法:
java
//只支持get请求
@GetMapping("/m4")
public String m4() {
return "m4";
}
//只支持post请求
@PostMapping("/m5")
public String m5() {
return "m5";
}
这里的m4只支持get请求,m5只支持post请求,使用这种写法的话注解里面的字符串可以相同,这里就会根据资源路径+请求的方法来区分那个方法。
这里的method也可以支持多个请求:
java
//支持get和post请求
@RequestMapping(value = "/m6",method = {RequestMethod.POST, RequestMethod.GET})
public String m6() {
return "m6";
}
get和post请求都支持。
2.4.2 请求传递参数
我们使用postman构造请求时候,是可以进行传参的。
参数介绍:

Params是传普通参数,通过查询字符串的键值对的形式传递。
Headers:请求头,键值对的形式可以自己设置。
Body:正文,可以自己添加内容。

from-data是通常用于提交图片或者文件。
x-www-form-urlencoded是对应的form表单。
raw可以上传text,json,html,xml等。
我们创建一个新的类来演示RequestController类:
java
@RequestMapping("/request")
@RestController
public class RequestController {
//传递一个参数
@RequestMapping("/r1")
public String r1(String keyword) {
return "接受的参数:" + keyword;
}
//传递多个参数
@RequestMapping("r2")
public String r2(String userName, String password) {
return "账号:" + userName + " " + "密码:" + password;
}
}
传递一个参数的请求:

传递多个参数的请求,通过查询字符串传递数据:

传递多个请求,通过正文来传递数据:

fiddler抓包结果:
POST http://127.0.0.1:8080/request/r2 HTTP/1.1
User-Agent: PostmanRuntime/7.49.1
Accept: */*
Postman-Token: 6eabf928-ee24-493a-a2c6-bc60c40e135b
Host: 127.0.0.1:8080
Accept-Encoding: gzip, deflate, br
Connection: keep-alive
Content-Type: application/x-www-form-urlencoded
Content-Length: 30
userName=admin&password=123456
这里面的用户名和密码数据就通过正文传递了。
传递参数是整型和基本类型呢?
java
//传递包装类型整型
@RequestMapping("/r3")
public String r3(Integer number) {
return "接收参数:" + number;
}
//传递基本类型整型
@RequestMapping("/r4")
public String r4(int number) {
return "接收参数:" + number;
}
传递包装类型和基本类型都能传递成功,显示200。

传递包装类型和基本类型,传递字母,会显示400,客户端错误:

如果包装类型不传参数,此时就是null,但是基本类型不传参数,就会报错,500,服务器错误,基本类型不能为null。

2.4.3 请求参数传递对象
如果传的参数过多的情况,我们接收时候可以使用一个对象来接收:
定义一个UserInfo类:
java
package com.sias.demo.controller;
public class UserInfo {
private String name;
private int gender;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getGender() {
return gender;
}
public void setGender(int gender) {
this.gender = gender;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "UserInfo{" +
"name='" + name + '\'' +
", gender=" + gender +
", age=" + age +
'}';
}
}
利用对象来接收请求的参数:
java
//使用对象来接收参数
@RequestMapping("r5")
public String r5(UserInfo userInfo) {
return "接收参数userInfo = " + userInfo.toString();
}

2.4.4 后端参数的重命名
required属性是设置该参数是否必须传参。
java
//接收到参数a,把a的值给keyword,使用这个注解的参数,默认是必须传参,否则报错
@RequestMapping("r6")
public String r6(@RequestParam("a") String keyword) {
return "接收参数:" + keyword;
}
//设置非必须传参
@RequestMapping("r7")
public String r7(@RequestParam(value = "a", required = false) String keyword) {
return "接收参数:" + keyword;
}
通过postman发送请求:
强制传参:

非强制传参:

2.4.5 传递数组
java
//传递数组
@RequestMapping("r8")
public String r8(String[] arr) {
return "接收参数:" + Arrays.toString(arr);
}
利用postman构造请求:
方法一:

方法二:

2.4.6 传递集合
默认情况下,传递同一个参数的多个值,是会放在数组里面的,如果要存到集合里面,需要使用@RequestParam注解来绑定参数关系。
错误代码:
java
//传递集合
@RequestMapping("r9")
public String r9(List<Integer> list) {
return "接收参数:" + list;
}
利用上面代码接受请求,结果会报错,500:

利用@RequestParam注解绑定关系后:
java
//传递集合
@RequestMapping("r9")
public String r9(@RequestParam List<Integer> list) {
return "接收参数:" + list;
}
这里跟数组一样,两种传参方式都可以:

2.4.7 传递JSON数据
JSON概念:
JSON就是一种数据格式,有着自己的规则来组织数据,本质是一个字符串,是客户端和服务端进行交互的一种数据格式。
JSON语法:
- 数据在键值对中
- 数据之间用 , 分隔
- 对象用 { } 表示
- 数组用 [ ] 表示
- 值可以为对象,也可以为数组,数组中可以包含多个对象。
示例代码:
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"]
}]
}
压缩版本:
{ "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字符串和Java对象转换:
常用的转换工具:gson,fastjson,jackson。
spring boot项目自带的是jackson,我们可以直接去使用。
我们在test目录下,创建一个JsonTest类,来演示Jackson的使用:
java
package com.sias.demo;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.sias.demo.controller.UserInfo;
import org.junit.jupiter.api.Test;
public class JsonTest {
//对象转Json
@Test
void testObjectToJson() throws JsonProcessingException {
ObjectMapper objectMapper = new ObjectMapper();
//构造一个对象
UserInfo userInfo = new UserInfo();
userInfo.setName("张三");
userInfo.setGender(1);
userInfo.setAge(18);
//对象转JSON
String s = objectMapper.writeValueAsString(userInfo);
System.out.println(s);
}
//Json转对象
@Test
void testJsonToObject() throws JsonProcessingException {
ObjectMapper objectMapper = new ObjectMapper();
//构造一个Json
String s = "{\"name\":\"张三\",\"gender\":1,\"age\":18}";
//Json转对象
UserInfo userInfo = objectMapper.readValue(s, UserInfo.class);
System.out.println(userInfo.toString());
}
}
我们使用了Jackson工具里面的ObjectMapper类的writeValueAsString()方法和readValue()方法来进行转换的。
这里我使用了一个@Test注解,这个注解类似于main方法,可以运行测试方法,这个注解在一个类里面可以有多个,一个类里面只能有一个main方法。
我们使用writeValueAsString()方法将对象转换成JSON,利用readValue()方法将JSON转换为对象,该方法参数里面要标明转成那个对象。
JSON的优点:
- 语法简单,便于阅读理解,能够快速的进行数据转换。
- JSON可以被多种语言解析,跨平台性好。
- JSON的数据格式消耗的带宽小,可以提高传输效率。
- JSON数据格式是纯文本格式,不是包含什么可执行代码,比较安全。
- JSON数据格式容易拓展,嵌套数组和对象等复杂类型。
传递JSON数据:
这里使用了@RequestBody注解,作用是将请求中的JSON数据转换成参数里面的对象类型,代码如下:
java
//传递JSON数据
@RequestMapping("r10")
public String r10(@RequestBody UserInfo userInfo) {
return "接收的参数:" + userInfo.toString();
}
利用postman构造请求,包含JSON的数据:

注意事项:
我们在test里面的代码,将JSON对象转为对象使用的是readValue()方法,这个代码工作原理是:利用对象对应的类里面的空的构造方法,先new一个对象,然后使用这个对象赋值类里面的属性,如果我们在类里面重写了带有参数的构造方法,没有空的构造方法,此时下面代码执行就会报错,不能实现JSON转对象的过程。
java
//Json转对象
@Test
void testJsonToObject() throws JsonProcessingException {
ObjectMapper objectMapper = new ObjectMapper();
//构造一个Json
String s = "{\"name\":\"张三\",\"gender\":1,\"age\":18}";
//Json转对象
UserInfo userInfo = objectMapper.readValue(s, UserInfo.class);
System.out.println(userInfo.toString());
}
2.4.8 获取URL中参数@PathVariable
我们打开一个今日头条网站,然后随便找两篇文章,我们可以看到这两篇文章的URL如下:
https://www.toutiao.com/trending/7576914740409139263/?rank=1\&log_from=b9be4e9e1f37d_1764233947866
https://www.toutiao.com/trending/7576991131788247049/?rank=2\&log_from=d5f8b1470f48c8_1764233959884
我们可以看到这两篇文章的资源路径中存在两个不同的数字,我们可以猜测后端接收到请求,是根据这两个数组来查询是哪一篇文章,把内容返回给浏览器。
那后端如何获取到URL里面的参数呢?
这里要用到@PathVariable注解来获取,这里URL的名字要和获取的参数名字相同,参数名字也可以修改成其他名字,这里的获取的参数要使用{ }来包含。
java
//获取URL的参数
@RequestMapping("/article/{articleId}")
public String r11(@PathVariable Integer articleId) {
return "获取到的参数:" + articleId;
}
//获取多个参数
@RequestMapping("/article/{type}/{articleId}")
public String r12(@PathVariable Integer articleId, @PathVariable("type") String articleType) {
return "获取到的参数:" + articleId + " " + articleType;
}
使用postman构造请求:


2.4.9 上传文件@RequestPart
这里使用MultipartFile类型来接收文件,使用transferTo将获取到的文件保存在本地路径下。
@RequestPart注解还可以用来对参数重命名的。
java
//获取文件并存储本地
@RequestMapping("/r13")
public String r13(@RequestPart("file11") MultipartFile file) throws IOException {
System.out.println(file.getOriginalFilename());
//将文件保存到本地
file.transferTo(new File("D:\\" + file.getOriginalFilename()));
return "文件获取成功,并上传到本地";
}
2.4.10 获取Cookie/Session
什么是Cookie:
cookie是客户端浏览器端存储的小型文本数据(键值对的形式),负责被动接收服务器发来的数据,然后存储到本地,等到发出请求时,自动将cookie中的数据存储在请求中。
http协议是一种无状态协议,就是说客户端和服务器的这次会话和下一次会话之间没有关系。
但是在实际开发中,会存在一些需要关联起来不同的会话。比如我们在网页登录自己的账号时候,下次进入网页就是直接登录好的页面。
**会话:**就是客户端和服务器建立的一次连接,就是一个会话,相当于打电话也是一次会话。
当一个客户端访问服务器时候,而这时候服务器就会生成一个身份令牌sessionId,将这个sessionId存储在响应里面的set-cookie字段中传给客户端,此时客户端的cookie就保存sessionId这个属性,下次请求时候将sessionId存储在请求的cookie字段中,发送给服务器,服务器就可以通过sessionId来确定是哪个客户端访问的服务器。

什么是Session:
在实际开发中,一个服务器需要处理很多个客户端的请求,也就会产生很多会话,那么服务器是如何区分那个请求是那个客户端发送来的呢?
这里服务器就会对每个客户端创建一个session对象,里面包括sessionId,以及客户端的信息等内容,session本质是一个哈希表,sessionId是key值,value是客户端的信息。
当一个客户端首次访问服务器,此时服务器就会创建一个session对象,生成sessionId,然后将这个sessionId放在响应的set-cookie字段中,返回给客户端,客户端的cookie中就会存储sessionId这个数据,等到下一次客户端发送请求,就会将这个sessionId数据放在请求的cookie字段发送给服务器,服务器就可以根据sessionId找到对应的客户端数据。
图示:

Cookie和Session的区别:
Cookie是客户端保存用户信息的一种机制,Session是服务器保护用户信息的一种机制。
Cookie和Session之间通过SessionId关联起来的。
Cookie和Session经常一起配合使用,但是也可以不一起使用,Cookie也可以保存一些其他数据在客户端,Session中的SessionId不一定非要Cookie/set-Cookie字段来传递,URL也可以。
获取Cookie
传统获取方法:
java
//传统获取Cookie的方法
@RequestMapping("r14")
public String r14(HttpServletRequest request, HttpServletResponse response) {
Cookie[] cookies = request.getCookies();
if(cookies != null) {
for(Cookie cookie : cookies) {
System.out.println(cookie.getName() + ":" + cookie.getValue());
}
}
return "获取cookie成功";
}
HttpServletRequest类是http请求的所有信息,HttpServletResponse类包含http响应的所有内容。
我们通过postman来设置cookie的值,然后发送请求,通过上面代码获取到cookie的键值对,然后返回字符串获取cookie成功。
简单获取cookie方法:
java
//通过注解拿到cookie的值
@RequestMapping("/r15")
public String r15(@CookieValue("name") String name) {
return "从cookie中获取的值" + name;
}
这里使用了@CookieValue注解来获取cookie的值。
获取Session
存储session:
java
//存储session
@RequestMapping("/setSession")
public String setSession(HttpServletRequest request) {
//先从request里面获取sessionId,然后根据sessionId找到session对象。
//这里默认为true,表示没有session就创建个session。
//参数为false,表示没有session就返回null。
HttpSession session = request.getSession(true);
//设置用户名和年龄
session.setAttribute("name", "lisi");
session.setAttribute("age", 20);
return "存储session成功";
}
这里使用HttpServletRequest来获取到session,使用setAttribute方法来设置session的值。
获取session:
java
//获取session
@RequestMapping("/getSession1")
public String getSession1(HttpServletRequest request) {
HttpSession session = request.getSession(false);
if(session == null) {
return "没有session对象";
}else {
String userName = (String)session.getAttribute("name");
return "用户名是:" + userName;
}
}
//获取session
@RequestMapping("/getSession2")
public String getSession2(HttpSession session) {
String userName = (String)session.getAttribute("name");
return "用户名是:" + userName;
}
//获取session
@RequestMapping("/getSession3")
public String getSession3(@SessionAttribute("name") String userName) {
return "用户名是:" + userName;
}
有三种方法获取session,第三种使用了@SessionAttribute注解来获取session的值。
2.4.11 获取Header
java
//获取Header
@RequestMapping("/getHeader1")
public String getHeader1(HttpServletRequest request) {
String userAgent = request.getHeader("User-Agent");
return userAgent;
}
//获取Header
@RequestMapping("/getHeader2")
public String getHeader2(@RequestHeader("User-Agent") String userAgent) {
return userAgent;
}
这里获取Header中的键值对的属性值,存在两种方法,第二种使用了@RequestHeader注解来获取。
2.5 响应
2.5.1 返回静态页面 和 返回数据@ResponseBody
java
@RequestMapping("/resp")
@Controller
public class RespController {
//返回静态页面
@RequestMapping("/r1")
public String returnPage() {
return "/index.html";
}
//返回数据
@RequestMapping("/r2")
@ResponseBody
public String returnData() {
return "返回的数据";
}
}


这里我们使用了@Controller注解。
@Controller注解 和 @RestController注解的区别:
@RestController = @Controller + @ResponseBody,被@Controller注解修饰的类,会被扫描的。程序运行时候默认扫描的是启动类所在的目录下的类。
@Conteoller注解是该类返回的都是页面时候使用的。
@RestController注解是该类返回的都是数据的时候使用的。
如果一个类既有页面又有数据,我们可以使用@Controller注解,然后返回数据的方法上使用@ResponseBody注解,该注解既是类注解,也是方法注解。
2.5.2 返回HTML代码片段
java
//返回HTML代码片段
@RequestMapping("r3")
@ResponseBody
public String returnHtml() {
return "<h1>一级标题<h1>";
}
//返回HTML代码
@RequestMapping(value = "r4", produces = "text/plain")
@ResponseBody
public String returnText() {
return "<h1>一级标题<h1>";
}


我们如果返回的是HTML的代码,浏览器会自动转换为HTML格式,我们可以通过设置Content-Type键值对的值来改变在浏览器显示的类型,使用produces参数来修改。

2.5.3 返回Json
java
//返回Json
@RequestMapping("/r5")
@ResponseBody
public UserInfo returnJson() {
UserInfo userInfo = new UserInfo("zhangsan",1,2);
return userInfo;
}

这里的返回结果为对象。
2.5.4 设置状态码
java
//设置响应的状态码
@RequestMapping("/r6")
@ResponseBody
public UserInfo setStatus(HttpServletResponse response) {
response.setStatus(404);
UserInfo userInfo = new UserInfo("zhangsan",1,2);
return userInfo;
}

此时看到状态码被设置成404。
2.5.5 设置Header
java
//设置Header
@RequestMapping("/r7")
@ResponseBody
public String setHeader(HttpServletResponse response) {
response.setHeader("one","one");
return "设置header成功";
}

此时看到请求头多了一个设置的键值对。
2.5.6 设置Content-Type
java
//返回HTML代码
@RequestMapping(value = "/r4", produces = "text/plain")
@ResponseBody
public String returnText() {
return "<h1>一级标题<h1>";
}
使用produces属性来设置。
3. 综合性练习
3.1 加法计算器

3.1.1 约定前后端交互接口
接口定义:

请求参数:

响应数据:

3.1.2 服务器代码
后端接收到两个参数后,返回数据显示在新页面里。
java
@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 "<h1>计算机计算结果:" + sum + "</h1>";
}
}
3.1.3 前端代码
这里使用的from标签来发送请求,action是请求的URL,method是请求的方式,input标签的name属性是key的值,输入的是value的值,type="submit"是提交按钮,点击后将from标签域内的数据提交给服务器。
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>
<!-- 使用from标签来向服务器发送请求 -->
<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>
3.1.4 运行测试


3.2 用户登录


3.2.1 约定前后端交互接口
用户登录接口:

请求参数:

响应数据:
Content-Type : text/html
响应内容:
true //账号密码验证成功
false //账号密码验证失败
第二个接口:
请求数据:
请求路径:/user/getLoginUser
请求方式:GET
接口描述:查询当前登录的用户
请求参数:无
响应数据:Content-Type:text.html
响应内容:zhangsan
3.2.2 服务器代码
服务器代码实现了两个接口,一个是登录页面,接收账号和密码,并且将账号的信息存储在session中,如果账号密码正确返回true,其余情况返回false。
一个是显示登录用户名字的页面,根据session查询登录用户的名字返回给前端。
java
@RequestMapping("/userL")
@RestController
public class UserLoginController {
@RequestMapping("/login")
public boolean login(String userName, String password, HttpSession session) {
//参数的校验
//方法1:
// if(userName == null || userName == "" || password == null || password == "") {
// return false;
// }
//方法2:
if(!StringUtils.hasLength(userName) || !StringUtils.hasLength(password)) {
return false;
}
//这里使用固定的账号密码
if("admin".equals(userName) && "123456".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;
}
}
3.2.3 前端代码
登录界面:
这里使用了Ajax技术,作用是可以保证页面不重新加载页面,跟服务器交换数据,更新局部页面,下面使用的jQuery语法来创建Ajax请求。
html
<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="js/jquery-3.7.1.min.js"></script>
<script>
// 创建login函数
function login() {
// 使用jQuery来创建Ajax请求
$.ajax({
url: "/userL/login",
type: "post",
//发送到服务端的数据
data: {
userName: $("#userName").val(),
password: $("#password").val()
},
//http请求成功
success: function(response) {
if(response) {
//跳转到新的页面
location.href = "index.html";
}else {
alert("密码错误,请重新输入");
}
}
});
}
</script>
</body>
用户登录成功首页:
这里也是用了Ajax技术来实现的。
html
<body>
登录人: <span id="loginUser"></span>
<script src="js/jquery-3.7.1.min.js"></script>
<script>
$.ajax({
url: "userL/getLoginUser",
type: "get",
success: function(userName) {
$("#loginUser").text(userName);
}
});
</script>
</body>
3.2.4 运行测试


3.3 留言板

3.3.1 约定前后端交互接口
获取全部留言:
请求:GET
url:message/getList
响应:JSON格式{ "from": "A", "to": "B", "message": "C" },{ "from": "D", "to": "E", "message": "F" }
发表新的留言:
请求: POST JSON格式
url:/message/publish
{
"from": "A",
"to": "B",
"message": "C"
}
响应:JSON格式{
ok: 1
}
3.3.2 服务器代码
lombok介绍:
Lombok是一个工具库,通过添加注解的方式,来简化Java的开发。
我们使用时候,需要导入依赖:
java
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
lombok库里面包含很多注解:
@Data注解是直接包含很多的方法,在编译时候就会加到类里面的,此时注解会消失。

下面这个类,可以换成添加注解的形式:
java
public class UserInfo {
private String name;
private int gender;
private int age;
public UserInfo() {
}
public UserInfo(String name, int gender, int age) {
this.name = name;
this.gender = gender;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getGender() {
return gender;
}
public void setGender(int gender) {
this.gender = gender;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "UserInfo{" +
"name='" + name + '\'' +
", gender=" + gender +
", age=" + age +
'}';
}
}
下面代码在编译之后,就会包含上面的各种方法。
java
@Data
public class UserInfo {
private String name;
private int gender;
private int age;
}
引入依赖的新方式:
使用EditStarter插件。




我们就可以通过上面页面添加依赖了。
服务器代码实现:
先定义一个类来表示一条留言对象。
java
@Data
public class MessageInfo {
private String from;
private String to;
private String message;
}
设置两个接口,一个是接收留言数据,显示在下面,另一个是保存留言数据在顺序表中,保证留言信息不会刷新掉。
java
@RequestMapping("/message")
@RestController
public class MessageController {
private List<MessageInfo> messageInfoList = new ArrayList<>();
@PostMapping("/publish")
public String publish(@RequestBody MessageInfo messageInfo) {
//使用@RequestBody注解,将JSON字符串转成对象
//检查参数规范
if(!StringUtils.hasLength(messageInfo.getFrom()) ||
!StringUtils.hasLength(messageInfo.getTo()) ||
!StringUtils.hasLength(messageInfo.getMessage())) {
return "{\"ok\": 0}";
}
//存储留言
messageInfoList.add(messageInfo);
return "{\"ok\": 1}";
}
@GetMapping("/getList")
public List<MessageInfo> getList() {
return messageInfoList;
}
}
3.3.3 前端代码
前端使用JSON.stringify()方法将对象转为JSON字符串,使用JSON.parse()方法将JSON字符串转为对象。
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="js/jquery-3.7.1.min.js"></script>
<script>
load();
//显示所有留言
function load() {
$.ajax({
type: "get",
url: "/message/getList",
success: function(messages) {
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向后端发送请求
$.ajax({
type: "post",
url: "/message/publish",
contentType: "application/json",
//stringify方法将对象转为JSON字符串
data: JSON.stringify(data),
success: function (result) {
//使用parse方法将JSON字符串转成对象
var str = JSON.parse(result);
if(str.ok == 1) {
//成功
var divE = "<div>" + from + "对" + to + "说" + say + "</div>";
//将内容添加到留言板下面
$(".container").append(divE);
//清空输入内容
$("#from").val("");
$("#to").val("");
$("#say").val("");
}else {
//失败
alert("留言发布失败");
}
}
});
}
</script>
</body>
</html>
3.3.4 运行测试

3.4 图书管理系统
3.4.1 生成前端页面的工具
Bootstarp网站,这里面可以有很多模版和样式,可以直接使用。


3.4.2 约定前后端交互接口
登录接口:
url /user/login
post
请求参数:name=admin&password=admin
响应:
true //账号密码正确
false //账号密码错误
图书列表展示:
url /book/getList
post
请求参数:无
响应:
返回图书列表
{ "id": 1, "bookName": "活着", "author": "余华", "count": 270, "price": 20, "publish": "北京⽂艺出版社",17 "status": 1, "statusCN": "可借阅" }, ........
字段说明:

3.4.3 服务器代码
用户登录接口:
java
@RequestMapping("/user")
@RestController
public class UserController {
@RequestMapping("/login")
public Boolean login(String name, String password, HttpSession session) {
if(!StringUtils.hasLength(name) && !StringUtils.hasLength(password)) {
return false;
}
//未使用数据库
if("admin".equals(name) && "123456".equals(password)) {
session.setAttribute("userName", name);
return true;
}
return false;
}
}
图书列表接口:
java
@RequestMapping("/book")
@RestController
public class BookController {
@RequestMapping("/getList")
public List<BookInfo> getList() {
//这里没有使用数据库,先mock数据(自己构造虚假数据)
List<BookInfo> bookInfos = mockData();
//这里返回给前端的图书借阅状态设置成文字
for(BookInfo book : bookInfos) {
if(book.getStatus() == 1) {
book.setStatusCN("可借阅");
}else {
book.setStatusCN("不可借阅");
}
}
return bookInfos;
}
//该方法从mock的数据中查数据
public List<BookInfo> mockData() {
List<BookInfo> bookInfos = new ArrayList<>();
for (int i = 1; i <= 15 ; i++) {
BookInfo book = new BookInfo();
book.setId(i);
book.setBookName("图书" + i);
book.setAuthor("作者" + i);
book.setCount(new Random().nextInt(80) + 21); //[20,100]
book.setPrice(new BigDecimal(new Random().nextInt(80) + 21));
book.setPublish("图书出版社" + i);
book.setStatus(i % 5 == 0 ? 2 : 1); //1-可借阅 2-不可借阅
bookInfos.add(book);
}
return bookInfos;
}
}
3.4.4 前端代码
登录页:
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>
<link rel="stylesheet" href="css/bootstrap.min.css">
<link rel="stylesheet" href="css/login.css">
<script type="text/javascript" src="js/jquery.min.js"></script>
</head>
<body>
<div class="container-login">
<div class="container-pic">
<img src="pic/computer.png" width="350px">
</div>
<div class="login-dialog">
<h3>登陆</h3>
<div class="row">
<span>用户名</span>
<input type="text" name="userName" id="userName" class="form-control">
</div>
<div class="row">
<span>密码</span>
<input type="password" name="password" id="password" class="form-control">
</div>
<div class="row">
<button type="button" class="btn btn-info btn-lg" onclick="login()">登录</button>
</div>
</div>
</div>
<script src="js/jquery.min.js"></script>
<script>
function login() {
$.ajax({
type: "post",
url: "/user/login",
data: {
name: $("#userName").val(),
password: $("#password").val()
},
success: function (result) {
if(result) {
//账号密码正确
location.href = "book_list.html";
}else {
alert("账号或密码错误,请重新输入");
}
}
});
}
</script>
</body>
</html>
图书列表页:
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>图书列表展示</title>
<link rel="stylesheet" href="css/bootstrap.min.css">
<link rel="stylesheet" href="css/list.css">
<script type="text/javascript" src="js/jquery.min.js"></script>
<script type="text/javascript" src="js/bootstrap.min.js"></script>
<script src="js/jq-paginator.js"></script>
</head>
<body>
<div class="bookContainer">
<h2>图书列表展示</h2>
<div class="navbar-justify-between">
<div>
<button class="btn btn-outline-info" type="button" onclick="location.href='book_add.html'">添加图书</button>
<button class="btn btn-outline-info" type="button" onclick="batchDelete()">批量删除</button>
</div>
</div>
<table>
<thead>
<tr>
<td>选择</td>
<td class="width100">图书ID</td>
<td>书名</td>
<td>作者</td>
<td>数量</td>
<td>定价</td>
<td>出版社</td>
<td>状态</td>
<td class="width200">操作</td>
</tr>
</thead>
<tbody>
</tbody>
</table>
<div class="demo">
<ul id="pageContainer" class="pagination justify-content-center"></ul>
</div>
<script>
getBookList();
function getBookList() {
$.ajax({
type: "post",
url: "/book/getList",
success: function (books) {
var finalHtml = "";
for(var book of books) {
finalHtml += '<tr>';
finalHtml += '<td><input type="checkbox" name="selectBook" value="' + book.id + '" id="selectBook" class="book-select"></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><div class="op">';
finalHtml += '<a href="book_update.html?bookId=' + book.id + '">修改</a>';
finalHtml += '<a href="javascript:void(0)" onclick="deleteBook(' + book.id + ')">删除</a>';
finalHtml += '</div></td></tr>';
}
$("tbody").html(finalHtml);
}
});
}
//翻页信息
$("#pageContainer").jqPaginator({
totalCounts: 100, //总记录数
pageSize: 10, //每页的个数
visiblePages: 5, //可视页数
currentPage: 1, //当前页码
first: '<li class="page-item"><a class="page-link">首页</a></li>',
prev: '<li class="page-item"><a class="page-link" href="javascript:void(0);">上一页<\/a><\/li>',
next: '<li class="page-item"><a class="page-link" href="javascript:void(0);">下一页<\/a><\/li>',
last: '<li class="page-item"><a class="page-link" href="javascript:void(0);">最后一页<\/a><\/li>',
page: '<li class="page-item"><a class="page-link" href="javascript:void(0);">{{page}}<\/a><\/li>',
//页面初始化和页码点击时都会执行
onPageChange: function (page, type) {
console.log("第"+page+"页, 类型:"+type);
}
});
function deleteBook(id) {
var isDelete = confirm("确认删除?");
if (isDelete) {
//删除图书
alert("删除成功");
}
}
function batchDelete() {
var isDelete = confirm("确认批量删除?");
if (isDelete) {
//获取复选框的id
var ids = [];
$("input:checkbox[name='selectBook']:checked").each(function () {
ids.push($(this).val());
});
console.log(ids);
alert("批量删除成功");
}
}
</script>
</div>
</body>
</html>
3.4.5 运行测试


4. 应用分层
为什么要应用分层?
我们前面写的代码创建了很多类,这些类混在一起就会难以阅读和区分,不方便别人阅读自己的代码,此时就需要一种规范来将代码进行分类,类似于spring MVC框架将系统分为Model(模型),View(视图),Controller(控制器)三个层次,将用户视图和业务处理完全分开,通过控制器来进行连接。

现在主流的开发模式是"前后端分离",前端和后端各自进行开发,又在Java后端产生了新的三层架构:
表现层 :用来接受请求和返回响应的,表现层一般使用Controller来表示。
业务逻辑层 :用来处理具体的业务的,业务逻辑层一般使用Sevice来表示。
数据层 :用来管理和存储数据的,数据层一般使用Dao来表示。
实体类可以使用 model , entity , pojo 这些来表示。
MVC模式和三层架构的联系:
MVC模型主要是把数据和视图分离,将数据展示和数据处理分开,通过控制器来建立联系。
三层架构是从不同的维度处理数据的高内聚和低耦合,将交互逻辑,业务处理,数据库管理分开。
5. 总结
本文介绍了很多注解:
@RequestMapping:路由映射,用来表示URL的。
@RequestParam:对前端传过来的参数名字进行重命名。
@RequestBody:接收前端传来的JSON类型的参数的。
@PathVariable:接受路径中的参数的。
@RequestPart:接收前端传来的文件的。
@ResponseBody:返回数据的。
@Controller:定义一个控制器,spring框架启动时加载该类,把这个对象交给spring管理,默认该类返回视图。
@RestController:@ResponseBody + @Controller 返回数据。
@CookieValue:从Cookie中获取值。
@SessionAttribute:从Session中获取值。
@RequestHeader:从Header中获取值。
Spring MVC内置的HttpServletRequest 和 HttpServletResponse两个对象,可以获得请求和响应中的所有参数,Cookie和session可以从HttpServletRequest中获取,可以使用HttpServletResponse来设置状态码。