目录
3.1.3 @RequestMapping是GET请求还是POST请求?
一、了解Servlet
要想了解Spring MVC,先理解下Servlet,Servlet是一种实现动态页面的技术,准确来讲Servlet是一套Java Web开发的规范,或者说是一套Java Web开发的技术标准。
二、什么是Spring MVC?
Spring MVC全程Spring Web MVC,它是基于Servlet API构建的原始Web框架,从一开始就包含在Spring框架中。它正式名称为Spring Web MVC,但它通常被称为Spring MVC,Spring MVC是一个WEB框架。
2.1 MVC定义
MVC是Model View Controller的缩写,它是软件工程中的一种软件架构设计模式,它把软件系统分为模式、视图和控制器三个基础部分。
- View(视图):指在应用程序中专门用来与浏览器进行交互,展示数据的资源
- Model(模型):是应用程序的主题部分,用来处理程序中数据逻辑的部分
- Controller(控制器):可以理解为一个分发器,用来决定对于视图发来的请求,需要用哪一个模型来处理,以及处理完后跳回哪一个视图
举例:
比如去饭店吃饭,客户进店之后,服务员来接待客户点餐,客户点餐之后,把客户菜单交给前厅,前厅根据客户菜单给后厨下达命令,后厨负责做饭,做完之后,再根据菜单告诉服务员,这是哪位客人的饭菜。
在这个过程中:
服务员就是View(视图):负责接待客户,帮助客人点餐以及给顾客端饭
前厅就是Controller(控制器):根据用户的点餐情况,来选择给哪个后厨下达命令
后厨就是Model(模型):根据前厅的要求完成客户的用餐需求
2.2 Spring MVC介绍
MVC是一种架构设计模式,也是一种思想,而Spring MVC是对MVC思想的具体实现,除此之外,Spring MVC还是一个Web框架。
总结来说,Spring Mvc是一个实现了MVC模式的Web框架。
其实,Spring MVC我们在前面已经用过了,在创建Spring Boot项目时,我们在勾选Spring Web框架其实就是Spring MVC框架:
这可能就有点懵了,前面创建的不是Spring Boot项目嘛?怎么又变成了Spring MVC项目了?它们之间到底有着什么样的联系?
Spring Boot只是实现Spring MVC的其中一种方式而已,Spring Boot可以添加很多依赖,借助这些依赖实现不同的功能,Spring Boot通过添加Spring Web MVC框架来实现web功能。
不过在Spring在实现MVC时,也结合自身项目的特点,做出了一些改变,相对而言,下面的图或许更适合一些。
比如上面的例子,去饭店吃饭,一些饭店是前厅来负责接待客户,帮助客户点餐,也就是Controller来负责接收用户的请求
再举一个例子:
比如你去面试公司,可能直接是面试团队来接待候选人,直接省去了HR中间的交接过程,再由面试团队挑选面试官对你进行面试,面试官再将面试情况返回给面试团队,再转交给HR,HR再通知你是否面试通过。
三、学习Spring MVC
学习Spring MVC,重点也就是学习如何通过浏览器和用户程序进行交互
主要分以下三个方面:
- 建立连接:将用户(浏览器)和Java程序连接起来,也就是访问一个地址能够调用到我们的Spring程序。
- 请求:用户请求的时候会带一些参数,在程序中要想办法获取到参数,所以请求这块主要是获取参数的功能。
- 响应:执行了业务逻辑之后,要把程序执行的结果返回给用户,也就是响应。
3.1 建立连接
在Spring MVC中使用@RequestMapping来实现URL路由映射,也就是浏览器连接程序的作用。
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class UserController {
@RequestMapping("/sayHi")
public String sayHi() {
return "hello, Spring MVC";
}
}
接下来访问:http://127.0.0.1:8080/sayHi,就可以看到程序返回的数据了
3.1.1 @RequestMapping注释介绍
@RequestMapping是Spring Web MVC应用程序中最常用的注解之一,它是用来注册接口的路由映射的。
表示服务收到请求时,路径为/sayHi的请求就会调用sayHi这个方法的代码。
路由映射:当用户访问一个URL时,将用户的请求对应到程序中某个类的某个方法的过程就叫路由映射。
既然@RequestMapping已经达到了我们的目的,为什么还需要加@RestController注解呢?
我把@RestController注解去掉,再访问一次:
可以看到,程序报了404,找不到页面
这就是@RestController起到的作用
是因为一个项目中会有很多类,每个类可能会有很多的方法,Spring程序怎么知道要执行哪个方法呢?
Spring会对所有的类进行扫描,如果类加了注解@RestController,Spring才会去看这个类里面的方法有没有加@RequestMapping这个注解,当然它的作用不止这一点,后续会详细介绍。
3.1.2 @RequestMapping使用
@RequestMapping既可修饰类,也可以修饰方法,当修饰类和方法时,访问的地址是类路径+方法路径。
- @RequestMapping标识一个类:设置映射请求的请求路径的初始信息
- @RequestMapping标识一个方法:设置映射请求请求路径的具体信息
为什么 @RequestMapping既可修饰类,也可以修饰方法?
点进去@RequestMapping源码进行查看
红色框中是它的元注解,表示这个注解既可修饰方法 ,也可修饰方法,具体后面介绍。
注意:
- 我们在写项目时,最好也用@RequestMapping注解修饰类,因为我们要是没加这个类注解的话,Spring在进行扫描的时候,不同的类中可能存在相同的修饰方法的@RequestMapping注解,这就会导致找不到究竟执行哪条命令。
- @RequestMapping的URL路径最前面加不加/都可以,Spirng程序启动时,会进行判断,如果前面没有加/,Spring会接上一个/
3.1.3 @RequestMapping是GET请求还是POST请求?
此处我们使用Postman来发送请求
1、发送GET请求
2、发送POST请求
通过上图发现,@RequestMapping既支持GET请求也支持POST请求。
3.2 请求
访问不同的路径,就是发送不同的请求,在发送请求时,可能会带一些参数,所以学习Spring的请求,主要学习如何传递参数到后端以及后端如何接收。
传递参数,此处使用Postman来模拟。
3.2.1 传递单个参数
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/param")
public class Param {
@RequestMapping("/m1")
public String param(String name) {
return "接到的参数name:" + name;
}
}
3.2.2 传递多个参数
@RequestMapping("/m2")
public String param2(String name, String password) {
return "接到的参数name:" + name + ", password:" + password;
}
注意:当有多个参数时,前后端进行参数匹配时,是以参数的名称进行匹配的,因此参数的位置是不影响后端参数的结果。
3.2.3 传递对象
如果参数比较多,方法声明就需要有很多形参,并且后续每次新增一个参数,也需要修改方法声明,我们不妨把这些参数封装成一个对象。
@RequestMapping("/m3")
public String param3(Person p) {
return p.toString();
}
3.2.4 后端参数重命名
@RequestMapping("/m4")
public String param4(@RequestParam("time") String createTime) {
return "接收到的参数createTime:" + createTime;
}
注意:
- 使用@RequestParam进行参数重命名时,请求参数只能和@RequestParam声明的名称一致,才能进行参数绑定和赋值。
- 使用@RequestParam进行参数重命名时,参数就变成了必传参数。
如果想注解的参数不是必传参数呢?
非必传参数设置
先来了解下参数必传的原因,我们查看@RequestParam注解的实现细节就可以发现端倪,注解实现如下:
可以看到required的默认值为true,表示的含义就是:该注解修饰的参数为必传 。
既然如此,我们可以用过设置@RequestParam中的required=false来避免不传递时报错,具体实现如下:
@RequestMapping("/m4")
public String param4(@RequestParam(value = "time", required = false) String createTime) {
return "接收到的参数createTime:" + createTime;
}
可以看到添加required=false后,在请求的URL中没有添加time参数,程序没有报错。
3.2.5 传递数组
@RequestMapping("/m5")
public String param5(String[] arrayParam) {
return Arrays.toString(arrayParam);
}
3.2.6 传递集合
集合参数:和数组类似,同一个请求参数名有很多个,且需要使用@RequestParam绑定参数关系
@RequestMapping("/m6")
public String param6(@RequestParam List<String> list) {
return "size:" + list.size() + ",listParam:" + list;
}
3.2.7 传递JSON对象
接收JSON对象,需要使用@RequestBody注解
@RequestBody:请求正文,意思就是这个注解作用在请求正文的数据绑定,请求参数必须在写请求正文中。
@RequestMapping("/m7")
public String param7(@RequestBody Person person) {
return person.toString();
}
通过Fiddler观察一下请求参数:
3.2.8 获取URL中参数@PathVariable
path variable:路径变量
和字面表达的意思一样,这个注解主要作用在请求URL路径上的数据绑定
@RequestMapping("/m8/{id}/{name}")
public String param8(@PathVariable Integer id, @PathVariable("name") String username) {
return "解析参数id:" + id + ",name:" + username;
}
3.2.9 上传文件
@RequestMapping("/m9")
public String param9(@RequestPart("file") MultipartFile file) throws IOException {
//获取文件名称
String filename = file.getOriginalFilename();
//文件上传到指定路径
file.transferTo(new File("G:/temp" + filename));
return "文件名称:" + filename;
}
3.2.10 获取Header
3.2.10.1 传统获取header
获取Header也是从HttpServletRequest中获取
@RequestMapping("/getHeader")
public String getHeader(HttpServletRequest request) {
String userAgent = request.getHeader("User-Agent");
return "userAgent:" + userAgent;
}
通过Fiddler观察,获取的User-Agent是否正确
3.2.10.2 简洁获取Header
@RequestMapping("/getHeader2")
public String getHeader2(@RequestHeader("User-Agent") String userAgent) {
return "userAgent: " + userAgent;
}
3.3 响应
在我们前面的代码例子中,都已经设置了响应数据,HTTP响应的结果可以是数据,也可以是静态页面,也可以针对响应设置状态码,Header信息等。
3.3.1 返回静态页面
后台代码:
@RequestMapping("/return")
@RestController
public class IndexController {
@RequestMapping("/index")
public String returnIndex() {
return "/index.html";
}
}
html代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Index页面</title>
</head>
<body>
hello,Spring MVC
</body>
</html>
运行结果;
结果却发现,页面未正确返回,http响应把"/index.html"当做了http响应正文的数据,那Spring MVC如何才能识别出来index.html是一个静态页面,并进行返回呢?
我们需要把@RestController改为@Controller
代码如下:
@RequestMapping("/return")
@Controller
public class IndexController {
@RequestMapping("/index")
public String returnIndex() {
return "/index.html";
}
}
发现页面正确展示了
@RestController和@Controller有着什么样的关联和区别呢?
咱们前面讲了MVC模式,后端会返回视图,这是早期的概念,随着互联网的发展,目前项目开发流行前后端分离模式,MVC的概念也逐渐发生了变化,View不再返回视图,而是返回显示视图所需要的数据。
所以前面使用的@RestController其实是返回的数据
@RestController = @Controller + @ResponseBody
3.3.2 返回数据@ResponseBody
@RequestMapping("/return")
@Controller
public class IndexController {
@RequestMapping("/returnHtml")
@ResponseBody
public String returnData() {
return "/index.html";
}
}
加上@ResponseBody注解,该方法就会把"/index.html"当做一个数据返回给
@ResponseBody既是类注解,又是方法注解
如果作用在类上,表示该类的所有方法,返回的都是数据,如果作用在方法上,表示该方法返回的是数据。
也就是说:在类上添加@ResponseBody就相当于在所有的方法上添加了@ResponseBody注解,
同样,如果类上有@RestController注解时,表示所有的方法上添加了@ResponseBody注解,也就是当前类下的所有方法返回值为响应数据。
注意:如果一个类的方法里,既有返回数据的,又有返回页面的,就把@ResponseBody注解添加到对应的方法上即可。
3.3.3 返回HTML代码片段
@RequestMapping("/return")
@Controller
public class IndexController {
@RequestMapping("/returnHtml2")
@ResponseBody
public String returnHtml() {
return "<h1>返回Html代码片段</h1>";
}
}
后端返回数据时, 如果数据中有HTML代码, 也会被浏览器解析
3.3.4 返回JSON
后端方法返回结果为对象
@RequestMapping("/return")
@Controller
public class IndexController {
@RequestMapping("/returnJson")
@ResponseBody
public HashMap<String, String> returnJson() {
HashMap<String, String> map = new HashMap<>();
map.put("java", "java value");
map.put("MySQL", "MySql value");
return map;
}
}
通过Fidder观察响应结果,Content-Type为application/json
3.3.5 设置状态码
Spring MVC会根据我们方法的返回结果自动设置响应状态码,程序员也可以手动指定状态码
@RequestMapping("/return")
@Controller
public class IndexController {
@RequestMapping("/setStatus")
@ResponseBody
public String setStatus(HttpServletResponse response) {
response.setStatus(401);
return "设置状态码";
}
}
通过Fidder观察响应结果:
3.3.6 设置Content-Type
通过设置produces属性的值,设置响应的报头Content-Type
@RequestMapping("/return")
@Controller
public class IndexController {
@RequestMapping(value = "r1", produces = "application/json; charset=utf8")
@ResponseBody
public String r1(HttpServletResponse response) {
//设置header
response.setHeader("head", "myhead");
return "{'OK': 1}";
}
}
通过Fidder观察响应结果:
3.4 注解源码讲解
3.4.1 @RestController源码
点进去RetentionPolicy,截图如下:
Java注解的生命周期:
- SOURCE:注解只保留在源文件,当Java源文件编译成class文件的时候,注解被遗弃
- ClASS:注解被保留到class文件中,但JVM加载class文件时候被遗弃
- RUNTIME:注解不仅保存到class文件中,JVM加载class文件之后仍然存在
这3个生命周期分别对应于:Java源文件(.java文件)—>.class文件—>内存中的字节码
3.4.2 @Controller源码
3.4.3 @ResponseBody源码