文章目录
- 1. 什么是 Spring MVC?
- 1.1 MVC 定义
- 1.2 MVC 和 Spring MVC 的关系
- 2. Spring MVC 有什么用 ?
- 3. 如何学 Spring MVC ?
- 3.1 Spring MVC 的创建
- 3.2 实现连接功能
- 3.2.1 @RquestMapping 详解
- 1. @RequestMapping 支持什么请求?
- 2. 请求限定
- 3. @GetMapping 和 @PostMapping
- 4. consumes 和 produces 有什么区别?
- 3.2.2 @RequestBodey 详解
- 3.2.3 @RequestBody 详解
- 3.3 获取参数
- 3.3.1 获取单个参数
- 3.3.2 获取多个参数
- 3.3.3 获取对象
- 3.3.4 后端参数重命名
- 3.3.5 @RquestBody 接收 json 对象
- 3.3.6 @PathVariable 获取基础 URL 中的参数
- 3.3.7 @RequestPart 上传图片
- 3.3.8 获取 Cookie/Session/header
- 3.4 返回数据
- 3.4.1 返回静态页面
- 3.4.2 请求转发与请求重定向(forward vs redirect)
- 请求转发 (forward)
- 请求重定向 (redirect)
- 二者区别:
1. 什么是 Spring MVC?
SpringMVC 是 Spring Framework 的一部分,是一个基于 Servlet API 构建的原始的 Web 框架 , 它的正式名称为 Spring Web MVC.
从上述定义中我们可知:
- Spring MVC 是一个 Web 框架.
- Spring MVC 是基于 Servlet API 构建的.
然而真正理解 SpringMVC , 我们需要先知道什么是 MCV?
1.1 MVC 定义
MVC 是 Model View Controller 的缩写 , 它是软件工程的一种软件框架模式 , 它把软件系统分为 模型 , 视图 , 控制器三个基本部分.
- Model( 模型) 是应用程序中用于处理应用程序数据逻辑部分 , 通常负责在数据库中存取数据.
- Controller (控制器) 是应用程序中用于用户交互的部分 , 通常负责从模型读取数据,控制用户输入并向视图发送数据.
- View (视图) 是应用程序中处理数据显示的部分 , 通常视图是依据模型数据创建的.
1.2 MVC 和 Spring MVC 的关系
MVC 是一种思想 , 而 Spring MVC 是对 MVC 思想的具体实现. 它利用 MVC 模型的优点来构建 Web 程序. SpringMVC框架的控制器是基于 Servlet API 实现的 , Spring MVC 提供了很多功能,如请求映射、视图解析、数据绑定等,使得开发Web应用程序变得更加简单和高效。
2. Spring MVC 有什么用 ?
绝大多数的 java 项目都是基于 Spring 的 , 而 Spring 的核心就是 Spring MVC , 它的作用是帮助开发者快速搭建 Web 应用程序 , SpringMVC 还提供了一系列的组件和工具,包括控制器、模型、视图、拦截器、表单处理、验证、异常处理等等,这些组件和工具可以帮助开发者更加方便地进行 Web 应用程序的开发和部署.
创建 Spring Boot 项目时 , 勾选的 Spring Web 框架其实就是 Spring MVC 框架 , 由此可以看出之所以要学 Spring MVC 是因为它是一切项目的基础.
3. 如何学 Spring MVC ?
Spring MVC 主要有以下三个功能:
- 连接功能: 将浏览器和 java 程序连接起来 , 也就通过一个地址可以调用到我们的 spring 程序.
- 获取参数功能: 想办法获取用户访问时携带的参数.(80%的时间都是该操作)
- 输出参数的功能: 执行了业务逻辑之后 , 要把程序执行的结果返回给用户.
3.1 Spring MVC 的创建
- 2018 年之前 , 使用 Maven 项目添加 Spring MVC 框架的方式创建.(太复杂)
- 现如今 , 使用 Spring Boot 来创建.
只需在创建 Spring 项目时添加 SpringWeb 框架即可.
3.2 实现连接功能
创建 UserController 类时 , 尽可能遵循标准分层.
@RestController // = @ResponeBody + @Controller
public class UserController {
@RequestMapping(value = "/sayhi")
public String sayhi(){
System.out.println("hi spring mvc");
return "hi spring mvc";
}
}
Tips: @RestController = @RequestBody + @ Controller
这样实现之后 , 当访问地址 http://localhost:8080/sayhi 时 , 就能打印 "hi spring mvc"的信息了.
3.2.1 @RquestMapping 详解
@RequestMapping 是 Spring Web 应用程序中最常被用到的注解之一 , 它用来注册接口的路由映射.
路由映射: 当用户访问一个 url 时 , 将用户的请求对应到程序中某个方法的过程就叫路由映射.
1. @RequestMapping 支持什么请求?
根据常识可知 , 访问浏览器地址栏属于 GET 请求.
通过 postman 来测试 POST 请求. 显然也是支持的.
继续测试发现所有请求 @RquestMapping 都是支持的.
2. 请求限定
但业务常见中 , 通常要求我们统一请求类型 , 这就意味着除了规定请求外我们不能使用其他请求.
查看源码 , 我们发现通过修改 method 可以限定请求类型.
name 相当于起个别名 , value 和 path 作用一致都是用于路由映射 , method 用于设置请求类型
params 用于获取参数 , headers 用于获取请求头中的信息.
consumes 和 produces 用于指定请求的 Content-Type 和 Accept 类型 , 即请求和响应的数据格式.
例如 , 我们需要限定请求的类型是 POST 和 Delete
@RequestMapping(value = "/sayhi",method = {RequestMethod.POST,RequestMethod.DELETE})
public String sayhi(){
System.out.println("hi spring mvc");
return "hi spring mvc";
}
接着我们通过 url 访问(GET) , 会发现页面返回 405 异常 (请求方式不支持)
3. @GetMapping 和 @PostMapping
除了@RequestMapping 以外 , @GetMapping 和 @PostMapping 也可以建立连接 , 不过 @PostMapping 限定只能接收 Post 请求 , @GetMapping 限定只能接收 Get 请求.
此时访问 localhost:8080/sayHi 就会报 405 请求类型不支持异常 , localhost:8080/sayHi2 可以正常访问.
@PostMapping("sayHi")
public String reg2(){
System.out.println("sayHi");
return "sayHi";
}
@GetMapping("sayHi2")
public String reg22(){
System.out.println("sayHi2");
return "sayHi2";
}
4. consumes 和 produces 有什么区别?
@RestController
@RequestMapping(value = "/demo", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
public class DemoController {
@GetMapping(value = "/hello", consumes = MediaType.ALL_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<String> sayHello() {
return ResponseEntity.ok("Hello World!");
}
@PostMapping(value = "/person", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<Person> createPerson(@RequestBody Person person) {
// 处理创建Person的逻辑
return ResponseEntity.ok(person);
}
}
上述代码中 @RequestMapping 注解在类级别上指定了请求的 Content-Type 和 Accept 类型为 JSON 格式。在方法级别上,@GetMapping注解指定了请求的Content-Type为任意类型,Accept类型为JSON格式。@PostMapping注解指定了请求的Content-Type为JSON格式,Accept类型为JSON格式。
这样配置后,当客户端发送 GET 请求到/demo/hello时,可以接受任意类型的请求,但只会返回 JSON 格式的响应。当客户端发送 POST 请求到/demo/person时,只能发送 JSON 格式的请求,也只会返回 JSON 格式的响应。这样可以确保请求和响应的数据格式一致,提高了系统的可靠性和稳定性。
3.2.2 @RequestBodey 详解
@ResponseBody 注解用于将方法的返回值转换成指定的格式,并将其作为响应体返回给客户端。
当处理请求时,SpringMV 会根据方法的返回值类型选择合适的转换器将返回值转换成指定的格式,例如 JSON、XML等。然后,将转换后的数据作为响应体返回给客户端。例如:下面示例方法的返回值是一个Java对象,SpringMVC会将其转换成JSON格式的数据,并将其作为响应体返回给客户端
@GetMapping("/users/{id}")
@ResponseBody
public ResponseEntity<User> getUserById(@PathVariable("id") Long id) {
User user = userService.getUserById(id);
return ResponseEntity.ok(user);
}
当客户端访问localhost:8080/users/1时,会返回一个JSON格式的数据,例如:
{
"id": 1,
"name": "Alice",
"age": 20
}
Tips: @ResponseBody 注解只能用于处理方法的返回值,不能用于处理请求体中的数据。如果需要处理请求体中的数据,可以使用 @RequestBody 注解。
3.2.3 @RequestBody 详解
@RequestBody注解用于将请求的数据绑定到方法的参数上,通常用于接收POST、PUT等请求中的JSON数据。
当客户端发送POST、PUT等请求时,请求体中的数据通常是JSON格式的,@RequestBody注解可以将这些JSON数据转换成Java对象,方便方法的处理。例如:
@PostMapping("/users")
public ResponseEntity<User> createUser(@RequestBody User user) {
// 处理创建用户的逻辑
return ResponseEntity.ok(user);
}
在上面的示例中,@RequestBody 注解将请求体中的 JSON数据转换成 User 对象,并将其作为参数传递给 createUser 方法。这样,我们就可以方便地处理 POST 请求中的 JSON 数据。
Tips: @RequestBody 注解只能用于接收请求体中的数据,不能用于接收请求中的 URL 参数。如果需要接收 URL 参数,可以使用@RequestParam注解。
3.3 获取参数
3.3.1 获取单个参数
@RequestMapping("/reg2")
public Object reg2(String name){
return "hi" + name;
}
由于 SpringMVC 是基于 Servlet API 的封装 , 所以 Servlet 中的方式在 SpringMVC 中同样适用.
@RequestMapping("/sayHi")
public Object reg2(HttpServletRequest request, HttpServletResponse response){
return "hi " + request.getParameter("name");
}
3.3.2 获取多个参数
方法中传入多个参数即可 (无序)
@RequestMapping("/sayHi")
public Object reg2(String user , String password){
return "user: " + user + "password: " + password;
}
3.3.3 获取对象
首先创建一个对象.
@Controller
@Data
public class Userinfo {
private int id;
private String name;
private String password;
private int age;
}
传入对象
框架会自动实现参数映射 , 也会根据结果值返回合适的类型 (Object -> application/json).
@RequestMapping("/reg")
public Object reg(Userinfo userinfo){
System.out.println(userinfo);
return userinfo;
}
3.3.4 后端参数重命名
在实际开发场景中 , 如果前后端命名不一致 , 如果前端规定用户名为 username , 而后端规定用户名为 name , 此时只能采用折中的方案 “重命名”
@RequestMapping("/reg2")
public Object reg2(@RequestParam("username") String name , String password){
return "name: " + name + "password: " + password;
}
观察 @RequestParam 注解的源码可以发现 , 如果有一个参数可传可不传 , 加上 required=fasle 此时如果不传该参数程序不会报错. 如果不加且不传递该参数就会抛400异常.
@RequestMapping("/reg2")
public Object reg2(@RequestParam(value = "username",required = false) String name , String password){
return "name: " + name +"|"+ "password: " + password;
}
3.3.5 @RquestBody 接收 json 对象
依据上述经验 , 我们应该可以使用 对象 来接收 json 对象字符串 , 但事实并非如此.
通过 postman 来构造一个 json 格式的字符串发送给后端 , 发现并没有预期的响应.
那么我们如果获取 json 对象呢?
只需给该方法加上 @RequestBody 注解 , 告诉方法我们拿到的是一个 json 对象.
@RequestMapping("/reg3")
public Object reg3 (@RequestBody Userinfo userinfo){
System.out.println("reg3");
return userinfo;
}
3.3.6 @PathVariable 获取基础 URL 中的参数
@PathVariable 用于将 URL 中的变量映射到方法的参数中。因此我们可以通过 URL 中的路径变量(Path Variable)来传递参数。
URL 传参有两种写法:
/user/12345
/user?uid=12345
通常我们使用第一种 :
- 搜索引擎抓取关键字权重更高
- url 更加简洁
从基础 URL 中获取参数:
假设 URL 为: localhost:8080/user/reg/张三/1111111
@RequestMapping("/reg/{name}/{password}")
public Object reg(@PathVarible("name") String name,@PathVatible("password") String password){
return "name->"+name+"|"+"password->"+password;
}
Tips: 使用 @RequestParam() 还是 @PathVarible() 具体看前端和给定的业务场景
3.3.7 @RequestPart 上传图片
假设将上传文件传递到 D 盘.
@RequestMapping("/myupload")
public Object upload(String name, @RequestPart("myfile") MultipartFile file){
File saveFile = new File("D:\\myfile.jpg");
try{
file.transferTO(saveFile);
return true;
}catch(IOException e){
e.printStackTrack();
}
return false;
}
构造 Postman 发送请求:
但以上写法有个致命的缺陷 , 后上传的文件会覆盖之前上传的文件. 为了解决这一问题 , 我们需要让文件名不同 , 且后缀名也是可变的(不一定每次都传递 .jpg文件).
@RequestMapping("/myupload")
public Object upload(String name, @RequestPart("myfile") MultipartFile file){
//这里我们使用 UUID 生成唯一标识
String fileName = UUID.randomUUID() +
file.getOriginalFilename().substring(file.getOriginalFilename().lastIndexof("."));
File saveFile = new File("D:\\" + fileName);
try{
file.transferTO(saveFile);
return true;
}catch(IOException e){
e.printStackTrack();
}
return false;
}
3.3.8 获取 Cookie/Session/header
Servlet 式获取 Cookie
@RequestMapping("param")
public String getCookie(HttpServletResponse response, HttpServletRequest request){
Cookie[] cookies = request.getCookies();
return cookies;
}
@CookieValue简洁的获取 Cookie
@RequestMapping("getck")
public Object getCK(@CookieValue(value = "java", require = false) String java){
return java;
}
如何使用了 @Cookie 注解 , 如何不确定有没有叫"java"的Cookie , 可以将 requires设为 false , 防止报错.
当然也可以伪造 Cookie.
**@RequestHeader 简洁的获取 Header **
@RequestMapping("/header")
public String getHeader(@RequestHeader("User-Agent")String ua){
return "header -> " + ua;
}
Tips: User-Agent 通常包含浏览器名称、版本号、操作系统名称和版本号等信息 , 也就是说 header 中的所有数据都可以获取
Session 存储和获取
想要获取 Session 首先需要向服务器存储 Session , 存储方式使用 Servlet 实现
@RquestMapping("/Session")
@ResponseBody
public String setsess(HttpServletRequest request){
//获取 Httpsession 对象,参数设置为 true, 表示如果没有 session 对象就创建一个 session 对象.
HttpSession session = request.getSession(true);
if(session == null){
session.setAttribute("username","java");
}
return "session 存储成功!";
}
@RequestMapping("/getSession")
public Object getSession(@SessionAttribute(username) String name){
return "session->"+ name;
}
3.4 返回数据
由于前后端分离 , 无需返回 xml , 后端只需返回数据即可 , 而 @RespondBody 就是实现该功能的.
3.4.1 返回静态页面
在 resource 目录的 static 目录下创建一个静态页面
@Controller
@RequestMapping("/test")
public Object test(){
return "/index.html";
}
Tips: 如果访问"index.html" 时不加 “/” , 那么就会访问失败 , 因为 index.html 在根目录下 , 而如何不加即在 test 目录下访问.
3.4.2 请求转发与请求重定向(forward vs redirect)
return 不仅可以返回一个视图 , 还可以实现跳转 , 实现跳转有两种方式:
请求转发 (forward)
// 请求转发
@RequestMapping("/forward")
public Object forward(){
return "forward:/index.html";
}
访问 index.html
请求重定向 (redirect)
// 重定向
@RequestMapping("/redirect")
public Object redirect(){
return "redirect:/index.html";
}
观察结果发现 , 重定向导致路径发生了改变.
二者区别:
1.定义不同
请求转发 (Forward): 发生在服务器内部 , 当服务器收到一个客户端请求之后 , 会先将请求转发给目标地址 , 再将目标地址的返回的结果转发给客户端 , 而客户端对这一行为是无感的 , 就好比张三找李四借 100 万 , 李四一分钱都没有 , 但是他认识有 100 万的王五 , 于是他向王五借 100 万 , 然后交给张三 , 这期间的一系列操作张三是不知道的.
请求重定向 (redirect): 当服务器收到客户端请求后 , 会返回给客户端一个临时响应头 , 临时响应头中记录了客户端需要重新请求的 URL 地址 , 客户端再收到地址的时候 , 会将请求发送到新的地址上. 就好比张三找李四借 100 万 , 李四一分钱都没有 , 但是他认识有 100 万的王五 , 于是他把王五的地址发送给张三 , 张三拿到地址后就会去向王五借钱.
2.请求方不同
请求转发是服务器的行为 , 服务器代替客户端发送请求 , 并返回给客户端. 而请求重定向是客户端的行为.
3.数据共享不同
请求转发是浏览器完成的 , 客户端只发送一次请求 , 整个过程中都使用同一个 request对象 和 respond对象 , 因此数据是共享的. 而请求重定向时 , 客户端会发送两个完全不同的请求 , 这两次请求的数据并不是共享的.
4.最终 URL 地址不同
请求转发由服务器代为执行 , 整个过程中客户端是感知不到的 , 最终 URL 地址并不会发生变化. 而请求重定向时 , 服务器会告诉客户端:“你到另一个地方去” , 客户单会重新发送请求 , 最终 URL 地址就会发送改变.
5.实现代码不同
在 Springboot 中请求转发的代码为:
// 请求转发
@RequestMapping("/forward")
public Object forward(){
return "forward:/index.html";
}
请求重定向的代码为:
// 重定向
@RequestMapping("/redirect")
public Object redirect(){
return "redirect:/index.html";
}
请求转发 (forward) 可能导致的问题
-
重定向循环:如果请求转发到的资源或页面又将请求转发回原始页面,就会形成一个无限循环,导致应用崩溃或响应缓慢。
-
内存泄漏:如果请求转发过程中出现异常或错误,可能会导致内存泄漏,使服务器资源耗尽。
-
安全漏洞:请求转发可能会导致安全漏洞,如跨站点脚本攻击(XSS)和跨站点请求伪造(CSRF)等。
-
性能问题:请求转发可能会导致性能问题,如响应时间延长、服务器负载增加等。
求转发的代码为:
// 请求转发
@RequestMapping("/forward")
public Object forward(){
return "forward:/index.html";
}
请求重定向的代码为:
// 重定向
@RequestMapping("/redirect")
public Object redirect(){
return "redirect:/index.html";
}
请求转发 (forward) 可能导致的问题
-
重定向循环:如果请求转发到的资源或页面又将请求转发回原始页面,就会形成一个无限循环,导致应用崩溃或响应缓慢。
-
内存泄漏:如果请求转发过程中出现异常或错误,可能会导致内存泄漏,使服务器资源耗尽。
-
安全漏洞:请求转发可能会导致安全漏洞,如跨站点脚本攻击(XSS)和跨站点请求伪造(CSRF)等。
-
性能问题:请求转发可能会导致性能问题,如响应时间延长、服务器负载增加等。