目录
2.1.2 @GetMapping 和 PostMapping
2.2.4 后端参数重命名(后端参数映射)——@RequestParam
2.2.6 使用@PathVariable获取基础URL中的参数(不是从URL的参数部分获取参数)
2.2.7@PathVariable和@RequestParam的区别
一、初识SpringMVC
Spring Web MVC 是基于 Servlet API 构建的原始 Web 框架,从一开始就包含在 Spring 框架中。它的正式名称“Spring Web MVC”来自其源模块的名称(Spring-WebMVC),但它通常被称为“Spring MVC“。
从上述定义我们可以得出两个关键信息:
- Spring MVC 是⼀个 Web 框架。
- Spring MVC 是基于 Servlet API 构建的。
然⽽要真正的理解什么是 Spring MVC?我们⾸先要搞清楚什么是 MVC?
1.1 MVC的定义
MVC(Model-View-Controller)是一种常见的设计模式,用于构建用户界面和应用程序逻辑的分离。MVC模式将应用程序分为三个部分:
- 模型(Model):负责维护应用程序的状态和数据。
- 视图(View):负责呈现模型数据,通常是用户界面。
- 控制器(Controller):接收和处理用户输入,并将请求委派给模型或视图进行处理。
在MVC框架中,应用程序逻辑由控制器处理,控制器从视图接收用户输入,然后使用模型进行状态更改和数据更新,最后将结果传递回视图进行呈现。
以上是传统的MVC的定义,但其实,由于技术的迭代,现在在企业开发的过程中已经不再使用上述这种传统的MVC框架。
当然,也并非完全弃用原先的MVC框架,而是将其简化,毕竟Spring提供的Web框架只有Spring MVC这一个。
最新的MVC框架则是,在 步骤4 时候 不返回数据给View层,而是直接返回数据前端所提供的API,不再负责用户界面的渲染。
1.2 MVC和SpringMVC的关系是什么?
MVC 是⼀种思想,⽽ Spring MVC 是对 MVC 思想的具体实现。
总结来说,Spring MVC 是⼀个实现了 MVC 模式,并继承了 Servlet API 的 Web 框架。既然是 Web框架,那么当⽤户在浏览器中输⼊了 url 之后,我们的 Spring MVC 项⽬就可以感知到⽤户的请求。
1.3 SpringMVC的重要性
现在绝⼤部分的 Java 项⽬都是基于 Spring(或 Spring Boot)的,⽽ Spring 的核⼼就是 Spring
MVC。也就是说 Spring MVC 是 Spring 框架的核⼼模块,⽽ Spring Boot 是 Spring 的脚⼿架,因此我们可以推断出,现在市⾯上绝⼤部分的 Java 项⽬约等于 Spring MVC 项⽬,这是我们要学 SpringMVC 的原因。
在创建SpringBoot项目时,我们所选择的Spring Web 框架其实就是Spring MVC框架,如下图所示:
这里给大家简单的讲讲RESTful,其实实际的应用场景并不是基于这一套,很多都是用Get表示获取资源,其他都是使用post方法。
RESTful介绍
RESTful是一种设计风格或者说是一种架构风格,用于构建Web服务。它是一种轻量级的风格,可以通过HTTP协议进行通信,适合分布式超媒体系统。
RESTful的核心思想是将所有的Web资源抽象成一些列的URI(Uniform Resource Identifier),并通过HTTP方法(GET、POST、PUT、DELETE等)对资源进行操作,实现客户端和服务端之间的无状态通信。RESTful强调的是资源的表现层状态转换(Representational State Transfer,简称REST),它需要满足一定的约束条件,包括:客户端-服务器模型、无状态、缓存、统一接口、分层系统。
RESTful架构的好处包括:
- 轻量级,传输数据格式简单,易于理解。
- 易于扩展,由于符合HTTP协议标准,因此支持多种语言开发和多种平台交互。
- 易于缓存,由于每个请求都包含足够的信息来理解请求本身,因此可以缓存结果以提高性能。
- 代码可读性好,因为每个请求都清晰地指定了要执行的操作,因此代码易于理解和调试。
二、Spring MVC的三大功能
在实际应用中,RESTful通常用于Web API设计,可以通过HTTP请求方式进行数据交互。
下面我们主要介绍SpringMV的三大功能,掌握了以下 3 个功能就相当于掌握了 Spring MVC
- 连接的功能:将⽤户(浏览器)和 Java 程序连接起来,也就是访问⼀个地址能够调⽤到我们的Spring 程序。
- 获取参数的功能:⽤户访问的时候会带⼀些参数,在程序中要想办法获取到参数。
- 输出数据的功能:执⾏了业务逻辑之后,要把程序执⾏的结果返回给⽤户。
2.1 连接功能
在SpringMVC项目中,创建⼀个 UserController 类,实现⽤户到 Spring 程序的互联互通,具体实现代码如下:
package com.example.demo.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去返回一个数据,
@RestController
public class UserController {
@RequestMapping(value = "/sayHi")//可以是一级路由也可以是n级路由
public String sayHi() {
return "say Hi";
}
}
分析:注意这里我们并没有在类上加入@RequestMapping,而是在方法上加入这个注解,这种做法是可以的,因为在SpringMVC中方法是最终访问的最小的单位和单元了。
在地址栏输入:localhost:8080/sayHi 后出现如下界面,代表访问成功。
当然,如果把@RestController给注解了就会出现以下界面:
分析:这是因为没加注解后,是无法让这个方法随着Spring容器启动而启动的。只有加了这个注解才能让这个url地址,随着Spring的启动而注册到Spring当中。
2.1.1 @RequestMapping 注解介绍
@RequestMapping 是 Spring Web 应⽤程序中最常被⽤到的注解之⼀,它是⽤来注册接⼝的路
由映射的。
路由映射
路由映射:所谓的路由映射指的是,当⽤户访问⼀个 url 时,将⽤户的请求对应到程序中某个类
的某个⽅法的过程就叫路由映射
c 基础使用:
可以像上述代码一样,直接修饰方法。
也可以修饰类,当修饰类和方法的时候,访问的地址是类+方法,如下所示:
@RequestMapping支持get、post等请求吗?
其实,了解HTTP协议的同学都知道,当我们在浏览器输入url的时候,其实就是通过浏览器向服务器发送Get请求,我们可以通过浏览器的开发者模式查看:
我们这里使用PostMan进行模拟POST请求:发现是可以实现的,PUT、DELETE等就不再一一模拟了,均可实现。
在很多时候,可能需要指定GET/POST方法
那么应该如何指定呢?我们来看看 @RequestMapping注解的源码:
分析:
具体来说,该注解定义了如下属性:
- name :名称,默认为空字符串。
- value:请求的路径,可以是一个字符串或字符串数组。@AliasFor(“path”)注解在value()方法上,表示value方法是path方法的别名。
- path:请求的路径,可以是一个字符串或字符串数组。@AliasFor(“value”)注解在path()方法上,表示path方法是value方法的别名。
- method:请求的 HTTP 方法,可以是一个 RequestMethod 类型的枚举值或枚举值数组。
- params:请求中必须包含的参数,可以是一个字符串或字符串数组。
- headers:请求中必须包含的头信息,可以是一个字符串或字符串数组。
- consumes:接受请求的内容类型,可以是一个字符串或字符串数组。
- produces:响应的内容类型,可以是一个字符串或字符串数组。
需要注意的是:注解中的属性都有默认值,使用时可以根据需要设置。其中,value和path二者是等价的,如果同时设置了这两个属性,则它们的值必须相同。如果没有设置任何属性,则默认将映射到处理程序方法的路径上。
进入RequestMetho[] 中可以看到有以下方法:
但是很多人可能会有疑问,可是刚刚源码上面不是默认为空吗?那为什么之前在没有设置方法的时候,我们可以通过post、get等方式来进行访问呢?
首先我们需要明确,在Java中,使用关键字default来表示默认值。在这段源码中RequestMethod[]()default{} 确实表示的是在没有设置path属性值时,该属性的默认值为空数组。
换句话说,如果使用@RequestMapping注解时没有指定path属性的值,则该属性的默认值为一个空数组。
但是@RequestMapping注解在默认情况下会被视为一个模糊匹配的方式来处理请求,也就是说,只要请求的 URL 能够匹配到该注解的value或path属性指定的路径,那么该注解就会被触发,不管这个请求是使用什么 HTTP 请求方法发送的。
分析之后,可以得知需要通过method方式指定访问的方式:
当指定之后,通过其他方式就不能访问这个url地址了,可以通过PostMan来发送请求进一步进行验证:
2.1.2 @GetMapping 和 PostMapping
这两种方法就是用来专门指定通过get/post方式来访问url的,简化了上述通过参数指定的方式:
这里由于GetMapping和PostMapping用法差不多,只演示一个的用法:
由于只支持post请求,通过其他方式访问会抛出405:
2.2 获取参数功能
2.2.1 传递普通参数
在 Spring MVC 中可以直接⽤⽅法中的参数来实现传参,⽐如以下代码:
展示效果:
如果是传递多个参数的时候:那么参数的顺序其实是无需固定的,只需要键值对匹配即可:
来看一组小练习:观察以下三个方法返回的内容是什么?
当我们分别访问这三个方法时候,不传入任何值时:
可以发现String,Integer这两个类,会有默认值null,而int这样的基本类型在没有传递给它值的时候会直接出现报错的行为。
因此,平常开发的过程中,建议传递参数的时候使用类,而不是基本类型,这样做的目的是可以让前端的开发人员明白快速的哪里出错,代码可以修改为如下所示:
我们知道,SpringMVC是基于Servlet API而搭建的,因此也可以使用HttpServletRequest/HttpServletResponse来完成接受参数/做出响应的任务.
不过这种方式比较少见,在SpringMVC中,通常是使用HttpServletRequest的getRemoteAddr()方法获取客户端的IP地址(例如用于将某些用户加入黑名单中以此来拒绝其访问):
@RequestMapping("/getIpAddress")
public String getIpAddress(HttpServletRequest request) {
String ipAddress = request.getRemoteAddr();
return ipAddress;
}
需要注意的是,使用该方法获取IP地址可能会有一定的误差,例如当客户端使用了代理服务器时,获取到的IP地址可能是代理服务器的IP地址而不是真实客户端的IP地址。如果需要更准确的IP地址,可以考虑通过X-Forwarded-For等HTTP头部信息来获取。
当然,也可以使用HttpServletResponse的sendRedirect方法来进行重定向操作:
@RequestMapping("/Hi4")//可以是一级路由也可以是n级路由
public String sayHi4(HttpServletRequest request, HttpServletResponse response) throws IOException {
response.sendRedirect("https://www.baidu.com/");//当然这里跳转了,就不会触发后续的return了。
return "say Hi "+request.getParameter("name");
}
聊到重定向,那么我们就顺便聊聊请求转发和请求重定向的区别吧
1. 概念不同;
重定向是指:当服务器接收到客户端发送的请求时,服务器返回一个重定向的响应,告诉客户端请求的资源已经被移动到另一个位置,客户端需要重新发送请求到新的地址。重定向是在浏览器端完成的,服务器只需要在响应中返回新的URL地址即可(URL改变了)。
请求转发是指:服务器接收到客户端发送的请求后,将请求转发给另一个资源进行处理,然后将处理结果返回给客户端。重转发是在服务器端完成的,客户端并不知道所请求的资源被转发了。因此整个过程请求转发的URL是不会变的。
2. 实现方式不同:
重定向通过修改HTTP响应头中的Location字段,让客户端重新发送请求到新的URL地址。因为是两次请求,所以在浏览器中会出现两个请求记录,因此重定向会比重转发慢一些。
请求转发则是在服务器端完成的,不需要修改HTTP响应头中的内容,只需要将请求转发到目标资源进行处理即可。因为是在服务器端完成的,所以重转发的效率要比重定向高。
3.数据共享不同:
重定向是两次请求,所以请求的上下文信息是不能够共享的,每次请求都是独立的,因此不能直接共享请求中的参数和属性。
重转发是在服务器端完成的,所以可以共享请求中的参数和属性,因为它们都在服务器内部处理的。
4.响应速度不同
理论上来说,请求转发的响应速度通常更快,因为它是在服务器内部完成的,没有涉及HTTP重定向和客户端的额外通信。
综上所述,重定向和重转发虽然都是页面跳转的方式,但是它们的实现方式和特点有很大的不同,应该根据实际情况选择使用哪种方式。
2.2.2 传递对象
待传递对象的代码:
获取对象的代码实现:
演示如下:
我们发现SpringMVC框架是很智能的,可以根据实际返回的类型,灵活的转换:比如上面代码所展示的,本来是放回Object类型,实际返回的是JSON格式。
还有下面所展示的:返回的本来是Object类,但是根据实际情况,却返回了一个HTML界面:
2.2.3 表单参数传递
表单参数的获取可以将多个数据封装成一个对象来获取,或者可以使用像获取多个参数一样的方法都可,由于上面已经演示过了,这里就不再详细演示。
2.2.4 后端参数重命名(后端参数映射)——@RequestParam
为什么需要给后端参数重命名呢?其实同学们可能已经知晓一二了,因为在后端捕获数据的时候,一般情况下,如果想获取成功,那么是需要保证参数的一致性的,如下所示:
但是在实际开发的过程中:可能前后端约定的参数不一定完全相同,这时为了保证能够正常的进行前后端交互,那么就需要将后端参数进行重命名:
对参数进行重命名免不了使用@RequestParam,我们先来看看其源码:
@RequestParam有三个主要的属性:
- value:请求参数的名称。
- required:指定该参数是否必须存在。默认值为true。
- defaultValue:指定请求参数的默认值。
接下来使用@RequestParam来演示下如何实现参数的重命名:
可能有人注意到@RequestParam中的属性required()是默认为true的,也就是说,如果没有传递正确的参数,或者没有传递参数,就会抛出异常:
总结:其实就是要让username获取到参数即可,如果这个参数是非必传的参数,那么可以将require设置为false,如下图所示:
2.2.5 使用@RequestBody来接受JSON对象
如果直接使用对象来接受JSON对象,那么是不被允许的:
@RequestMapping("/reg3")
public Object reg3(Userinfo userinfo) {
System.out.println(userinfo);
return userinfo;
}
使用PostMan模拟发送POST请求进行验证:
使用@RequestBody,代码如下:
@RequestMapping("/reg3")
public Object reg3(@RequestBody Userinfo userinfo) {
System.out.println(userinfo);
return userinfo;
}
需要注意的是,@RequestBody注解只能用于处理请求体中的数据,如果需要处理URL参数,那么需要使用@RequestParam注解,同时,由于@RequestBody需要反序列化请求体,因此请求体中的数据格式需要符合指定类型的Java对象的格式要求,否则反序列化会失效。
什么是反序列化?
序列化是将对象转换为可传输或可存储的格式的过程。那么反序列化就是将序列化后的数据恢复成原始数据的过程,通常在网络传输、数据存储等场景中使用。在Java中,反序列化的过程是通过将序列化后的字节流转换成Java对象来实现的。反序列化的目的是为了将数据从一种形式转换成另一种形式,方便数据在不同系统或场景中的传输、存储和处理。常见的反序列化库包括Java原生的序列化机制、Jackson、Gson等。
2.2.6 使用@PathVariable获取基础URL中的参数(不是从URL的参数部分获取参数)
@PathVariable获取基础URL中的参数:
/**
* 获取URL参数(?之前的参数)
* @param name
* @param password
* @return
*/
@RequestMapping("/reg4/{name}/{password}")
public Object reg4(@PathVariable String name,@PathVariable String password) {
return "say Hi "+name+" 密码是: "+password;
}
实际效果:
以下这种将参数放入URL中,而不是queryString中的方式,会有以下两种优点:
- 搜索引擎抓取关键字权重更高
- URL更加简洁
也就是 reg4/lisi/123 相比于reg4?name=lisi&password=123 更加简洁,搜索引擎抓取关键字权重更高。
另外需要注意以下几点:
@PathVariable源码:
观察@PathVariable源代码发现,其默认值为true,那就是说,跟@RequestParam一样,如果参数获取失败,就会报错:
虽然我们可以将required设置为false,让其不报错,但是这样还是获取不到参数,显示为null。
在设置@PathVariable中设置value属性后:就可以正常获取参数了。
2.2.7@PathVariable和@RequestParam的区别
回顾:我们知道:使用@PathVariable和@RequestParam都可以获取请求的参数,但是它们的实现方式和使用场景有所不同。
@RequestParam主要用于获取请求参数(?之后的参数),它将请求参数映射到对应的控制器方法参数上,可以获取 GET 和 POST 请求中的请求参数。默认情况下,使用@RequestParam获取参数时,请求参数必须要传递,如果不传递则会报错。
而@PathVariable则是用于获取 URL 中的参数(?之前的参数),它可以将 URL 中的一部分作为参数传递到控制器方法中。相对于@RequestParam,它的优势在于可以实现更为友好的 URL,并且可以增强网站的 SEO,让搜索引擎更容易地识别和解析网站的 URL。
因此,如果需要实现更为友好的 URL,并且增强网站的 SEO,建议使用@PathVariable获取参数。
什么是SEO?
SEO(Search Engine Optimization)是指搜索引擎优化,是一种通过了解搜索引擎的运作规则,对网站进行内外部的优化,以提高网站在自然搜索结果中的排名,从而提高网站在搜索引擎中的曝光度、流量和转化率的过程。
2.2.8 上传文件@RequestPart
一般会把上传的文件存储到哪?
可能会存储到OSS中,亦或者是自己的服务器中。
OSS是"Object Storage Service"的缩写,指的是对象存储服务。对象存储是一种云计算服务,旨在用于存储和管理大量数据、文件和对象,通常是非结构化数据,如图片、视频、文档、备份文件等。OSS通常由云服务提供商提供,用户可以将数据上传到云存储中,并随时访问和检索这些数据。
在介绍@RequestPart之前,我们先来看看几个预备知识:
MultipartFile是什么
MultipartFile 是 Spring框架提供的一个接口,用于表示接收到的上传文件。它是 Spring 对 Servlet 中的 Part 接口进行封装的结果,提供了一些方法用于获取文件名、文件类型、文件字节数组等信息,同时也支持流式读取文件内容。
在 Spring Web 应用程序中,通常会在控制器方法的参数列表中使用MultiFile参数来接收上传的文件。这样做的好处是可以轻松地对上传文件进行处理,例如保存到本地文件系统或者上传到云存储服务等。
MultipartFile的transferto方法的作用是什么?
MultipartFile的transferto方法是用于将MultipartFile的内容写入到磁盘文件中的方法,它的语法如下:
void transferTo(File dest) throws IOException, IllegalStateException;
该方法将MultipartFile的内容写入指定的文件dest中。如果目标文件已经存在,则会覆盖原有的文件。当MultipartFile的内容已经被写入到文件中时,它就不再可用了。
需要注意的是,transferTo 方法需要指定一个File对象作为参数,该File对象表示目标文件的路径和文件名。同时,调用该方法的前提是目标文件所在的目录必须存在,并且具有写入权限。如果目录不存在,则会抛出IOException异常。
实现代码如下:
@RequestMapping("/upload")
public Object upload(@RequestPart("img") MultipartFile file) {
File saveFile = new File("C:\\Users\\86136\\Desktop\\img.png");
try {
file.transferTo(saveFile);
return true;
} catch (IOException e) {
e.printStackTrace();
}
return false;
}
使用PostMan发送请求:
上传成功之后,便可以在目标路径中找到:
在上传文件的过程中,可能会碰到文件太大而上传失败的情况,这时候可以在配置文件中修改配置:
以properties为例:
限制单个文件最大大小,单位为字节,默认1MB
spring.servlet.multipart.max-file-size=2MB
限制整个请求的最大大小,包括所有文件和表单项的大小,单位为字节,默认为10MB
spring.servlet.multipart.max-request-size=2MB
但是这个方法是有缺陷的,因为使用 transferto 方法将 MultipartFile 的内容写入指定的文件dest中。如果目标文件已经存在,则会覆盖原有的文件。当MultipartFile的内容已经被写入到文件中时,它就不再可用了。
使用UUID进行改进
这里简单介绍一下UUID:
UUID是"Universally Unique Identifier"的缩写,意为通用唯一标识符。它是一种用于标识信息的128位长度的唯一标识符,通常以32个十六进制数字的形式表示,例如:"550e8400-e29b-41d4-a716-446655440000"。
PS:UUID里面大致是有当前主机的MAC地址,当前的时间戳以及一些随机数组成的。
@RequestMapping("/upload")
public Object upload(@RequestPart("img") MultipartFile file) {
String fileName = UUID.randomUUID() + //获取文件名
file.getOriginalFilename().substring( //获取文件后缀
file.getOriginalFilename().lastIndexOf("."));
File saveFile = new File("C:\\Users\\86136\\Desktop\\"+fileName);
try {
file.transferTo(saveFile);
return true;
} catch (IOException e) {
e.printStackTrace();
}
return false;
}
2.2.9 获取Cookie/Session/header
获取Cookie
第一种获取Cookie的方式是使用HttpServletRequest的getCookie的方式获取:
@RequestMapping("/getCookies")
public String getCookies(HttpServletResponse response,HttpServletRequest request) {
Cookie[] cookies = request.getCookies();
return cookies.toString();//无实际意义,只是为了不报错
}
第二种则是使用@CookieValue的方式来获取单个Cookie:
@RequestMapping("getCookie")
public Object getCookie(@CookieValue String a) {
return a;
}
分析该代码的含义:获取一个名为a的Cookie,注入到String类型的a中,但是由于源码中显示required默认为true,获取失败时候会报错:
当然我们也可以把required的值设置为false,这样就不会报错,但是页面仍然获取不到:
当然,如果手动添加了名称为 a 的Cookie,那么就可以获取成功了:
获取header
使用HttpServletRequest的getHeader方式获取header:
@RequestMapping("/getHeader")
public Object getHeader(HttpServletResponse response,HttpServletRequest request) {
String userAgent = request.getHeader("User-Agent");
return userAgent;
}
实现效果如图:
使用@RequestHeader方式获取header:
其源码的required仍然为true,这里就不再做演示。
实现代码如下:
@RequestMapping("/getHeader2")
public String getHeader2(@RequestHeader("User-Agent") String userAgent) {
return "userAgent : "+userAgent;
}
实现效果:
当然,也可以进一步验证:发现完全匹配。
获取Session
在获取Session之前,我们需要手动的先存储一个Session进去。
注意这里我们可以不用传参,因为默认是true。(千万不要传false,否则将无法完成存储session的任务):
获取Session:
/**
* 获取Session
* @param name
* @return
*/
@RequestMapping("/getSession")
public Object getSession(@SessionAttribute(SESSION_KEY) String name) {
return name;
}
获取成功:
2.3 返回功能
通过上⾯的学习我们知道,默认请求下⽆论是 Spring MVC 或者是 Spring Boot 返回的是视图
(xxx.html),⽽现在都是前后端分离的,后端只需要返给给前端数据即可,这个时候我们就需要使⽤@ResponseBody(或者使用@RestController代替@ResponseBody和@Controller)。
现在static的目录下创建前端页面 index.html:
发现无法访问该页面(这种方式默认是请求转发forward,如果当前路径找不到那么就报错):
而我们将index.html文件前面加个 '/' ,发现就可以正常访问了,这是为什么呢?
首先我们需要明白根路径的含义:域名(IP)+端口号。
因为如果按照正常逻辑,这个路由的查询地址的意思是:在localhost:8080/test/目录中查询这个静态网页:
也就是实际上URL是:localhost:8080/test/index。
可以通过fiddler抓包查看:
如果加上/的话表示:在根目录中查找,这是因为这个return是默认重定向的,如果带了根路径,那么就会直接去查询这个绝对路径下有没有这个html界面。
2.3.1 请求转发VS请求重定向
return 不但可以返回⼀个视图,还可以实现跳转,跳转的⽅式有两种:
- forward: 是请求转发;
- redirect:请求重定向。
请求转发和重定向的使⽤对⽐:
请求转发:
请求重定向(当然,也可以像上面 2.2.1 一样,使用sendRedirect来重定向):
我们会发现:
请求转发是服务器端代为请求,再将结果返回给客户端的,所以整个请求的过程中 URL 地址是不变的;而请求重定向是服务器端告诉客户端,“你去另一个地访问去”,所以浏览器会重新再发送一次请求,因此客户端最终显示的 URL 也为最终跳转的地址,而非刚开始请求的地址,所以 URL 地址发生了改变。