前言🍭
前篇我们讲了Spring日志,知道了日志的作用,日志怎么用以及通过lombok去进行更简单的日志输出,然后我们就基本讲完了Spring 相关知识,现在进入SpringMVC的学习。
一、什么是SpringMVC🍭
官方对于 Spring MVC 的描述是这样的:
Spring Web MVC :: Spring Framework
从上述官方定义的描述我们可以提取两个关键信息:
- Spring MVC 是⼀个 Web 框架。
- Spring MVC 是基于 Servlet API 构建的。
然而要真正的理解什么是 Spring MVC?我们首先要搞清楚什么是 MVC?
1、什么是MVC?🍉
MVC 是 Model View Controller 的缩写,它是软件⼯程中的⼀种软件架构模式,它把软件系统分为模型、视图和控制器三个基本部分
- Model(模型)是应用程序中用于处理应⽤程序数据逻辑的部分。通常模型对象负责在数据库中存取数据。
- View(视图)是应用程序中处理数据显示的部分。通常视图是依据模型数据创建的。
- Controller(控制器)是应用程序中处理用户交互的部分。通常控制器负责从视图读取数据, 控制用户输入,并向模型发送数据。
2、MVC 和 Spring MVC 的关系🍉
Spring MVC是基于MVC模式的Java Web开发框架,是Spring框架的一部分。Spring MVC提供了一种结构良好的开发模式,使得开发人员能够更好地组织和管理代码。它使用了MVC的概念,将应用程序的逻辑分离为模型、视图和控制器,并提供了一些额外的功能,如请求处理、表单验证、数据绑定等。是⼀个实现了 MVC 模式,并继承了 Servlet API 的 Web 框架,当⽤户在浏览器中输⼊了 url 之后,我们的 Spring MVC 项目就可以感知到用户的请求。
因此,可以说Spring MVC是基于MVC模式的一种实现方式,它将MVC的概念应用于Web开发,并提供了一些与Web开发相关的功能和特性。
二、为什么要学 Spring MVC?🍭
现在绝大部分的 Java 项目都是基于 Spring(或 Spring Boot)的,而 Spring 的核心就是 Spring MVC。也就是说 Spring MVC 是 Spring 框架的核心模块,而 Spring Boot 是 Spring 的脚手架,因此 我们可以推断出,现在市⾯上绝⼤部分的 Java 项目约等于 Spring MVC 项⽬,这是我们要学 Spring MVC 的原因。
SpringMVC的优点:🍓
1. 轻量级:Spring MVC是一个轻量级的框架,它只提供了基本的Web开发功能,没有过多的冗余功能,使得应用程序的开发和部署更加高效。
2. 灵活性:Spring MVC采用了基于注解的配置方式,使得开发者可以更灵活地定义控制器、请求映射和视图解析等,极大地简化了开发过程。
3. 松耦合:Spring MVC采用了MVC设计模式,将应用程序的不同层次分离开来,使得各个模块之间的耦合度降低,提高了代码的可维护性和可测试性。
4. 可扩展性:Spring MVC提供了丰富的扩展点和插件机制,开发者可以根据自己的需求进行扩展和定制,满足各种复杂的业务需求。
5. 高度集成:Spring MVC与Spring框架紧密集成,可以很容易地与其他Spring组件(如Spring Boot、Spring Security等)进行集成,提供了更完整的解决方案。
6. 强大的视图解析能力:Spring MVC提供了多种视图解析器,支持多种视图技术(如JSP、Thymeleaf、Freemarker等),使得开发者可以根据自己的喜好选择合适的视图技术。
7. 易于测试:Spring MVC采用了面向接口的编程方式,使得控制器和服务层的代码可以很容易地进行单元测试,提高了代码的质量和稳定性。
在创建 Spring Boot 项⽬时,我们勾选的 Spring Web 框架其实就是 Spring MVC 框架,如下图所 示:
简单来说,咱们之所以要学习 Spring MVC 是因为它是⼀切项目的基础,我们以后创建的所有 Spring、Spring Boot 项目基本都是基于 Spring MVC 的。
三、怎么学 Spring MVC?🍭
学习 Spring MVC 我们只需要掌握以下 3 个功能:
- 连接的功能:将用户(浏览器)和 Java 程序连接起来,也就是访问⼀个地址能够调用到我们的 Spring 程序。
- 获取参数的功能:用户访问的时候会带⼀些参数,在程序中要想办法获取到参数。
- 输出数据的功能:执行了业务逻辑之后,要把程序执行的结果返回给用户。
对于 Spring MVC 来说,掌握了以上 3 个功能就相当于掌握了 Spring MVC。
1、Spring MVC 创建和连接🍉
Spring MVC 项目创建和 Spring Boot 创建项目相同(Spring MVC 使用 Spring Boot 的方式创建), 在创建的时候选择 Spring Web 就相当于创建了 Spring MVC 的项目。 在 Spring MVC 中使用 @RequestMapping 来实现 URL 路由映射,也就是浏览器连接程序的作用。
Ⅰ、创建SpringMVC项目🍓
- 使用Maven方式传统的创建SpringMVC(不过这已经是过时的方法)。
- 使用Spring Boot添加Spring Web模块(Spring MVC)。
创建步骤:
创建之后,初始化完成:
接下来,创建⼀个 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 注解介绍🍓
@RequestMapping 是 Spring Web 应用程序中最常被用到的注解之⼀,它是用来注册接口的路由映射的。
@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";
}
}
代码运行:
Ⅲ、@RequestMapping 是 post 还是 get 请求?🍓
我们先在浏览器看一下这是post请求还是get请求?
可以看到这是get请求。
下面使用 PostMan 测试⼀下,默认情况下使用注解 @RequestMapping 是否可以接收 GET 或 POST 请求?
我们测试之后发现在现在版本的@RequestMapping 既支持GET方式的请求有支持PSOT方式的请求。
GET:
POST
GET方法和POST方法有什么区别🍒
下面这篇文章讲解的十分详细了:
面试突击71:GET 和 POST 有什么区别? - 掘金 (juejin.cn)
指定 GET/POST 方法类型🍒
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请求。
Ⅳ、@GetMapping 和 PostMapping🍓
get 请求的 3 种写法:🍒
// 写法1
@RequestMapping("/hi")
// 写法2
@RequestMapping(value = "/hi",method = RequestMethod.GET)
// 写法3
@GetMapping("/hi")
post 请求的 2 种写法:🍒
// 写法1
@RequestMapping(value = "/hi",method = RequestMethod.POST)
// 写法2
@PostMapping("/hi")
2、获取参数🍉
Ⅰ、传递单个/多个参数🍓
在 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
浏览器也是一样:
如果我们参数名错误(不同)则传递不成功:
如果我们传递了多个参数,其中有所需要的(参数名字相同),那它会自动匹配:
Ⅱ、传递对象🍓
当参数个数过多时,可以进行传递对象,将参数封装成一个类。
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();
}
}
运行代码+传递参数:
注意事项🍒
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()源码:
没有传递参数时:
@RequestMapping("/m4")
public Object method_4(@RequestParam(value = "time", required = false) String createtime) {
System.out.println("时间:" + createtime);
return "Hi "+createtime;
}+ createtime);
Ⅳ、@RequestBody 接收JSON对象🍓
我们先来试试看接受对象的是否可以接收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:
Ⅴ、获取URL中参数@PathVariable🍓
后端实现代码:
@PostMapping("/m6/{name}/{password}")
public Object method_6(@PathVariable String name, @PathVariable String password) {
return "name:" + name+" password:" + password;
}
Ⅵ、上传文件@RequestPart🍓
@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字节(即上传的文件是有大小限制的)
Ⅶ、获取Cookie/Session/header🍓
获取 Request 和 Response 对象 🍒
//Spring MVC(Spring Web) 内置了HttpServletRequest 和 HttpServletResponse
@RequestMapping("/getparam")
public String param10(HttpServletRequest request) {
return request.getParameter("name");
}
通过获取Request对象获取参数:
获取Cookie🍒
@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打印出来:
@RequestMapping("/getck2")
public String getCookie2(@CookieValue("zhangsan") String val) {
return "Cookie Value: "+val;
}
为什么浏览器会去实现这个机制呢?
简洁获取 Header—@RequestHeader🍒
@RequestMapping("/header")
public String header(@RequestHeader("User-Agent") String userAgent) {
return "userAgent:"+userAgent;
}
浏览器:
Session 存储和获取🍒
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;
}
3、返回数据🍉
Ⅰ、返回静态页面🍓
创建前端页面 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";
}
}
Ⅱ、返回text/html🍓
Ⅲ、返回 JSON 对象🍓
@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;
}
Ⅳ、请求转发或请求重定向🍓
forward VS redirect
return 不但可以返回⼀个视图,还可以实现跳转,跳转的方式有两种:
- forward :请求转发;
- redirect:请求重定向。
请求转发和重定向的使用对比:
// 请求重定向
@RequestMapping("/hello")
public String hello(){
return "redirect:/hello.html";
}
// 请求转发
@RequestMapping("/hello2")
public String hello2(){
return "forward:/hello.html";
}
forward 和 redirect 具体区别如下:
- 请求重定向(redirect)将请求重新定位到资源;请求转发(forward)服务器端转发。
- 请求重定向地址发⽣变化,请求转发地址不发⽣变化。
- 请求重定向与直接访问新地址效果⼀直,不存在原来的外部资源不能访问;请求转发服务器端转发有可能造成原外部资源不能访问。
请求转发如果资源和转发的页面不在⼀个目录下,会导致外部资源不可访问 。
四、送书活动🍭
Spring Cloud Alibaba核心技术与实战案例
本书特色:
不留遗漏:全面覆盖Dubbo核心知识点
直击要害:实战化案例精准定位技术细节
学以致用:精要式演示确保开发、学习不脱节
潜移默化:研磨式知识讲解渗透技术要点
提升效率:垂直式技术精讲不饶弯路
循序提升:渐进式知识点编排确保连贯
配套资源:赠送全书案例源文件助力学习
作者简介
高洪岩,某世界500强公司项目经理,有10年Java开发和项目管理经验,精通Java语言,擅长Java EE、分布式、微服务、高性能服务器架构、
智能报表、多线程和高并发相关的技术内容,理论与实践经验颇丰,也积极参与开源项目的开发与设计,涉及Dubbo、Jedis、Pulsar、ZooKeeper等主流开源项目。
著有《Java多线程编程核心技术》《Java并发编程:核心方法与框架》《NIO与Socket编程技术指南》《Java EE核心框架实战(第2版)》《Java Web实操》《虚拟化高性能NoSQL存储案例精粹:Redis+Docker》等多本图书。
京东链接:https://item.jd.com/14010448.html