Spring Web MVC 入门
一. Spring Web MVC 概述
1. Spring MVC 简介
Spring Web MVC, 简称 Spring MVC (Spring Model - View - Controller). Spring Web MVC 是使用Java语言, 基于 Servlet API , 实现了 MVC架构 的一个 Web框架.
2. MVC 是什么
MVC 即: "Model - View - Controller". MVC是一种设计模式 / 架构, 也是一种思想.
[1] Model(模型): 用于数据处理.
[2] view(视图): 人能看到的界面, 用于人机交互. 用于向Controller发送请求, 从Controller获取响应.
[3] Controller(控制器): 连接模型和视图 (中间处理单元), 根据视图发来的请求决定用那个模型处理.
可以简单理解为: 服务员就是view(负责人机交互, 和客户进行交流), 收银/前台 就是Controller(负责中间处理), 后厨就是Model(负责数据处理, 逻辑处理).
二. Spring MVC 学习
学习SpringMVC, 主要就是学习三个方面:
① 建立连接: 将用户和Java程序连接起来. 也就是在浏览器中通过一个地址能够访问到我们的程序.
② 请求: 建立连接之后, 用户肯定会向服务端发起请求. 请求中有时会带一些参数.
③ 响应: 服务端在执行完业务逻辑之后, 将结果返回给用户, 这就叫做响应.
1. 建立连接
@RestController注解 作用: 标记一个类, 表示这个类用于 处理http请求 并 返回响应.
@RequestMapping注解 作用: 为某类或者某方法的设置一个的请求路径.
- @RequestMapping 定义在某类上就是为某类设置一个请求路径 (类路径)
- @RequestMapping 定义在某方法上就是为某方法设置一个请求路径 (方法路径).
举例:
@RequestMapping("/user") //类路径
public class UserController {
@RequestMapping("/v1") //方法路径
public String print() {
return "hello print";
}
[注]:
[1] 访问时, 先写类路径, 再写方法路径.
[2] 路径上的"/"可以省略, 但是通常不省略.
[3] 可以不加类路径, 只写方法路径, 但是不建议这样做. 因为不同类中的方法路径有可能重复, 加上类注解以便编译器区分, 不加类注解的话编译器就区分不了了
@RequestMapping 注解 还能指定 使用GET方法, 还是使用POST方法.
- method="RequestMethod.GET" 表示只能接收GET请求.
- method="RequestMethod.POST" 表示只能接收POST请求.
其中 value 指定资源路径.
@RequestMapping(value = "/v1", method = RequestMethod.GET) //只接收GET请求.
public String print() {
return "hello print";
}
@RequestMapping(value = "/v3",method = RequestMethod.POST) //只接收POST请求.
public String print3() {
return "hello print3";
}
为了使用方便, Spring还提供了两个注解:
@GetMapping 仅支持GET请求 ; @POSTMapping 仅支持 POST请求.
@GetMapping("/v1") // 只支持GET请求
public String print2() {
return "hello print";
}
@PostMapping("/v4") // 只支持POST请求
public String print4() {
return "hello print4";
}
[补充]:
路由映射: 当用户访问一个 URL 时, 将用户的请求对应到程序中某个类的某个方法的过程就叫做路由映射.
2. 请求
(1) 传递参数
① 传递单个参数
传递单个参数, 就直接在方法里写一个参数. 在构造请求的时候, 把参数加上即可.
代码:
@RequestMapping("/param")
@RestController
public class ParamController {
@RequestMapping("/p1")
public String p1(String name) { //对参数name的处理函数p1
return "接收到参数name: " + name;
}
}
前端请求: (使用 postman 来构造)
- 这里的参数我们直接写在请求的URL中.
② 传递多个参数
- 接收一个参数, 方法的形参写一个:
@RequestMapping("/p1")
public String p1(String name) {
return "接收到参数name: " + name;
}
- 接收两个参数, 方法的形参写两个:
@RequestMapping("/p2")
public String p4(String name, int age) {
return "接收到参数name: " + name + ", age: " + age;
}
- 接收多个参数, 方法的形参写多个:
@RequestMapping("/p3")
public String p5(String name, int age, String gender) {
return "接收到name: " + name + ", age: " + age + ", gender: " + gender;
}
[接收参数使用Servlet层实现]:
@RequestMapping("/p4")
public String p4(String name, int age) { // Spring实现
return "接收到参数name: " + name + ", age: " + age;
}
@RequestMapping("/p41")
public String p41(HttpServletRequest reaquest) { // Servlet实现
String name = reaquest.getParameter("name");
int age = Integer.parseInt(reaquest.getParameter("age"));
return "接收name: " + name + ", age: " + age;
}
注意: 在请求中传参, 不需要保证 参数的顺序 和服务端代码中 参数的顺序 相同. 只需要保证参数类型和个数一致即可.
(这是因为 参数在Spring底层是用Map存储的, 而Map中的元素是无序的).
(2) 传递对象
我们上面提到, 传几个参数, 就在方法的形参里写几个参数即可. 但是如果参数很多呢 -- 有十几个甚至几十个呢? 此时我们在一个一个将形参列出来就很麻烦了.
所以我们就引入一个新的传参方式: 传递对象.
我们可以将多个参数封装成一个对象, 然后接收这个对象即可.
User对象:
public class User {
private String name;
private int age; //age是int类型的
private String gender;
private String classNumber;
// getter and setter
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getGender() {
return gender;
}
public void setGender(String gender) {
this.gender = gender;
}
public String getClassNumber() {
return classNumber;
}
public void setClassNumber(String classNumber) {
this.classNumber = classNumber;
}
//重写 toString()
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", age=" + age +
", gender='" + gender + '\'' +
", classNumber='" + classNumber + '\'' +
'}';
}
}
接收User对象:
@RequestMapping("/p6")
public String p6(User user) {
return "接收到user: " + user;
}
但是在发送请求的时候, 我们将要传的参数一个一个列出来, 然后该请求 传递到后端被接收时 参数就会自动转换成User类型的对象.
(3) 参数绑定 (参数重命名)
使用 @RequestParam 注解, 这个注解的作用是"参数绑定"
@RequestParam() 的括号内是前端的参数名称, 括号外是后端的参数名称. 表示把前端参数名和后端参数名进行绑定. 也可以成为"重命名".
@RequestMapping("p7")
public String p7(@RequestParam("UserName") String name) {
return name;
}
例如上述代码, 就是把 前端名为"UserName"的参数 和 后端名为"name"的参数 进行了绑定.
@RequestParam 注解 还有一个参数"required", 其有两个值(true"必选" 和 false"非必选"), 用来说明当前参数是否为必选参数 (默认为 true"必选参数")
@RequestMapping("p7")
public String p7(@RequestParam(value = "UserName", required = true) String name) { // 必选参数
return name;
}
@RequestMapping("p7")
public String p7(@RequestParam(value = "userName", required = false) String name) { //非必选参数
return name;
}
- 如果是必选参数 (true), 那么前端如果不传此参数, 就会报错.
- 如果不是必选参数 (false), 那么前端若果不传此参数, 没有影响.
(3) 传递数组
后端以数组方式接收参数, 代码如下:
@RequestMapping("/p8")
public String p8(String[] arr) {
return "arr: " + List.of(arr);
}
那么前端该如何传递一个数组呢?
- 前端传参数的时候, 我们可以传递多个变量名相同的参数, 后端在接收到这些参数之后会把它们解析成一个数组. 示例如下:
-
也可以直接传递一个字符串, 后端接收到 arr=AAA,BBB,CCC 这个字符串之后, 会对它进行自动分割, 并封装成一个数组.示例如下:
(4) 传递集合
集合的种类有很多, 我们这里以传递List为例.
因为前端传递多个参数的时候默认是以数组的形式传递. 所以如果要想使前端传来的参数转化成集合, 就需要使用 @RequestParam 注解 来将前端和后端进行绑定, 这样传过来的参数就会自动转换成集合了.
@RequestMapping("/p9")
public String p9(@RequestParam List<String> list) {
return "list: " + list;
}
(5) 传递 JSON 数据
① 认识JSON
JSON (JavaScript Object Notation "JavaScript 对象表示法") (JSON只能表示对象). 简单来说, JSON是一种数据格式 (有自己的语法格式), 用文本来表示一个对象或数组的信息. 的本质是字符串, 负责在不同语言中数据的传递与交换.
JSON是一种纯文本的格式.
[JSON语法]:
数据在键值对(key-value)中.
数据之间用 "," 分割.
对象用 "{ }" 表示.
数组用 "[ ]" 表示.
值 可以为对象, 也可以为数组. 数组中可以包含对个对象, 对象中也可以包含多个数组.
可读性: xml > JSON > protbuf
轻量级: xml < JSON < protbuf
所以, 如果要同时兼顾可读性和轻量级, 那么JSON是一个折个的办法.
下面展示一段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"]
}]
}
JSON字符串 和 Java对象 的相互转换:
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
public class JsonDemo {
public static void main(String[] args) throws JsonProcessingException {
ObjectMapper objectMapper = new ObjectMapper();
User user = new User();
user.setName("Jack");
user.setAge(18);
user.setGender("male");
user.setClassNumber("112");
//对象转JSON
String s = objectMapper.writeValueAsString(user);
System.out.println(s);
//JSON转对象
User user1 = objectMapper.readValue(s, User.class);
System.out.println(user1);
}
}
② 前后端使用JSON交互
首先我们要明确: 在请求中传输参数的时候时: 可以直接在URL中带上参数, 也可以在Header中发送参数, 还可以在Body中发送参数.
如果要用JSON传输数据, 就只能在Body中传输. 而且在后端代码中需要加上 @RequestBody 注解 表示从body(正文)中获取数据.
后端代码:
@RequestMapping("/p10")
public String p10(@RequestBody User user) {
return "user: " + user;
}
前端传参: Body --> raw (表示传原始数据)
(6) 获取 URL 中的参数
使用 @PathVariable 注解 从URL中获取参数.
当前后端参数不一致的时候, 需要在注解后面的括号里加上前端参数名, 如果前后端参数名一致, 注解后面的括号里可以不写.
@RequestMapping("/article/{articleId}")
public String p11(@PathVariable("articleId") String articleId) {
return "articleId: " + articleId;
}
@RequestMapping("/article/{articleId}")
public String p11(@PathVariable String articleId) {
return "articleId: " + articleId;
}
// 前后端参数名一样的话, 上面两种方式都可以.
@RequestMapping("/article/{articleId}")
public String p11(@PathVariable("articleId") String id) {
return "articleId: " + id;
}
//前后端参数名不一样的话, 需要在括号里写上前端参数名.
也可以从URL中获取多个参数:
@RequestMapping("/article/{articleId}/{authorName}")
public String p11(@PathVariable String articleId, @PathVariable("authorName") String name) {
return "articleId: " + articleId + ", authorName: " + name;
}
[注]: URL中的都是必选参数 (required=false)
(7) 提交表单数据 (上传文件)
使用 @RequestPart 注解.
@RequestMapping("/p13")
public String p13(MultipartFile file) {
System.out.println(file.getOriginalFilename());
return "file: " + file.getOriginalFilename();
}
和传递JSON类似, 传递表单数据也只能在Body中发送 (Body --> form-data)
传输文件也可以重命名 ("文件名绑定"). 使用@RequestPart注解.
@RequestMapping("/p13")
public String p13(@RequestPart("fileName") MultipartFile file) {
System.out.println(file.getOriginalFilename());
return "file: " + file.getOriginalFilename();
}
(8) 获取 Cookie/Session
① 获取Cookie
- 原始方式:
// 原始方式
@RequestMapping("/getCookie")
public String getCookie(HttpServletRequest request) {
Cookie[] cookies = request.getCookies();
//TODO cookie可能为空, 需要做一下空指针的判断.
for (Cookie cookie : cookies) {
System.out.println(cookie.getName() + " : " + cookie.getValue());
}
return "获取Cookie成功";
}
- 注解方式:
// 使用注解
@RequestMapping("/getCookie2")
public String getCookie2(@CookieValue("AAA") String name) {
return "从Cookie中获取信息, name: " + name;
}
② 设置和获取Session
设置Session:
@RequestMapping("/setSession")
public String setSession(HttpServletRequest request) {
HttpSession session = request.getSession();
session.setAttribute("userName", "zhangsan");
session.setAttribute("userAge", "18");
return "设置Session成功";
}
获取Session:
- 原始方式
// 原始方式
//session是类似map的结构
@RequestMapping("/getSession")
public String getSession(HttpSession session) {
String userName = (String) session.getAttribute("userName");
int userAge = (Integer) session.getAttribute("userAge");
return "userName: " + userName + ", userAge: " + userAge;
}
- 注解方式:
// 使用注解
@RequestMapping("/getSession3")
public String getSession3(@SessionAttribute("userName") String userName) {
return "userName: " + userName;
}
[注意]:
- getSession(true): 如果Session对象为空, 会创建一个空的Session.
- getSession(false): 如果Session对象为空, 不会创建空Session.
- getSession() 默认为true.
- Session存储在服务器的内存上, 重启服务, Session会丢失~
(9) 获取 Header
- 原始方式:
// 原始方式
@RequestMapping("/getHeader")
public String getHeader(HttpServletRequest request) {
String userAgent = request.getHeader("User-Agent");
return "userAgent: " + userAgent;
}
- 注解方式:
// 使用注解
@RequestMapping("getHeader2")
public String getHeader2(@RequestHeader("User-Agent") String userAgent) {
return "userAgent: " + userAgent;
}