Spring MVC 详解 - Spring Boot
一、什么是 Spring MVC
官方描述:
Spring Web MVC is the original web framework built on the Servlet API and has been included in the Spring Framework from the very beginning. The formal name, “Spring Web MVC,” comes from the name of its source module (spring-webmvc), but it is more commonly known as “Spring MVC”.
引⽤来⾃:https://docs.spring.io/spring-framework/docs/current/reference/html/web.html
翻译为中⽂:
Spring Web MVC 是基于 Servlet API 构建的原始 Web 框架,从⼀开始就包含在 Spring 框架中。它的正式名称“Spring Web MVC”来⾃其源模块的名称(Spring-webmvc),但它通常被称为“Spring MVC”。
从上述定义我们可以得出两个关键信息:
1)Spring MVC 是⼀个 Web 框架。
2)Spring MVC 是基于 Servlet API 构建的。
然⽽要真正的理解什么是 Spring MVC?我们⾸先要搞清楚什么是 MVC?
1.1 MVC 定义
MVC 是 Model View Controller 的缩写,它是软件⼯程中的⼀种软件架构模式,它把软件系统分为模型、视图和控制器三个基本部分。
- Model(模型) 是应⽤程序中⽤于处理应⽤程序数据逻辑的部分。通常模型对象负责在数据库中存取数据。
- View(视图) 是应⽤程序中处理数据显示的部分。通常视图是依据模型数据创建的。
- Controller(控制器) 是应⽤程序中处理⽤户交互的部分。通常控制器负责从视图读取数据,
控制⽤户输⼊,并向模型发送数据。
1.2 MVC 和 Spring MVC 的关系
MVC 是⼀种思想,⽽ Spring MVC 是对 MVC 思想的具体实现。
总结来说,Spring MVC 是⼀个实现了 MVC 模式,并继承了 Servlet API 的 Web 框架。既然是 Web框架,那么当⽤户在浏览器中输⼊了 url 之后,我们的 Spring MVC 项⽬就可以感知到⽤户的请求。
1.3 学习目的
现在绝⼤部分的 Java 项⽬都是基于 Spring(或 Spring Boot)的,⽽ Spring 的核⼼就是 Spring MVC。简单来说,咱们之所以要学习 Spring MVC 是因为它是⼀切项⽬的基础,我们以后创建的所有 Spring、Spring Boot 项⽬基本都是基于 Spring MVC 的。
学习完 Spring MVC 我们需要掌握以下 3 个功能:
1)连接的功能: 将⽤户(浏览器)和 Java 程序连接起来,也就是访问⼀个地址能够调⽤到我们的 Spring 程序。
2)获取参数的功能: ⽤户访问的时候会带⼀些参数,在程序中要想办法获取到参数。
3)输出数据的功能: 执⾏了业务逻辑之后,要把程序执⾏的结果返回给用户。
二、Spring MVC 创建和连接
2.1 创建 Spring MVC 项目
Spring MVC 项⽬创建和 Spring Boot 创建项⽬相同(Spring MVC 使用 Spring Boot 的⽅式创建),在创建的时候选择 Spring Web 就相当于创建了 Spring MVC 的项⽬:
在创建 Spring Boot 项⽬时,我们勾选的 Spring Web 框架其实就是 Spring MVC 框架,如下图所示:
项目创建与简单使用 博客链接:https://blog.csdn.net/yyhgo_/article/details/128639865?spm=1001.2014.3001.5501
2.2 相关注解
@RequestMapping 是 Spring Web 应⽤程序中最常被⽤到的注解之⼀,它是⽤来注册接⼝的路由映射的。
路由映射:所谓的路由映射指的是,当⽤户访问⼀个 url 时,将⽤户的请求对应到程序中某个类的某个⽅法的过程就叫路由映射。
@RequestMapping 既可以修饰类,也可以修饰方法:
- 单独只修饰方法时,访问的地址是方法的 @RequestMapping 的路由。
- 当修饰类和方法时,访问的地址是类 + 方法的 @RequestMapping 的路由。
package com.example.demo.controller;import org.springframework.web.bind.annotation.RequestMapping;@RequestMapping("/web")public class WebController { @RequestMapping(value = "/hi") public Object sayHi() { return "Hi,Spring MVC"; }}
此时访问 http://localhost:8080/web/hi,报错:
为什么呢?
此时虽然你加了这个功能了,但是类还没有加载,没有人调用啊!所以要先加载并注册:加类注解 @Controller:
我们有五大类注解,添加其他的类注解是否可行呢?
之前我们讲过:
每个注解都有各自的使用场景,此处只有 @Controller 才能实现加载并注册的功能,不能用其他四个替代!
package com.example.demo.controller;import org.springframework.stereotype.Controller;import org.springframework.web.bind.annotation.RequestMapping;@Controller@RequestMapping("/web")public class WebController { @RequestMapping(value = "/hi") public Object sayHi() { return "Hi,Spring MVC"; }}
重新启动并访问 http://localhost:8080/web/hi,依然报错:
为什么呢?
因为最开始咱们介绍 MVC 时就说过,“V” 代表 “视图 (View)”,但此时我们返回的并不是一个 HTML 页面…
所以需要再加上一个注解 @ResponseBody,表示返回的仅仅是响应正文,而不是一个页面:
(或者使用组合注解 @RestController,表示 @Controller + @ResponseBody)
@ResponseBody 既可以修饰类,又可以修饰方法。修饰类:类中所有方法返回的都不是静态页面;修饰方法:这一个方法返回的不是静态页面。
package com.example.demo.controller;import org.springframework.stereotype.Controller;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.ResponseBody;@Controller@ResponseBody@RequestMapping("/web")public class WebController { @RequestMapping(value = "/hi") public Object sayHi() { return "Hi,Spring MVC"; }}
顺利访问了 ~~
如今大都是“前后端分离”,所以返回“视图” (HTML 页面) 的情况并不多。
@RequestMapping 处理的是什么方法的HTTP请求呢?
经过使用 Postman 测试,2.6.3 版本之后,@RequestMapping 默认情况下对于各种类型的 HTTP 请求均可以处理!
指定 GET/POST 方法类型:
- 设置 method:
@RequestMapping(value = "/hi",method= RequestMethod.POST)
(RequestMethod.POST 是枚举的值)
此时只能接收 POST 请求了! - 使用 @GetMapping 和 PostMapping:
@PostMapping(value = "/hi")
使用这两个注解替代 @RequestMapping。
三、获取参数
3.1 使用 Servlet API
前面我们讲了:Spring Web MVC 是基于 Servlet API 构建的原始 Web 框架。所以 Servlet 学的那些在 Spring MVC 这里同样适用:getParameter()
方法!
对于每个方法来说,它有两个默认的内置的参数:HttpServletRequest 和 HttpServletResponse。
可以随时得到他们,直接在参数里写了就行,然后我们就可以使用 Servlet 的方式获取参数了!
详细讲解博客链接:https://blog.csdn.net/yyhgo_/article/details/128494852?spm=1001.2014.3001.5501
缺点:
1)需要先声明出 HttpServletRequest 和 HttpServletResponse 参数。
2)返回的数据类型永远是 String,假如想要使用 int 等类型,需要先进行强转,可能会出现空指针异常。
3.2 通过方法参数直接拿到
3.2.1 传递单个参数
在 Spring MVC 中可以直接⽤⽅法中的参数来实现传参,比如以下代码:
@RequestMapping("/get1") public String getParam1(Integer age) { return "value:" + age; }
通过 http://localhost:8080/web/get1?age=5 访问:
参数名一定要对应!
这里为什么不使用 int,而使用 Integer 呢?
若我们在输入 URL 时没有传入对应的参数,使用 Integer 时会输出 null;而使用 int 时会直接报错!
因为 int 是基本数据类型,并不会有 null 值,而 Integer 是包装类 ~~
3.2.2 传递多个参数
@RequestMapping("/get2") public String getParam2(String name, Integer age) { // 执行到此行已经得到了前端传递参数的值,就可以随意使用了 return "name:" + name + " | age:" + age; // 返回的顺序无所谓 }
参数的先后顺序没有影响,因为是根据参数名来拿的 ~~
访问 http://localhost:8080/web/get2?age=7&name=yyh:
3.2.3 对象接收
当参数很多时,以上方法不利于修改和维护,这时我们可以传递对象!
首先要封装一个对象:
package com.example.demo.model;import lombok.Data;@Datapublic class Student { private Integer id; private String name; private Integer age; private String sex; private String classname; // ....}
@RequestMapping("/get3") public String getParam3(Student student) { return student.toString(); }
访问 localhost:8080/web/get3?id=3&name=张三&age=18&classname=六三:
(前端依然正常传递参数,后端拿到后会 setter 到对象的属性中)
Student 就是一个普通的实体类,不需要做特殊的处理。属性名一定要对应!
3.2.4 form 表单参数传递
根据场景选择 多个参数接收 / 对象接收。
3.2.5 ajax 参数传递
同 form 表单参数传递。
举个登录的例子:
@RequestMapping("/login2") public HashMap<String, Object> login2(String name, String password) { HashMap<String, Object> result = new HashMap<>(); result.put("name", name); result.put("password", password); return result; // 业务代码先省略~ }
这里我们让服务器返回的是一个 HashMap,响应就会自动解析成 json 格式的数据!
(可以通过 抓包查看 /console.dir()
后在浏览器控制台查看)
3.2.6 传递JSON对象 – @RequestBody
{"id":1,"name":"java","password":"javapwd"}
后端接收代码:
package com.example.demo.model;import lombok.Data;@Datapublic class Person { private Integer id; private String name; private String password;}
@RequestMapping(value = "/login1", method = RequestMethod.POST) public Object method_5(@RequestBody Person person) { HashMap<String, Object> result = new HashMap<>(); result.put("id", person.getId()); result.put("name", person.getName()); result.put("password", person.getPassword()); return result; // 业务代码先省略~ }
通过 http://localhost:8080/web/login1 ,Send 后:
由此:
1)当传递 json 对象时,接收参数必须是一个对象,且前面必须加上 @RequestBody 注解。
2)返回一个 HashMap 对象时,响应就会自动解析成 json 格式的数据。
3.2.7 上传文件 – @RequestPart
@RequestMapping("/reg") public String reg2(String name, @RequestPart("myfile") MultipartFile file) throws IOException { // 保存文件 file.transferTo(new File("C:\\yyhjava_project\\tmp\\yyh.jpg")); return "success"; }
上传文件加上
@RequestPart("xxx")
注解!!!
同样可以使用 Postman:(或者构造一个 form 表单)
此时查看我的保存路径下:
成功保存在服务器端了 ~
其他类型的文件如MP3、MP4格式…的也可以传递,这时可以拼接一下:
@RequestMapping("/param") public String param(String name, @RequestPart("myfile") MultipartFile file) throws IOException { // 获取⽂件后缀名 String fileName = file.getOriginalFilename().substring(file.getOriginalFilename().lastIndexOf(".")); // ⽂件保存地址 String filePath = ClassUtils.getDefaultClassLoader().getResource("static").getPath() + "/" + UUID.randomUUID() + fileName; // 保存⽂件 file.transferTo(new File(filePath)); return filePath + " 上传成功."; }
获取项目目录的几种方式:
ResourceUtils.getFile(ResourceUtils.CLASSPATH_URL_PREFIX).getPath();new ClassPathResource("").getFile().getAbsolutePath();ClassUtils.getDefaultClassLoader().getResource("").getPath();ResourceUtils.getFile("classpath:static/").getPath();
3.2.8 获取Cookie/Session/header
使用 Servlet API
Servlet API 详细讲解博客链接:https://blog.csdn.net/yyhgo_/article/details/128494852?spm=1001.2014.3001.5501
Cookie/Session 详细讲解博客链接:https://blog.csdn.net/yyhgo_/article/details/128511187?spm=1001.2014.3001.5501
简洁获取Cookie – @CookieValue
@RequestMapping("/getck") public String getCookie(@CookieValue("yyh") String bite,@CookieValue("java") String java) { return "yyh:" + bite + " |java:" + java; }
验证:
首先访问路由 http://localhost:8080/web/getck:
此时没有 “yyh” 和 “java” 这两个 Cookie,所以报错:
这时打开开发者工具设置 Cookie:
设置完成后重新刷新页面,就可以得到:
默认设置的 Cookie 是在内存中存储的,并没有持久化。可以通过一些方式来实现持久化 ~
简洁获取Header – @RequestHeader
@RequestMapping("/get_header") public String getHead(@RequestHeader("User-Agent") String userAgent) { return "User-Agent:" + userAgent; }
访问 http://localhost:8080/web/get_header:
Session 存储和获取 – @SessionAttribute(获取)
Session 存储和 Servlet 类似,如下代码所示:
@RequestMapping("/setsess") public String setsess(HttpServletRequest request) { // 获取 HttpSession 对象,参数设置为 true 表示如果没有 session 对象就创建⼀个 session HttpSession session = request.getSession(true); if(session!=null){ session.setAttribute("username","java"); } return "session 存储成功"; }
获取 Session 可以使用 HttpServletRequest,如下代码所示:
@RequestMapping("/sess1") public String sess1(HttpServletRequest request) { // 如果 session 不存在,不会⾃动创建 HttpSession session = request.getSession(false); String username = "暂⽆"; if(session!=null && session.getAttribute("username")!=null){ username = (String) session.getAttribute("username"); } return "username:"+username; }
获取 Session 更简洁的方式:
@RequestMapping("/sess2") public String sess2(@SessionAttribute(value = "username",required = false) String username) { return "username:"+username; }
required 参数 :(“是否必需”)
- 不设置默认为 true:session 不存在的话就会报错;
- 设置为 false:session 不存在也不会报错,而是返回 null ~
验证:
1)访问 http://localhost:8080/web/setsess:
此时多了一个名为 JSESSIONID 的 Cookie。会话建立了 ~
2)访问 http://localhost:8080/web/sess1:
3)访问 http://localhost:8080/web/sess2:
都能成功获取会话!
若我们在控制台修改了 JSESSIONID 的 value,是无法正确访问到会话的!:
3.3 扩充知识点
3.3.1 后端参数重命名 (后端参数映射)
某些情况下,前端传递的参数 key 和我们后端接收的 key 不⼀致,这样就会导致参数接收不到。如果出现了这种情况,我们就可以使用 @RequestParam 来重命名前后端的参数值。
后端代码示例:
@RequestMapping("/gettime") public String getTime(@RequestParam(value = "t", required = false) String time) { return "time:" + time; }
访问 http://localhost:8080/web/gettime?t=2023:
required 参数起着同样的效果 ~
源码:
3.3.2 获取URL中参数 – @PathVariable
一些特殊场景可能需要直接在URL中传递参数 (不在query string中)!
例如想让网站在搜索引擎中展现的优先级高一些。
后端实现代码:
@RequestMapping("/login4/{name}/{password}") public String login4(@PathVariable("name") String username, @PathVariable String password) { return "username:" + username + " | password:" + password; }
@PathVariable 中设置参数也相当于重命名了。
访问 http://localhost:8080/web/login4/yyh/632:
成功获取到了URL中的参数 ~
四、返回数据
通过上⾯的学习我们知道,默认情况下无论是 Spring MVC 还是 Spring Boot 返回的是视图 (xxx.html),而现在都是前后端分离的,后端只需要返回给前端数据即可,这个时候我们就需要使用 @ResponseBody 注解了。
4.1 返回静态页面
创建前端页面 index.html:
创建控制器 controller:
package com.example.demo.controller;import org.springframework.stereotype.Controller;import org.springframework.web.bind.annotation.RequestMapping;@Controller@RequestMapping("/p")public class PersonController { @RequestMapping("/index") public Object index(){ // 执⾏业务... // 返回view -> index.html return "/index.html"; }}
访问 http://localhost:8080/p/index 返回 index.html 页面 ~
4.2 返回 text/html
@RequestMapping("/m7") @ResponseBody public String method_7() { return "Hello,HTML~
"; }
访问 http://localhost:8080/p/m7:
Fiddler 抓包:(响应)
4.3 返回 JSON 对象
@RequestMapping("/m8") @ResponseBody public HashMap<String, String> method_8() { HashMap<String, String> map = new HashMap<>(); map.put("Java", "Java Value"); map.put("MySQL", "MySQL Value"); map.put("Redis", "Redis Value"); return map; }
访问 http://localhost:8080/p/m8:
Fiddler 抓包:(响应)
4.4 请求转发或请求重定向
现在很多场景的转发/重定向都由前端来完成了 ~
forward VS redirect
return 不但可以返回⼀个视图,还可以实现跳转,跳转的⽅式有两种:
- forward:请求转发
- redirect:请求重定向
请求转发和重定向的使⽤对比:
// 请求转发 @RequestMapping("/index2") public String index2(){ return "forward:/index.html"; }
// 请求重定向 @RequestMapping("/index") public String index(){ return "redirect:/index.html"; }
“转发” 和 “重定向” 理解:转发是服务器帮转的;而重定向是让浏览器重新请求另⼀个地址。
forward 和 redirect 具体区别如下:
- 请求重定向(redirect)将请求重新定位到资源;请求转发(forward)服务器端转发。
- 请求重定向地址发生变化;请求转发地址不发生变化。
- 请求重定向与直接访问新地址效果⼀致,不存在原来的外部资源不能访问;请求转发服务器端转发有可能造成原外部资源不能访问。
4.5 @ResponseBody 补充说明
- @ResponseBody 返回的值如果是字符串会转换成 text/html,如果返回的是对象会转换成 application/json 返回给前端。
- @ResponseBody 可以⽤来修饰方法或者是修饰类,修饰类表示类中的所有方法都会返回 html 或者 json,而不是视图。
- @RestController = @Controller + @ResponseBody
查看更多注解,官方API:
https://docs.spring.io/spring-framework/docs/current/reference/html/web.html