前言
- 最近因为业务需要,被拉去研究后端的项目,代码框架基于 Spring Boot,后端对我来说完全小白,需要重新学习研究…
- 出于个人习惯,会以 Blog 文章的方式做一些记录,文章内容基本来源于「 Spring Boot 从入门到精通(明日科技) 」一书,做了一些整理,更易于个人理解和回顾查找,所以大家如果希望更系统性的学习,可以阅读此书。
系列 | 文章 |
---|---|
Spring Boot 学习之路 | 📑 基础认知 |
Spring Boot 学习之路 | 📑 项目配置 |
Spring Boot 学习之路 | 📑 处理 HTTP 请求 |
Spring Boot 学习之路 | 📑 Service 层 |
Spring Boot 学习之路 | 📑 Thymeleaf 模板引擎 |
HTTP 请求是指从客户端到服务器的请求消息。对于一个 Spring Boot 项目而言,服务器就是 Spring Boot,客户端就是用户本地的浏览器。启动 Spring Boot 项目后,首先用户通过 URL 地址发送请求,然后 Spring Boot 通过解析 URL 地址处理请求,最后 Spring Boot 把处理结果返回给用户。
HTTP 请求有 3 种很常见的请求类型,它们分别是 GET、POST 和 DELETE。
请求类型 | 说明 |
---|---|
GET | 表示请求从服务器获取特定资源 |
POST | 表示在服务器上创建一个新的资源 |
DELETE | 表示从服务器删除特定的资源 |
本文将介绍 Spring Boot 是如何使用注解解析 URL 地址,进而处理上述 3 种类型的 HTTP 请求的。
一、处理 HTTP 请求的注解
在开发 Spring Boot 项目的过程中,Spring Boot 的典型应用是处理 HTTP 请求。所谓处理 HTTP 请求,就是 Spring Boot 把用户通过 URL 地址发送的请求「交给不同的业务代码进行处理的过程」。
1.1 @Controller
Spring Boot 提供了用于声明控制器类的 @Controller 注解。也就是说,在 Spring Boot 项目中,把被 @Controller 注解标注的类称作「控制器类」。控制器类在 Spring Boot 项目中发挥的作用是处理用户发送的 HTTP 请求。
Spring Boot 会把不同的用户请求交给不同的控制器进行处理,而控制器则会把处理后得到的结果反馈给用户。
说明:
控制器(controller)定义了应用程序的行为,它负责对用户发送的请求进行解释,并把这些请求映射成相应的行为。
因为 @Controller 注解本身被 @Component 注解标注:
@Component
public @interface Controller {
所以控制器类属于组件。这说明在启动 Spring Boot 项目时,控制器类会被扫描器自动扫描。这样,程序开发人员就可以在控制器类中注入 Bean。例如,在控制器中注入 Environment 环境组件,代码如下:
@Controller
public class TestController {
@Autowired
Environment env;
}
1.2 @RequestMapping
Spring Boot 提供了用于映射 URL 地址的 @RequestMapping 注解。@RequestMapping 注解可以标注类和方法。如果一个类或者方法被 @RequestMapping 注解标注,那么这个类或者方法就能够「处理用户通过 @RequestMapping 注解映射的 URL 地址发送的请求」。
下面将首先介绍 @RequestMapping 注解的属性,然后介绍如何使用 @RequestMapping 注解映射包含层级关系的 URL 地址。
注意:
@Controller 注解要结合 @RequestMapping 注解一起使用。
属性:value
value 属性是 @RequestMapping 注解的默认属性,用于指定映射的 URL 地址。在单独使用 value 属性时,value 属性可以被隐式调用。调用 value 属性的语法如下:
@RequestMapping("test")
@RequestMapping("/test")
@RequestMapping(value= "/test")
@RequestMapping(value={"/test"})
上面这 4 种语法所映射的 URL 地址均为“域名/test”。其中,域名指的是当前 Spring Boot 项目所在的域。如果你在在 IntelliJ IDEA 中启动一个 Spring Boot 项目,那么域名默认为:
http://localhost:8080 或 http://127.0.0.1:8080
比如我们创建一个 TestController:
@Controller
public class TestController {
@RequestMapping("/index") // 映射的 URL 地址为 /index
@ResponseBody // 直接将字符串显示在页面上
public String test(){
return "欢迎访问我的主页";
}
}
在浏览器上访问 http://127.0.0.1:8080/index 地址:
说明:
如果一个方法被 @ResponseBody 注解标注,那么由这个方法返回的字符串将被直接显示在浏览器的页面上。
同时,@RequestMapping 注解映射的 URL 地址也可以是多层的。例如:
@RequestMapping("/shop/books/computer")
上述代码映射的完整URL地址是:http://127.0.0.1:8080/shop/books/computer。需要特别注意的是,这个 URL 地址中的任何一层都是不可或缺的,否则将引发 404 错误。
另外,@RequestMapping 注解允许一个方法同时映射多个 URL 地址。其语法如下:
@RequestMapping(value = { "/address1", "/address2", "/address3", ....... })
属性:method
method 属性能够指定用户通过 @RequestMapping 注解映射的 URL 地址「发送的请求的类型」。这样,使用 method 属性就能够让不同的方法处理由相同 URL 地址发送的不同类型的请求。
下面将通过一个实例演示 method 属性的用法,我们修改 TestController 类:
- 如果由 “/index” 地址发送的请求的类型是 GET,则打印“处理 GET 请求”
- 如果由 “/index” 地址发送的请求的类型是 POST 请求,则打印“处理 POST 请求”
@Controller
public class TestController {
@RequestMapping(value = "/index", method = RequestMethod.GET)
@ResponseBody
public String get(){
return "处理 GET 请求";
}
@RequestMapping(value = "/index", method = RequestMethod.POST)
@ResponseBody
public String post(){
return "处理 POST 请求";
}
}
使用 Postman 模拟 GET 请求和 POST 请求,所示结果如下。如果发送的请求既不是 GET 类型也不是 POST 类型,则会触发 405 错误。
由 URL 地址发送的请求具有多种类型,详见 RequestMethod 枚举类。
RequestMethod 枚举类的代码如下:
public enum RequestMethod {
GET,
HEAD,
POST,
PUT,
PATCH,
DELETE,
OPTIONS,
TRACE;
}
属性:params
params 属性能够指定在用户通过 @RequestMapping 注解映射的 URL 地址发送的请求中「须包含哪些参数」。因为 params 属性的类型是「字符串数组」,所以通过 params 属性能够同时指定多个参数。
下面将通过一个实例演示 params 属性的用法,我们修改 TestController:
@Controller
public class TestController {
@RequestMapping(value = "/index", params = { "name", "id" })
@ResponseBody
public String haveParams(){
return "欢迎回来~";
}
@RequestMapping(value = "/index")
@ResponseBody
public String noParams(){
return "忘传参数了...";
}
}
使用 Postman 模拟用户通过 URL 地址发送的请求。查看结果:
属性:headers
headers 属性能够指定在用户通过 @RequestMapping 注解映射的 URL 地址发送的请求中「须包含哪些指定的请求头」。也就是说,在一个 headers 属性中,可以包含若干个请求头。通过这些请求头,服务器能够得知客户端环境以及与请求正文相关的一些信息,例如浏览器的版本、请求参数的长度等。
headers 属性在 @RequestMapping 注解中的格式如下:
@RequestMapping(headers = {"键1=值1", "键2=值2", ......})
说明:
请求头指的是 HTTP 请求中的头部信息,即用于 HTTP 通信的操作参数。
从 headers 属性在 @RequestMapping 注解中的格式,能够非常清晰地看到请求头在 headers 属性中的格式:
"键=值"
我们测试下,修改 TestController,在控制器类中编写 noParams() 和 haveParams() 这两个方法。如果用户通过 URL 地址发送的请求包含 headers 属性,就交由 havaParams() 方法处理,让用户直接进入欢迎界面;否则,就交由 noParams() 方法处理,让用户进入登录界面。
@Controller
public class TestController {
@RequestMapping(value = "/index")
@ResponseBody
public String noParams(){
return "请重新登录!";
}
@RequestMapping(value = "/index", headers = { "Cookie=JSESSIONID=123456789"})
@ResponseBody
public String havaParams(){
return "欢迎回来~";
}
}
说明:
Cookie 是某些网站为了辨别用户身份、进行 Session 跟踪而储存在用户本地终端上的数据。Cookie 会被暂时地或永久地保存在用户客户端计算机中。
对于本示例,如果用户在某个登录界面选择了“自动登录”选项,那么服务器就会将用户登录的 session id 写在浏览器的 Cookie 中。
使用 Postman 模拟用户通过 URL 地址发送的请求。
- 如果在请求中不包含 headers 属性,访问 http://127.0.0.1:8080/index 地址:
- 如果为请求头添加 Cookie,值为“JSESSIONID=123456789”,再访问同一个地址:
属性:consumes
consumes 属性能够指定用户通过 @RequestMapping 注解映射的 URL 地址发送的请求的「数据类型」。其中,常见的类型有 “application/json”、“text/html” 等。
下面将通过一个实例演示 consumes 属性的用法。
修改 TestController 类,将 @RequestMapping 注解的 consumes 属性设置为“application/json”。在控制器类中编写 formatError() 和 hello() 这两个方法。如果用户发送的请求的数据类型是 JSON,就交由 hello() 方法处理,并提示“成功进入接口”的信息;否则,就交由 formatError() 方法处理,并提示“数据格式错误!”的信息。
@Controller
public class TestController {
@RequestMapping(value = "/index")
@ResponseBody
public String formatError(){
return "数据格式错误!";
}
@RequestMapping(value = "/index", consumes = "application/json")
@ResponseBody
public String hello(){
return "成功进入接口";
}
}
使用 Postman 模拟用户通过URL地址发送的请求。如果直接访问 http://127.0.0.1:8080/index 地址,结果如下:
如果在请求体(Body)中填写 JSON 数据,再访问上述地址就可以看到下图结果:
层级 URL
通常一个 URL 地址不只是简单的一层地址,而是根据业务分类形成的多层地址。那么,如何理解在一个 URL 地址中包含多层地址呢?
例如,在某电商平台通过访问 “/shop/books” 地址查看图书信息;其中,“/shop” 是这个电商平台的地址,“/books” 表示图书类。那么,应该如何使用 @RequestMapping 注解映射这个包含层级关系的 URL 地址呢?
代码如下:
@Controller
public class BeanTestController {
@RequestMapping("/shop/books")
@ResponseBody
public String books() {
return "图书类";
}
}
不难发现,在表示电商平台的控制器类中包含一个 book() 方法。通过使用 @RequestMapping 注解标注这个 book() 方法,就能够实现映射一个多层的 URL 地址的功能。
如果这个电商平台还卖服装,那么就会含有表示服装类的 “/clothes” 地址。那么,又应该如何使用 @RequestMapping 注解既映射 “/shop/books” 地址,又映射 “/shop/clothes” 地址呢?代码如下:
@Controller
public class BeanTestController {
@RequestMapping("/shop/clothes")
@ResponseBody
public String cloths() {
return "服饰类";
}
@RequestMapping("/shop/books")
@ResponseBody
public String books() {
return "图书类";
}
}
不难发现,“/shop/books” 和 “/shop/clothes” 这两个地址具有相同的上层地址 “/shop”。那么,有没有什么编码方式能够优化上述代码呢?
答案是使用 @RequestMapping(“/shop”) 注解标注表示电商平台的「控制器类」。
优化后的代码如下:
@Controller
@RequestMapping("/shop")
public class BeanTestController {
@RequestMapping("/clothes")
@ResponseBody
public String cloths() {
return "服饰类";
}
@RequestMapping("/books")
@ResponseBody
public String books() {
return "图书类";
}
}
说明:
前面我们就提到过:@RequestMapping 注解不仅可以标注方法,还可以标注类。
在访问一个多层的 URL 地址时,输入的 URL 地址必须是完整的,比如下面的效果图:
1.3 @ResponseBody
在上文讲解的所有实例中,其中的方法都被 @RequestMapping 和 @ResponseBody 注解同时标注。在掌握了 @RequestMapping 注解的相关内容后,下面将介绍 @ResponseBody 注解的作用。
@ResponseBody 注解的作用是把被 @ResponseBody 注解标注的方法的返回值「转换为页面数据」。
- 如果被 @ResponseBody 注解标注的方法的返回值是字符串,页面就会显示字符串;
- 如果被 @ResponseBody 注解标注的方法的返回值是其他类型的数据,这些数据就会先被自动封装成 JSON 格式的字符串,再显示在页面中。
下面将介绍在使用 @ResponseBody 注解时会遇到的另外一种情况:如果控制器类中的某个方法被 @RequestMapping 注解标注,却没有被 @ResponseBody 标注,那么这个方法的返回值会是什么呢?
答案是即将跳转的 URL 地址。例如:
@Controller
public class TestController {
@RequestMapping("/index") // 映射 "/index" 地址,未标注 @ResponseBody
public ModelAndView index(){
return new ModelAndView("/welcome"); // 跳转至 "/welcome" 地址
}
}
在上述代码中,index() 方法被 @RequestMapping 注解标注,却没有被 @ResponseBody 标注。该方法的返回值是 org.springframework.web.servlet.ModelAndView
类型。
因为上述代码的功能是当用户访问 “/index” 地址时,页面就会跳转至与 “/welcome” 地址对应的页面,所以可以把 index() 方法的返回值修改为字符串。修改后的代码如下:
@Controller
public class BeanTestController {
@RequestMapping("/index") // 映射 "/index" 地址,未标注 @ResponseBody
public String index(){
return "/welcome"; // 跳转至 "/welcome" 地址
}
}
通过上述代码,是不是就能够实现跳转页面的功能了呢?答案是否定的。为了实现跳转页面的功能,还需要向上述代码添加用于映射 “/welcome” 地址的方法,并且这个方法要被 @RequestMapping 和 @ResponseBody 注解同时标注。添加用于映射 “/welcome” 地址的方法后的代码如下:
@Controller
public class BeanTestController {
@RequestMapping("/index")
public String index(){
return "/welcome";
}
@RequestMapping("/welcome")
@ResponseBody
public String welcome(){
return "欢迎来到我的主页";
}
}
启动项目后,打开浏览器访问 http://127.0.0.1:8080/index 地址,即可看到页面会跳转至与 “/welcome” 地址对应的页面:
此外,在使用 @ResponseBody 注解时还需要特别注意一个问题:@ResponseBody 注解虽然也可以标注控制器类,但是控制器类中的所有方法的返回值都会直接显示在页面上。例如,把上述代码中的 @ResponseBody 注解标注在控制器类上,代码如下:
@Controller
@ResponseBody // 将注解标注在类上
public class BeanTestController {
@RequestMapping("/index")
public String index(){
return "/welcome";
}
@RequestMapping("/welcome")
public String welcome(){
return "欢迎来到我的主页";
}
}
启动项目,打开浏览器访问 http://127.0.0.1:8080/index 地址,会发现页面没有发生跳转,并且显示的结果是index()方法的返回值:
1.4 @RestController
@RestController 注解虽然是 Spring Boot 的新增注解,但实质上是 @Controller 和 @ResponseBody 这两个注解的综合体。也就是说,当控制器类同时被 @Controller 和 @ResponseBody 这两个注解标注时,这两个注解可以被 @RestController 注解替代。这样就可以起到简化代码的作用。例如:
@Controller
@ResponseBody
public class TestController {
}
使用 @RestController 注解可以简化上述代码。简化后的代码如下:
@RestController
public class TestController {
}
二、重定向 URL 地址
重定向 URL 地址是指用户通过原始的 URL 地址发送的请求指向了新的 URL 地址,并且请求中的数据不会被保留。也就是说,通过重定向 URL 地址,服务器可以把用户推送到其他网站上。
下面将介绍 Spring Boot 用于实现重定向的两种方法。
2.1 redirect:
前面一节,我们已经明确了如果控制器类中的某个方法被 @RequestMapping 注解标注,却没有被 @ResponseBody 标注,且这个方法的返回值是字符串,那么这个方法的作用是实现页面跳转的功能,这个方法返回的字符串表示的是即将跳转的 URL 地址。如果在即将跳转的 URL 地址的前面加上“redirect:”,就表示用户通过原始的 URL 地址发送的请求指向了这个 URL 地址。
下面通过一个实例演示“redirect:”前缀的用法。
@Controller
public class BeanTestController {
@RequestMapping("/bd")
public String bd(){
return "redirect:https://www.baidu.com";
}
}
启动项目后,打开浏览器访问 http://127.0.0.1:8080/bd 地址,浏览器会自动跳转至百度首页,并且地址栏中 URL 地址显示的也是百度首页的 URL 地址,原始的 URL 地址已经在地址栏中看不到了。
2.2 response
response 对象指的是 HttpServletResponse 类型的对象,可用于实现重定向 URL 的功能。那么,Spring Boot 是如何使用 response 对象实现这个功能的呢?
Spring Boot 可以直接在控制器类的某个方法中创建 response 对象,通过这个对象调用 sendRedirect() 方法就可以指定重定向的 URL 地址。只不过,如果这个方法具有返回值,那么上述操作会导致这个返回值失效。因此,程序开发人员通常会把这个方法的返回值的类型设置为 void。
@Controller
public class BeanTestController {
@RequestMapping("/bd")
public void bd(HttpServletResponse response){
try {
response.sendRedirect("https://www.baidu.com");
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
启动项目后,打开浏览器访问 http://127.0.0.1:8080/bd 地址,即可看到百度首页。
三、解析 URL 地址中的参数
当用户通过 URL 地址发送请求时,Spring Boot 要做的第一个工作就是解析 URL 地址中的参数,从而明确这个请求需要交由控制器类中的哪个方法进行处理。
3.1 自动解析
Spring Boot 能够自动解析 URL 地址中的参数,并将这些参数的值注入某个方法的参数中。想要获取 URL 地址中的参数的值,只需在这个方法中设置「同类型」的、「同名」的参数即可。
例如,URL 地址为 http://127.0.0.1:8080/index?name=tom 的请求,想要获取这个 URL 地址中的 name 参数,可以在控制器类的方法中定义 name 参数,代码如下:
@RestController
public class TestController {
@RequestMapping("/index")
public String test(String name){
System.out.println("name = " + name);
return "success";
}
}
在 test() 方法的作用下,Spring Boot 会自动将 URL 地址中参数的值注入同名的方法参数中。上面这段代码将会在控制台输出:
name = tom
使用这种自动识别 URL 地址中参数的功能时,要注意以下几点:
- Spring Boot 可以识别各种类型的 URL 地址中的参数,例如 GET、POST、PUT 等类型。
- 参数名区分大小写。
- 参数没有顺序要求,Spring Boot 会以参数名称作为识别条件。
- URL 地址中参数的数量可以与方法中参数的数量不一致,只有名称相同的参数才会被注入。
- 方法参数的类型不应采用基本数据类型。例如,整数应采用 Integer 类型,而不是 int 类型。
- 如果方法参数没有被注入值,则采用默认值;其中,引用类型默认值为 null。如果方法的某一参数的值为 null,要么是因为 URL 地址没有此参数,要么是因为参数名称不匹配。
- URL 地址的参数(Request Param)不是请求体(Request Body)。
我们来测试下,写个例子:验证用户发送的账号、密码是否正确。
创建 TestController 控制器类,当用户访问 “/login” 地址时需要向服务发送账号和密码。如果账号是 “David”,密码是 “123456”,则提示用户登录成功;否则,提示用户登录失败。
@RestController
public class TestController {
@RequestMapping("/login")
public String login(String username, String password) {
if (username != null && password != null) {
if ("David".equals(username) && "123456".equals(password)) {
return username + ", 欢迎回来!";
}
}
return "您的账号或密码错误!";
}
}
接下来看下效果:
3.2 @RequestParam
Spring Boot 是一个支持海量注解的框架,因此通过注解也能够解析 URL 地址中的参数。Spring Boot 中用于标注方法参数的注解就是 @RequestParam 注解。它的作用是「显式地指定 URL 地址中的参数与方法中的参数之间的映射关系」。
@RequestParam 注解的语法如下:
@RequestMapping("/test")
public String test(@RequestParam String value1, @RequestParam String value2) {
return "";
}
为了能够深入理解 @RequestParam 注解的语法,下面介绍 @RequestParam 注解中的 3 个属性。
属性:value
@RequestParam 注解允许方法中的参数与 URL 地址中的参数不同名,value 属性用于指定 URL 地址中的参数的名称,Spring Boot 会自动将 value 属性的值注入方法参数中。value 属性是 @RequestParam 注解的默认属性,可以隐式调用。
value 属性的两种使用语法如下:
public String test(@RequestParam("n") String name) { } // 在语法格式中省略了 "value = "
public String test(@RequestParam(value = "n") String name) { } // 在语法格式中没有省略 "value = "
在上述语法中,“n” 是 URL 地址中的参数的名称。在 @RequestParam 注解的作用下,test() 方法中的参数 name 就可以得到 URL 地址中的参数 n 的值。
比如看下面的例子:获取用户发送的 token 口令。
在互联网领域内,token 用以表示指令牌。所谓指令牌,就是许可证、通行证、密码或者口令。当用户向服务器发送 token 口令时,经常把 token 缩写成 “tk”、“tn” 或 “t”。
我们修改 TestController 控制器类,将用户通过 URL 地址发送的请求中参数 tk 的值注入 login() 方法的参数 token 中,打印此口令值。
@RestController
public class TestController {
@RequestMapping("/login")
public String login(@RequestParam(value = "tk") String token) {
return "前端传递的口令为:" + token;
}
}
打开浏览器,访问 http://127.0.0.1:8080/login?tk=dh6wd84n 地址(参数 tk 的值可任意输入)。
属性:required
required 属性的作用是指定被 @RequestParam 注解标注的「方法参数是否必须被注入值」,required 属性的默认值为 true。也就是说,对于被 @RequestParam 注解标注且没有写明 “required=false” 的方法参数,一律强制把 URL 地址中的参数的值注入其中。如果 URL 地址没有此参数,就会抛出 MissingServletRequestParameterException(缺少请求参数)异常。
以下面这行代码为例,介绍 required 属性的使用方法。
public String test(@RequestParam(value = "n") String name) { }
因为 required 属性的默认值为 true,所以可以对上述代码做如下的修改:
public String test(@RequestParam(value = "n" , required = true) String name) { }
当在 @RequestParam 注解中写明 “required=false” 时,需要对上面的这行代码做如下的修改:
public String test(@RequestParam(required = false) String name) { }
需要特别说明的是,对于上述的 test() 方法,如果在 @RequestParam 注解中写明 “required=false”,就等同于如下的不被 @RequestParam 注解标注方法参数的 test() 方法:
public String test(String name) { }
属性:defaultValue
defaultValue 属性的作用是「指定方法参数的默认值」。如果 URL 地址没有 @RequestParam 注解指定的参数,@RequestParam 注解就会将默认值注入方法参数中。
比如我们看下面这个例子:如果用户没有发送用户名,则用“游客”称呼用户。
修改 TestController 控制器类,给 login() 方法中的参数 username 设定默认值(即“游客”)。如果 URL 地址没有参数 name,则让 username 取默认值。
@RestController
public class TestController {
@RequestMapping("/login")
public String login(@RequestParam(value = "name", defaultValue = "游客") String username) {
return username + "您还,欢迎访问 XXX 网站";
}
}
3.3 @RequestBody 封装 JSON
@RequestBody 注解的作用是把 URL 地址中的「键值对注入方法参数中」。如果 URL 地址中的键值对的数据类型是 JSON 类型,那么 @RequestBody 注解可以把这个 JSON 类型的数据直接封装成实体类对象。
来看个例子:将 URL 地址中的 JSON 数据封装成 People 类对象。
- 首先,定义 URL 地址中的 JSON 数据的结构。JSON 数据中只包含两个键,分别是 id 和 name。其中,id 的值为 26,name 的值为 “David”。因此,JSON 数据的结构如下:
{ "id": 26, "name": "David" }
- 然后,定义与 JSON 数据的结构对应的实体类。创建 People 类,其中包含 id 和 name 这两个属性。
public class People {
private Integer id;
private String name;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
People 类的属性与 JSON 数据中的字段是一一对应的。这样,就可以使用 @RequestBody 注解将 JSON 数据封装成 People 类的对象。
- 修改 TestController 控制器类,用于映射的 index() 方法的参数为 People 类型,并使用 @RequestBody 注解对该参数进行标注。 index() 方法具有返回值,返回的是 People 类型的对象中的 id 和 name 这两个属性的值。
@RestController
public class TestController {
@RequestMapping("/index")
public String index(@RequestBody People someone) {
return "编号:" + someone.getId() + ",用户名:" + someone.getName();
}
}
使用 Postman 模拟用户通过 URL 地址发送的请求,访问 http://127.0.0.1:8080/index 地址,在 Postman 中设置 JSON 数据,单击 Send 按钮。
注意:
只有用户通过 URL 地址发送的是 POST 类型的请求,在这个请求中才会包含键值对。
3.4 request response/session
在开发 Spring Boot 项目的过程中,有非常多的程序开发人员都在通过获取 request、response 和 session 这 3 个对象解决问题。
这 3 个对象的各自作用如下:
对象 | 说明 |
---|---|
request | 用于从用户通过 URL 地址发送的请求中接收数据 |
response | 用于对已经接收到的数据做出响应 |
session | 用于保存用户通过 URL 地址发送的请求,并跟踪这个请求的操作状态 |
那么,Spring Boot 是如何获取上述的 3 个对象呢?有如下两种方式。
注入属性
Spring Boot 会自动创建 request 对象和 response 对象的 Bean,控制器类可以直接注入这两个 Bean。其中:
- request 对象的类型是:HttpServletRequest
- response 对象的类型是:HttpServletResponse。
通过下面这段代码即可在控制器类中注入 request 对象和 response 对象的 Bean:
@Controller
public class TestController {
@Autowired
HttpServletRequest request;
@Autowired
HttpServletResponse response;
}
具备上述代码后,就可以直接在方法中调用 request 对象和 response 对象。通过 request 对象调用 getSession() 方法,就可以获取 session 对象。其中,session 对象的类型是 HttpSession。获取 session 对象的代码如下:
HttpSession session= request.getSession();
注入参数
所谓注入参数,就是直接向控制器类的用于处理 HTTP 请求的方法添加 HttpServletRequest、HttpServletResponse 和 HttpSession 类型的对象(即 request、response 和 session 这 3 个对象)的过程。
例如,同时在一个用于处理 HTTP 请求的方法中调用 request、response 和 session 对象。代码如下:
@RestController
public class TestController {
@RequestMapping("/index")
public String index(HttpServletRequest request, HttpServletResponse response, HttpSession session) {
request.setAttribute("id", "test");
response.setHeader("Host", "www.baidu.com");
session.setAttribute("userLogin", "true");
return "";
}
}
因为注入参数的方式只对参数类型有要求,对参数名称、参数顺序没有要求,所以上述用于定义 index() 方法的代码可以写成下面的形式:
public String index( HttpServletResponse rp,HttpSession s,HttpServletRequest rq) { }
在实际开发中,HttpServletRequest、HttpServletResponse 和 HttpSession 类型的对象可以与其他参数一起使用。例如:
public String index(@RequestParam("tk") String token, HttpServletRequest rq, Integer id) { }
我们测试一下:服务器返回图片
程序开发人员可以通过 response 对象向浏览器页面发送任何类型的数据,例如文字、图片、文件。我们修改 TestController 控制器类,映射 “/image” 地址,读取 URL 地址中参数 massage 的值。通过 BufferedImage 类创建图片对象,将参数 massage 中的文字显示在图片上。
@RestController
public class TestController {
@RequestMapping("/image")
public void image(String message, HttpServletResponse response) {
// 创建宽 300、高 100 的缓冲图片
BufferedImage image = new BufferedImage(300, 100, BufferedImage.TYPE_INT_RGB);
Graphics g = image.getGraphics(); // 获取绘图对象
g.setColor(Color.BLUE); // 画笔为蓝色
g.fillRect(0, 0, image.getWidth(), image.getHeight()); // 覆盖图片的实心矩形
g.setColor(Color.WHITE); // 画笔为白色
g.setFont(new Font("Times New Roman", Font.BOLD, 20)); // 字体
g.drawString(message, 10, 50); // 将参数字符串绘制在制定坐标上
try {
// 将绘制好的额图片,写入 response 的输出流中
ImageIO.write(image, "jpg", response.getOutputStream());
} catch (IOException e) {
e.printStackTrace();
}
}
}
打开浏览器,访问 “http://127.0.0.1:8080/image?massage=好好学习,天天向上” 地址:
四、RESTful 风格映射动态 URL 地址
4.1 RESTful 风格
REST 是一种软件架构规则,其全称是 representational state transfer,中文直译是「表述性状态传递」,可以简单地理解成“让 HTTP 请求用最简洁、最直观的方式来表达自己想要做什么”。基于 REST 构建的 API 就属于 RESTful 风格。
很多网站的 HTTP 请求中的参数都是通过 GET 方式发送的。例如,某用户访问某电商网站的 “/details” 地址,想获取编号为 5678 的图书的详细信息;在传入商品编号和商品类型这两个参数后,URL 地址可能是这样的:
http://127.0.0.1:8080/shop/details?id=5678&item_type=book
如果后台服务采用的是 RESTful 风格,那么获取商品详情就不需要传入任何参数了。因此,在使用 RESTful 风格后,上述的 URL 地址可能会是这样的:
http://127.0.0.1:8080/shop/book/5678
从这个地址可以看出,“商品的类型”和“编号”从 URL 地址的参数变为了 URL 地址的其中一层。这种地址也被叫作资源地址(URI),它看起来更像是在访问一个 Web 资源而不是用户通过 URL 地址发送的请求。当下,用户在互联网上已经可以看到很多采用 RESTful 风格的网站了。
例如,在京东商城上查看具体商品的地址如下:
https://item.jd.com/12185501.html
再例如,在 GitHub 上访问某个开源软件的地址如下:
https://github.com/spring-projects/spring-boot
说明:
URI 全名为 uniform resource identifier,翻译过来叫「统一资源标识符」。URL 的全名为 uniform resource locator,翻译过来叫「统一资源定位器」。注意两者的区别。
RESTful 风格也存在一个问题:仅从 URL 地址上看不出这个请求是要查询商品还是删除商品。为此,在 REST 规则中,规定以用户通过 URL 地址发送的请求的类型来决定要执行哪种业务。
例如,对于同一个 URL 地址:
- 如果用户通过 URL 地址发送的请求为 GET 请求,则表示「查询指定商品的数据」;
- 如果用户通过 URL 地址发送的请求为 POST 请求,则表示「向数据库添加指定商品」;
- 如果用户通过 URL 地址发送的请求为 DELETE 请求,则表示「删除指定商品」。
HTTP 请求包含多种类型,常用类型及其相关内容如表所示:
请求类型 | 约定的义务 | 对应枚举 | 对应注解 |
---|---|---|---|
GET | 查询资源 | RequestMethod.GET | @GetMapping |
POST | 创建资源 | RequestMethod.POST | @PostMapping |
PUT | 更新资源 | RequestMethod.PUT | @PutMapping |
PATCH | 只更新资源中一部分内容 | RequestMethod.PATCH | @PatchMapping |
DELETE | 删除资源 | RequestMethod.DELETE | @DeleteMapping |
与各个 HTTP 请求类型对应的「枚举」用于为 @RequestMapping 注解的 method 属性赋值,进而用于指定映射的是哪种类型的 HTTP 请求。例如,只映射 POST 请求的写法如下:
@RequestMapping(value="/index",method = RequestMethod.POST)
与各个 HTTP 请求类型对应的「注解」则是在@RequestMapping 注解的基础上延伸出来的新注解,这些注解的功能与 @RequestMapping 注解相同,但只能映射固定类型的请求。例如下面这个注解:
@GetMapping(value="/index")
上述注解等同于如下的注解:
@RequestMapping(value="/index",method = RequestMethod.GET)
说明:
不是所有的浏览器都支持发送多种请求类型,例如 HTML 格式的浏览器仅支持发送 GET 请求和 POST 请求,这种浏览器对于其他类型的 HTTP 请求需要借助 JavaScript 格式的浏览器予以发送。
4.2 映射动态 URL 地址
服务器使用 RESTful 风格就意味着控制器类所映射的 URL 地址不是唯一的,每一个数据的 URL 地址都不同。这就需要让控制器类能够映射动态 URL 地址。Spring Boot 使用英文格式的 “{}” 作为动态 URL 地址中的「占位符」,例如 “/shop/book/{type}” 就表示这个 URL 地址中的 “/shop/book/” 是固定的,“/{type}” 是动态的,该动态地址可以成功匹配下面这些地址:
http://127.0.0.1:8080/shop/book/music
http://127.0.0.1:8080/shop/book/FOOD
http://127.0.0.1:8080/shop/book/123456789
http://127.0.0.1:8080/shop/book/+-*_!#&$
http://127.0.0.1:8080/shop/book/{}()<>
http://127.0.0.1:8080/shop/book/数学
http://127.0.0.1:8080/shop/book/(空格)
……
为了解析动态 URL 地址中的占位符,Spring Boot 提供了 @PathVariable 注解。@PathVariable 注解可以将 URL 相应位置的值注入方法参数。例如下面这段代码:
@RequestMapping(value = "/shop/{type}/{id}")
public String shop(@PathVariable String type, @PathVariable String id) { }
@PathVariable 注解可以将 URL 地址中与 {type} 占位符对应位置的值注入 shop() 方法中的参数 type,将 URL 地址中与 {id} 对应位置的值注入 shop() 方法中的参数 id。因为 @PathVariable 注解会根据方法参数的名称进行匹配,所以方法参数的先后顺序不影响注入的结果。
在实际开发中,程序开发人员也可以指定方法参数对应哪个占位符,只需将 @PathVariable 注解的 value 属性赋值为占位符名称即可。例如,方法参数的名称分别为 goodsType 和 goodsID,它们各自要匹配的占位符分别是 {type} 和 {id}。代码如下:
@RequestMapping(value = "/shop/{type}/{id}")
public String shop(@PathVariable(value = "type") String goodsType,
@PathVariable(value = "id") String goodsID) { }
因为 @PathVariable 注解的 value 属性可以被隐式调用,所以上述代码可以做如下修改:
@RequestMapping(value = "/shop/{type}/{id}")
public String shop(@PathVariable("type") String goodsType, @PathVariable("id") String goodsID) { }