官方描述:
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
翻译为中⽂:
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?
MVC 是 Model View Controller 的缩写,它是软件⼯程中的⼀种软件架构模式,它把软件系统分为模型、视图和控制器三个基本部分。
MVC 是⼀种思想,⽽ Spring MVC 是对 MVC 思想的具体实现。
总结来说,Spring MVC 是⼀个实现了 MVC 模式,并继承了 Servlet API 的 Web 框架。既然是 Web框架,那么当⽤户在浏览器中输⼊了 url 之后,我们的 Spring MVC 项⽬就可以感知到⽤户的请求。
现在绝⼤部分的 Java 项⽬都是基于 Spring(或 Spring Boot)的,⽽ Spring 的核⼼就是 Spring MVC。简单来说,咱们之所以要学习 Spring MVC 是因为它是⼀切项⽬的基础,我们以后创建的所有 Spring、Spring Boot 项⽬基本都是基于 Spring MVC 的。
学习完 Spring MVC 我们需要掌握以下 3 个功能:
1)连接的功能: 将⽤户(浏览器)和 Java 程序连接起来,也就是访问⼀个地址能够调⽤到我们的 Spring 程序。
2)获取参数的功能: ⽤户访问的时候会带⼀些参数,在程序中要想办法获取到参数。
3)输出数据的功能: 执⾏了业务逻辑之后,要把程序执⾏的结果返回给用户。
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
@RequestMapping 是 Spring Web 应⽤程序中最常被⽤到的注解之⼀,它是⽤来注册接⼝的路由映射的。
路由映射:所谓的路由映射指的是,当⽤户访问⼀个 url 时,将⽤户的请求对应到程序中某个类的某个⽅法的过程就叫路由映射。
@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 方法类型:
@RequestMapping(value = "/hi",method= RequestMethod.POST)
@PostMapping(value = "/hi")
前面我们讲了: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 等类型,需要先进行强转,可能会出现空指针异常。
在 Spring MVC 中可以直接⽤⽅法中的参数来实现传参,比如以下代码:
/**
* 获取单个参数
*
* @return
*/
@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 是包装类 ~~
/**
* 获取多个参数(2个及以上)
*
* @param name
* @param age
* @return
*/
@RequestMapping("/get2")
public String getParam2(String name, Integer age) {
// 执行到此行已经得到了前端传递参数的值,就可以随意使用了
return "name:" + name + " | age:" + age; // 返回的顺序无所谓
}
参数的先后顺序没有影响,因为是根据参数名来拿的 ~~
访问 http://localhost:8080/web/get2?age=7&name=yyh:
当参数很多时,以上方法不利于修改和维护,这时我们可以传递对象!
首先要封装一个对象:
package com.example.demo.model;
import lombok.Data;
@Data
public class Student {
private Integer id;
private String name;
private Integer age;
private String sex;
private String classname;
// ....
}
/**
* 获取参数类型为对象的 demo
*
* @param student
* @return
*/
@RequestMapping("/get3")
public String getParam3(Student student) {
return student.toString();
}
访问 localhost:8080/web/get3?id=3&name=张三&age=18&classname=六三:
(前端依然正常传递参数,后端拿到后会 setter 到对象的属性中)
根据场景选择 多个参数接收 / 对象接收。
同 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()
后在浏览器控制台查看)
{"id":1,"name":"java","password":"javapwd"}
后端接收代码:
package com.example.demo.model;
import lombok.Data;
@Data
public 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 格式的数据。
@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();
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
/**
* spring mvc 获取 cookie 值
*/
@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 里面的信息
*
* @param userAgent
* @return
*/
@RequestMapping("/get_header")
public String getHead(@RequestHeader("User-Agent") String userAgent) {
return "User-Agent:" + userAgent;
}
访问 http://localhost:8080/web/get_header:
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:
都能成功获取会话!
某些情况下,前端传递的参数 key 和我们后端接收的 key 不⼀致,这样就会导致参数接收不到。如果出现了这种情况,我们就可以使用 @RequestParam 来重命名前后端的参数值。
后端代码示例:
/**
* 后端参数重命名
*
* @param time
* @return
*/
@RequestMapping("/gettime")
public String getTime(@RequestParam(value = "t", required = false) String time) {
return "time:" + time;
}
访问 http://localhost:8080/web/gettime?t=2023:
一些特殊场景可能需要直接在URL中传递参数 (不在query string中)!
例如想让网站在搜索引擎中展现的优先级高一些。
后端实现代码:
/**
* 从 url 中获取参数
* @param username
* @param password
* @return
*/
@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 注解了。
创建前端页面 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 页面 ~
@RequestMapping("/m7")
@ResponseBody
public String method_7() {
return "Hello,HTML~
";
}
访问 http://localhost:8080/p/m7:
@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:
现在很多场景的转发/重定向都由前端来完成了 ~
forward VS redirect
return 不但可以返回⼀个视图,还可以实现跳转,跳转的⽅式有两种:
请求转发和重定向的使⽤对比:
// 请求转发
@RequestMapping("/index2")
public String index2(){
return "forward:/index.html";
}
// 请求重定向
@RequestMapping("/index")
public String index(){
return "redirect:/index.html";
}
“转发” 和 “重定向” 理解:转发是服务器帮转的;而重定向是让浏览器重新请求另⼀个地址。
forward 和 redirect 具体区别如下:
查看更多注解,官方API:
https://docs.spring.io/spring-framework/docs/current/reference/html/web.html#mvc-ann-requestmapping