1. MVC
MVC 是 Model View Controller 的缩写,它是软件⼯程中的⼀种软件架构设计模式,它把软件系统分为模型、视图和控制器三个基本部分。
View (视图): 指在应⽤程序中专⻔⽤来与浏览器进⾏交互,展⽰数据的资源.
Model (模型): 是应⽤程序的主体部分,⽤来处理程序中数据逻辑的部分.
Controller(控制器):可以理解为⼀个分发器,⽤来决定对于视图发来的请求,需要⽤哪⼀个模型来处理,以及处理完后需要跳回到哪⼀个视图。即⽤来连接视图和模型。
2. Spring MVC
Spring MVC 是对 MVC 思想的具体实现,此外 Spring MVC 还是一个 web 框架,所以说 Spring MVC 是一个实现了 MVC 模式的 web 框架
3. 项目创建
Spring MVC 项目的创建和上次的 Spring Boot 项目的步骤一样
3.1. 建立连接
在 Spring MVC 中使用 @RequestMapping 来实现 URL 路由的映射,也就是浏览器链接程序的作用
@RequestMapping 是用来注册接口的路由映射的,表示服务器收到请求时,映射的“/hello”路径就会调用 hello 的方法,路径的名称也可以随便写,不用和方法名保持一致
public class HelloController { ("/hello") public String hello(){ return "hello spring"; } }
运行程序之后,然后在浏览器中输入 http://127.0.0.1:8080/hello 就能访问了
@RestController标识了这是一个控制器类,一个项目中会有很多类和方法,Spring 会对所有的类进行扫描,如果添加了 @RestController注解,才会去访问这个类中有没有 @RequestMapping 注解,如果把 @RestController去掉再访问就会访问不到这个路径
@RequestMapping既可以修饰类,也可以修饰方法,当修饰类和方法时,访问的路径是类路径 + 方法路径,如果不加类路径还是会找不到页面
@RequestMapping 标识⼀个类:设置映射请求的请求路径的初始信息。
@RequestMapping 标识⼀个⽅法:设置映射请求请求路径的具体信息。
("/user") public class HelloController { ("/hello") public String hello(){ return "hello spring"; } }
3.2. 请求
@RequestMapping同时支持 post 请求和 get 请求
("/v1") public String method(){ return "v1"; }
使用 postman 发送 get 请求和 post 请求都可以得到响应
可以通过设置来只支持 get 请求或者使用 @GetMapping来限制只支持 get 请求
(value = "/v2",method = RequestMethod.GET) public String method1(){ return "v2"; } ("/v3") public String method2(){ return "v3"; }
无论是 v2 还是 v3 都已经不支持 post 请求了
同理,如果设置为只支持 post 请求或者使用 @PostMapping就不会支持 get 请求了
(value = "/v4",method = RequestMethod.POST) public String method3(){ return "v4"; } ("v5") public String method4(){ return "v5"; }
在上面的方式中,无论是通过设置 @RequestMapping的方式还是直接使用 @PostMapping,@GetMapping 注解的方式都可以
4. 传递参数
4.1. 传递单个参数
接收单个参数,直接在方法中加入参数即可
("/param") public class ParamController { ("/m1") public String method1(String name){ return "接收到参数name: " + name; } }
在传递参数的时候,代码中的参数名称需要和请求的参数名称是一致的
如果传入的参数是整形呢,整数的话是可以用 Integer 和 int 来表示的,下面来演示一下
("/m2") public String method2(Integer age){ return "接收到参数age: " + age; } ("/m3") public String method3(int age){ return "接收到参数age: " + age; }
那么他们的区别是什么呢?
接下来试着不传参,发现 int 定义的参数是直接报错了,Integer 定义的是可以接受 null 的
并且无论是 int 还是 Integer,传入的参数一定要对应,否则也会报错
错误日志上描述的是 String 类型转化为其他类型失败,传递的普通参数,默认的类型是 String ,后端接收时根据定义的类型再进行相应的转化
int 等一些基本数据类型不能传入 null,不过 boolean 是可以传入 null 的
("/m4") public String method4(Boolean gender){ return "接收到参数gender: " + gender; } ("/m5") public String method5(boolean gender){ return "接收到参数gender: " + gender; }
可以看出,如果不传参数的话,Boolean 接收到的是 null,boolean 接收到的 false,boolean 类型默认就是 false
4.2. 传递多个参数
多个参数也是可以直接传递的,并且不需要保证传参的顺序,只需要参数名对应即可
("/m6") public String method6(String name,Integer age){ return "接收到参数name: " + name + "," + "age: " + age ; }
再传递几个参数也是可以的,不过这种方式传递这么多参数就有些麻烦了,可以通过传递对象的方式可以传入多个参数
创建一个 User 的类(重写了 toString() 方法)之后再进行对象的传递
("/m7") public String method7(User user){ return "接收到参数user: " + user; }
通过传入对象的方式就可以进行多个参数的传递,在之前,如果 int 类型参数没有传的话是会报错的,如果对象中使用 int 类型来描述属性的话,传递参数时不传也不会报错:
因为 age 是成员变量,是有默认值的,即使如此,还是建议使用包装类型,可以避免一些难以预料的问题
4.3. 参数重命名
在上面提到过,前端传递的参数要和后端方法里的参数保持一致,而前端可能会传入各种各样的参数,比如 userName, productName 等,后端如果只想要一个 name 的话可以对前端传递的参数进行重命名,把前端传入的名称都重命名为 name,后续就使用 name 进行操作,这就需要使用到 @RequestParam注解
("/m8") public String method8(("userName") String name){ return "接收到参数name: " + name; }
@RequestParam就起到了参数绑定的作用,把前端的 userName 和后端的 name 进行了绑定,那如果前端还是传入 name 会怎么样呢
直接报了一个 400 的错误,并且无论是传入参数和名称不对应还是不传递参数,都会报错
意思就是传递 userName 的参数,相当于强制绑定
来看 @RequestParam的源码:
value(或name)属性:用于指定请求参数的名称
required属性:表示该参数是否必须在请求中存在。如果设置为true,当请求中没有这个参数时,会抛出异常。如果设置为false,则在请求中没有该参数时,会使用默认值(如果有设置默认值)或者为null。
在使用@RequestParam注解时,如果只提供一个字符串参数,它会被视为value(或name)属性的值。
("/m8") public String method8((value = "userName",required = false) String name){ return "接收到参数name: " + name; }
把 required 改为 false 不传递参数也不会报错,会使用默认值或者 null
4.4. 传递数组
("/m9") public String method9(String[] arr){ return "接收到参数name: " + List.of(arr); }
传递数组的方式是有两种的,第一种就是直接传入数组中的数据,第二种就是传入多个数据,参数一致
那么第一种传入的数据是按照字符来传的还是按照数组中的三个元素来传的
调试一下发现是按照数组元素的形式自动切割了
4.5. 传递集合
数组可以通过上面的方式传,那么 List 行不行呢
("/m10") public String method10(List<String> list){ return "接收到参数list: " + list; }
这一次是直接报错了,显示 500 的状态码
抛出了异常,无法构造一个 List
原因是发送上述请求时,默认是把请求的参数封装成数组的,并不是一个 List,所以需要进行参数绑定,也就是把数组转化为 List
("/m10") public String method10( List<String> list){ return "接收到参数list: " + list; }
4.6. 传递 JSON 数据
4.6.1. JSON 语法
- 数据在键值对(Key/Value)中;
- 数据由逗号 , 分隔;
- 对象用 {} 表示;
- 数组用 [] 表示;
- 值可以为对象,也可以为数组,数组中可以包含多个对象。
接下来看 JSON 是怎么转化为 java 对象的:
使用 ObjectMapper 对象提供的两个⽅法,可以完成对象和 JSON 字符串的互转。
writeValueAsString:把对象转为 JSON 字符串。
readValue:把字符串转为对象。
public class JsonTest { public static void main(String[] args) throws JsonProcessingException { ObjectMapper objectMapper = new ObjectMapper(); User user = new User(); user.setName("zhangsan"); user.setGender(1); user.setAge(18); //对象转JSON String s = objectMapper.writeValueAsString(user); System.out.println(s); //JSON转对象 User user1 = objectMapper.readValue(s,User.class); System.out.println(user1); } }
4.6.2. 传递 JSON
传递 JSON 参数是通过@RequestBody注解来实现的,从请求正文中获取数据
("/m11") public String method11( User user){ return "接收到参数user: " + user; }
可以看出,请求的数据格式是不同的
4.7. 获取 URL 中的参数
获取 URL 中的参数是通过 @PathVariable 注解实现的
("/article/{articleId}") public String method12(("articleId") String articleId){ return "接收到参数articleId: " + articleId; }
@RequestMapping("/article/{articleId}")大括号中的内容就相当于占位符,可以传入任何参数,但是不能不传
多个参数也是可以接受的:
("/article/{articleId}/{name}") public String method12( String articleId, String name){ return "接收到参数articleId: " + articleId + ", name :" + name; }
前面也提到过,路径中的参数相当于占位符,不能少传,顺序也必须一致
把 required 改为 false 也不行
4.8. 上传文件
通过 MultipartFile可以获取文件的对象
("/m12") public String method13(MultipartFile file){ System.out.println(file.getOriginalFilename()); return "接收到参数file: " + file.getOriginalFilename(); }
通过 Fiddler 抓包可以看到上传的二进制文件
这里的重命名和上面的是不一样的,这里使用的是 @RequestPart注解