❤️❤️❤️Spring专栏更新中,各位大佬觉得写得不错,支持一下,感谢了!❤️❤️❤️ Spring + Spring MVC + MyBatis专栏
前篇我们讲了Spring日志,知道了日志的作用,日志怎么用以及通过lombok去进行更简单的日志输出,然后我们就基本讲完了Spring 相关知识,现在进入SpringMVC的学习。
官方对于 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". Spring Web MVC是基于Servlet API的原始Web框架,从一开始就包含在Spring框架中。其正式名称“Spring Web MVC”来自其源模块(Spring -webmvc)的名称,但更常见的名称是“Spring MVC”。
Spring Web MVC :: Spring Framework
从上述官方定义的描述我们可以提取两个关键信息:
然而要真正的理解什么是 Spring MVC?我们首先要搞清楚什么是 MVC?
MVC 是 Model View Controller 的缩写,它是软件⼯程中的⼀种软件架构模式,它把软件系统分为模型、视图和控制器三个基本部分
Spring MVC是基于MVC模式的Java Web开发框架,是Spring框架的一部分。Spring MVC提供了一种结构良好的开发模式,使得开发人员能够更好地组织和管理代码。它使用了MVC的概念,将应用程序的逻辑分离为模型、视图和控制器,并提供了一些额外的功能,如请求处理、表单验证、数据绑定等。是⼀个实现了 MVC 模式,并继承了 Servlet API 的 Web 框架,当⽤户在浏览器中输⼊了 url 之后,我们的 Spring MVC 项目就可以感知到用户的请求。
因此,可以说Spring MVC是基于MVC模式的一种实现方式,它将MVC的概念应用于Web开发,并提供了一些与Web开发相关的功能和特性。
现在绝大部分的 Java 项目都是基于 Spring(或 Spring Boot)的,而 Spring 的核心就是 Spring MVC。也就是说 Spring MVC 是 Spring 框架的核心模块,而 Spring Boot 是 Spring 的脚手架,因此 我们可以推断出,现在市⾯上绝⼤部分的 Java 项目约等于 Spring MVC 项⽬,这是我们要学 Spring MVC 的原因。
在创建 Spring Boot 项⽬时,我们勾选的 Spring Web 框架其实就是 Spring MVC 框架,如下图所示:
简单来说,咱们之所以要学习 Spring MVC 是因为它是⼀切项目的基础,我们以后创建的所有 Spring、Spring Boot 项目基本都是基于 Spring MVC 的。
学习 Spring MVC 我们只需要掌握以下 3 个功能:
对于 Spring MVC 来说,掌握了以上 3 个功能就相当于掌握了 Spring MVC。
Spring MVC 项目创建和 Spring Boot 创建项目相同(Spring MVC 使用 Spring Boot 的方式创建), 在创建的时候选择 Spring Web 就相当于创建了 Spring MVC 的项目。 在 Spring MVC 中使用 @RequestMapping 来实现 URL 路由映射,也就是浏览器连接程序的作用。
创建步骤:
创建之后,初始化完成:
接下来,创建⼀个 TextController 类,实现⽤户到 Spring 程序的互联互通,具体实现代码如下:
package com.example.mvcdemo.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller//让框架启动的时候加载当前类(只有加载的类,别人才能使用[访问])
@ResponseBody//告诉程序我返回的是一个数据而非页面
@RequestMapping("/text")//路由注册
public class TextController {
@RequestMapping("/hi")//路由注册
public String sayHi(){
return "Hi,Spring MVC";
}
}
运行起来:
也可以使用@RestController代替@Controller+@ResponseBody
package com.example.mvcdemo.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
/*@Controller//让框架启动的时候加载当前类(只有加载的类,别人才能使用[访问])
@ResponseBody//告诉程序我返回的是一个数据而非页面*/
@RestController//@Controller+@ResponseBody
@RequestMapping("/text")//路由注册
public class TextController {
@RequestMapping("/hi")//路由注册
public String sayHi(){
return "Hi,Spring MVC";
}
}
@RequestMapping 是 Spring Web 应用程序中最常被用到的注解之⼀,它是用来注册接口的路由映射的。
路由映射:所谓的路由映射指的是,当⽤户访问⼀个 url 时,将⽤户的请求对应到程序中某个类的某个方法的过程。
@RequestMapping 即可修饰类,也可以修饰方法,当修饰类和方法时,访问的地址是类 + 方法。
@RequestMapping 也可以直接修饰方法,代码实现如下:
package com.example.mvcdemo.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
/*@Controller//让框架启动的时候加载当前类(只有加载的类,别人才能使用[访问])
@ResponseBody//告诉程序我返回的是一个数据而非页面*/
@RestController//@Controller+@ResponseBody
/*@RequestMapping("/text")//路由注册*/
public class TextController {
@RequestMapping("/hi")//路由注册
public String sayHi(){
return "Hi,Spring MVC";
}
}
代码运行:
我们先在浏览器看一下这是post请求还是get请求?
可以看到这是get请求。
下面使用 PostMan 测试⼀下,默认情况下使用注解 @RequestMapping 是否可以接收 GET 或 POST 请求?
我们测试之后发现在现在版本的@RequestMapping 既支持GET方式的请求有支持PSOT方式的请求。
GET:
POST:
下面这篇文章讲解的十分详细了:
面试突击71:GET 和 POST 有什么区别? - 掘金 (juejin.cn)
package com.example.mvcdemo.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
/*@Controller//让框架启动的时候加载当前类(只有加载的类,别人才能使用[访问])
@ResponseBody//告诉程序我返回的是一个数据而非页面*/
@RestController//@Controller+@ResponseBody
/*@RequestMapping("/text")//路由注册*/
public class TextController {
@RequestMapping(value = "/hi",method= RequestMethod.POST)//路由注册
public String sayHi(){
return "Hi,Spring MVC";
}
}
我们打开浏览器发现报错了,难道代码有问题?我们使用Postman看看
GET:
使用GET请求仍然报405,使用POST请求试试,发现可以访问:
这是因为我们设置了方法为POST,即只能使用POST请求去访问。我们的浏览器之前已经看了。它是GET请求。
// 写法1
@RequestMapping("/hi")
// 写法2
@RequestMapping(value = "/hi",method = RequestMethod.GET)
// 写法3
@GetMapping("/hi")
// 写法1
@RequestMapping(value = "/hi",method = RequestMethod.POST)
// 写法2
@PostMapping("/hi")
在 Spring MVC 中可以直接用方法中的参数来实现传参,比如以下代码:
package com.example.mvcdemo.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
/*@Controller//让框架启动的时候加载当前类(只有加载的类,别人才能使用[访问])
@ResponseBody//告诉程序我返回的是一个数据而非页面*/
@RestController//@Controller+@ResponseBody
/*@RequestMapping("/text")//路由注册*/
public class TextController {
/*@RequestMapping(value = "/hi",method= RequestMethod.POST)//路由注册*/
/*@PostMapping("/hi")*/
@GetMapping("/hi")
public String sayHi(String name){
return "Hi"+name;
}
}
不加参数,直接输出null
添加name参数,输出:Hi 张三
浏览器也是一样:
如果我们参数名错误(不同)则传递不成功:
如果我们传递了多个参数,其中有所需要的(参数名字相同),那它会自动匹配:
多个参数也是一样传递,而且我们通过上面,也发现了参数的顺序并不影响参数的传递。 总结说明:当有多个参数时,前后端进行参数匹配时,是以参数的名称进行匹配的,因此参数的位置 是不影响后端获取参数的结果
当参数个数过多时,可以进行传递对象,将参数封装成一个类。
Person对象
package com.example.mvcdemo.controller;
import lombok.Data;
@Data
public class Person {
private int id;
private String name;
private String password;
}
TextController
package com.example.mvcdemo.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
/*@Controller//让框架启动的时候加载当前类(只有加载的类,别人才能使用[访问])
@ResponseBody//告诉程序我返回的是一个数据而非页面*/
@RestController//@Controller+@ResponseBody
/*@RequestMapping("/text")//路由注册*/
public class TextController {
/*@RequestMapping(value = "/hi",method= RequestMethod.POST)//路由注册*/
/*@PostMapping("/hi")*/
@GetMapping("/hi1")
public String sayHi1(Person p){
return "Hi "+p.getId()+" "+p.getName()+" "+p.getPassword();
}
}
运行代码+传递参数:
在Spring Boot(Spring MVC)中传参一定要是包装类型,而非基础类型。
package com.example.mvcdemo.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
/*@Controller//让框架启动的时候加载当前类(只有加载的类,别人才能使用[访问])
@ResponseBody//告诉程序我返回的是一个数据而非页面*/
@RestController//@Controller+@ResponseBody
/*@RequestMapping("/text")//路由注册*/
public class TextController {
/*@RequestMapping(value = "/hi",method= RequestMethod.POST)//路由注册*/
/*@PostMapping("/hi")*/
@GetMapping("/num")
public String sayHi2(int num){
return "num="+num;
}
}
我们先正常传递参数:
但是如果我们忘记传递或是没有传递(前后端工作人员沟通不及时时),则会报错,而且这是非常严重的。
这个时候就很莫名其妙,也找不到错误。
如果我们将int换成它的包装类时:
正常传递参数可以正常显示:
没有传递参数时它则会显示null,这就会很明显发现错误的来源。
所以注意:参数类型应该设置为包装类
某些特殊的情况下,前端传递的参数 key 和我们后端接收的 key 可以不⼀致,比如前端传递了⼀个 time 给后端,而后端又是用 createtime 字段来接收的,这样就会出现参数接收不到的情况,如果出现 这种情况,我们就可以使用 @RequestParam 来重命名前后端的参数值。
具体示例如下,后端实现代码:
@RequestMapping("/m4")
public Object method_4(@RequestParam("time") String createtime) {
System.out.println("时间:" + createtime);
return "Hi "+createtime;
}
代码运行:
这就说明参数的重命名生效了。
还有需要注意的是使用了@RequestParam(),则这个参数是必须要传递的,我们可以看@RequestParam()源码:
没有传递参数时:
所以当这个重命名参数是非必传参数时,我们可以设置@RequestParam 中的required=false 来避免不传递时报错
@RequestMapping("/m4")
public Object method_4(@RequestParam(value = "time", required = false) String createtime) {
System.out.println("时间:" + createtime);
return "Hi "+createtime;
}+ createtime);
我们先来试试看接受对象的是否可以接收JSON对象:
@GetMapping("/hi1")
public String sayHi1(@RequestBody Person p){
return p.toString();
}
使用Postman 传递JSON对象
传递的是 0 null null ,就发现传递不了。那我们传递JSON对象时应该任何传递?
使用@RequestBody 注解。
@PostMapping("/hi1")
public String sayHi1(@RequestBody Person p){
return "Hi "+p.getId()+" "+p.getName()+" "+p.getPassword();
}
不过当@RequestBody传递JSON格式对象时需要配合PostMapping一起使用,因为@RequestBody传递JSON格式对象时是Post类型传参。
Postman:
后端实现代码:
@PostMapping("/m6/{name}/{password}")
public Object method_6(@PathVariable String name, @PathVariable String password) {
return "name:" + name+" password:" + password;
}
这样写就很简洁,SEO(Search Engine Optimization 是指搜索引擎优化)效果很好。 注意事项:@PostMapping("/m6/{name}/{password}") 中的 {password} 参数不能省略。 这是因为在Spring的路径映射中使用了占位符(即{})来表示可变的路径段。 /m6/{name}/{password}中的{name}和{password}都是路径变量,它们需要被具体的值替代才能匹配相应的请求路径。 参数的位置和个数都必须保持一致。
@RequestMapping("/m9")
public String upFile(@RequestPart("myfile") MultipartFile file) throws IOException {
// ⽂件保存地址
String filePath = "C:\\Users\\lin\\Pictures\\JiangHai\\11.png";
// 保存⽂件
file.transferTo(new File(filePath));
return filePath + " 上传成功.";
}
文件夹什么都没有:
使用Postman进行上传文件:
随便选择一张图片(文件名为myFile)
上传成功:
我们也可以打开这张图片
但是我们发现我们把路径定死了,这在实际开发中是不可能的,那我们现在来写一个最终版的文件上传:
@RequestMapping("/upfile")
public String myUpFile(@RequestPart("myupfile") MultipartFile file) throws IOException {
//根目录
String path ="C:\\Users\\lin\\Pictures\\JiangHai\\";
//根目录+唯一文件名(进行随机取名)
path+=UUID.randomUUID().toString();
//根目录+唯一文件名+文件的后缀 ex: aaa.aaa.png
path+=file.getOriginalFilename().substring(file.getOriginalFilename().lastIndexOf("."));
file.transferTo(new File(path));
return path + " 上传成功";
}
再随便上传一张图片
我们也可以上传一个.txt文件:
上传成功:
注意:字段myfile允许的最大大小为1048576字节(即上传的文件是有大小限制的)
//Spring MVC(Spring Web) 内置了HttpServletRequest 和 HttpServletResponse
@RequestMapping("/getparam")
public String param10(HttpServletRequest request) {
return request.getParameter("name");
}
通过获取Request对象获取参数:
@RequestMapping("/getck")
public String getCookie( HttpServletRequest request) {
// 获取所有 cookie 信息
Cookie[] cookies = request.getCookies();
for(Cookie item:cookies){
log.error(item.getName()+" "+item.getValue());
//log的使用需要添加@Slf4j注解
}
return "get cookie!";
}
打开浏览器开发人员工具(F12) ,手动添加结果Cookie:
浏览器访问 localhost:8080/getck
控制台就会将我们的Cookie打印出来:
上面我们是获取全部Cookie,我们也可以去获取指定的Cookie:
@RequestMapping("/getck2")
public String getCookie2(@CookieValue("zhangsan") String val) {
return "Cookie Value: "+val;
}
明明你在请求时没有加Cookie,为什么可以拿到呢?这是因为浏览器,浏览器自己实现了这个机制,浏览器会在你每一次访问网站时,将这个网站的所以Cookie传送给你的后端。 可以看下面:
为什么浏览器会去实现这个机制呢?
是因为HTTP协议是一种无状态协议,服务器无法知道用户之前的状态信息。 为了解决这个问题,Web应用使用了Cookie机制来跟踪和记录用户的状态。当用户首次访问一个网站时,服务器会在响应中设置一个或多个Cookie,并将它们发送到用户的浏览器。浏览器会将这些Cookie保存起来。 随后,当用户再次访问同一网站时,浏览器会将之前保存的Cookie附加到请求中,然后发送给服务器。这样,服务器可以通过读取Cookie中的信息来识别并恢复用户的状态,例如登录信息、用户偏好等。 因此,浏览器在每次访问网站时将所有与该网站相关的Cookie传送给后端,以便服务器能够根据这些Cookie识别用户并提供相应的服务。 需要注意的是,浏览器会根据Cookie的设置规则来决定是否发送Cookie,例如Cookie的过期时间、域名限制等。
@RequestMapping("/header")
public String header(@RequestHeader("User-Agent") String userAgent) {
return "userAgent:"+userAgent;
}
浏览器:
Session 存储和 Servlet 类似,是使⽤ HttpServletRequest 中获取的,如下代码所示
@RequestMapping("/setsess")
public String setsess(HttpServletRequest request) {
// 获取 HttpSession 对象,参数设置为 true 表示如果没有 session 对象就创建⼀个session
HttpSession session = request.getSession(true);
if(session!=null){
session.setAttribute("username","username");
}
return "session 存储成功";
}
读Session①
//读Session1
@RequestMapping("/getsess")
public String sess(HttpServletRequest request) {
// 如果 session 不存在,不会⾃动创建
HttpSession session = request.getSession(false);
String username = "暂⽆";
if(session!=null && session.getAttribute("username")!=null){
username = (String) session.getAttribute("username");
return (String)session.getAttribute("username");
}
return "暂无Session信息!";
}
读Session②(更简洁的方式)
//读Session2
@RequestMapping("/getsess2")
public String sess2(@SessionAttribute(value = "username",required = false)
String username) {
return "username:"+username;
}
我们先去读Session,可以发现浏览器显示:暂无Session信息!
然后我们去存储Session:
再去读取Session就有了:
当然Session是默认存在内存中的,如果当我们程序重新启动时,就没了,这是因为内存中的数据不具有持久性,无法跨越程序重启的边界。
创建前端页面 hello.html
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>hello,spring mvc</title>
<script src="index.js"></script>
</head>
<body>
<h1>Hello,Spring MVC.</h1>
</body>
</html>
创建控制器 RespController:
package com.example.mvcdemo.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
@RequestMapping("/resp")
public class RespController {
@RequestMapping("/hi")
public String sayHi(String name){
return "/hello.html";
}
}
如果我们加上@ResponseBody,则显示:
@ResponseBody
@RequestMapping("/m8")
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;
}
当你使用字典(键值对)类型时,Spring MVC会自动将其转换成JSON对象
forward VS redirect
return 不但可以返回⼀个视图,还可以实现跳转,跳转的方式有两种:
请求转发和重定向的使用对比:
// 请求重定向
@RequestMapping("/hello")
public String hello(){
return "redirect:/hello.html";
}
// 请求转发
@RequestMapping("/hello2")
public String hello2(){
return "forward:/hello.html";
}
forward(请求转发)和 redirect(请求重定向)的区别,举例来说,例如,你告诉你妈妈,你想吃辣条,如果你妈妈,说好,我帮你去买,这就是 forward 请求转发;如果你妈妈让你自己去买,那么就是请求 redirect 重定向。
“转发”和“重定向”理解:在中国官⽅发布的内容越少事也越大, “转发”和“重定向”也是⼀样:字越少,责任越大 。转发是服务器帮转的,而重定向是让浏览器重新请求另⼀个地址。
forward 和 redirect 具体区别如下:
请求转发如果资源和转发的页面不在⼀个目录下,会导致外部资源不可访问 。