Spring Boot 学习之路 -- 处理 HTTP 请求
前言
- 最近因为业务需要,被拉去研究后端的项目,代码框架基于 Spring Boot,对我来说完全小白,需要重新学习研究…
- 出于个人习惯,会以 Blog 文章的方式做一些记录,文章内容基本来源于「 Spring Boot 从入门到精通(明日科技) 」一书,做了一些整理,更易于个人理解和回顾查找,所以大家如果希望更系统性的学习,可以阅读此书(比较适合我这种新手)。
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 注解标注,所以控制器类属于组件。这说明在启动 Spring Boot 项目时,控制器类会被扫描器自动扫描。这样,程序开发人员就可以在控制器类中注入 Bean。例如,在控制器中注入 Environment 环境组件,代码如下:
@Controller
public class TestController {
@Autowired
Environment env;
}
1.2 使用 @RequestMapping 映射 URL 地址
Spring Boot 提供了用于映射 URL 地址的 @RequestMapping 注解。@RequestMapping 注解可以标注类和方法。如果一个类或者方法被 @RequestMapping 注解标注,那么这个类或者方法就能够处理用户通过 @RequestMapping 注解映射的 URL 地址发送的请求。
下面将首先介绍 @RequestMapping 注解的属性,然后介绍如何使用 @RequestMapping 注解映射包含层级关系的 URL 地址。
注意:
@Controller 注解要结合 @RequestMapping 注解一起使用。
1.2.1 @RequestMapping 注解的属性
@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 项目,那么域名就是 127.0.0.1:8080。
比如我们修改之前文章的 BeanTestController:
@Controller
public class BeanTestController {@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 属性的用法,我们修改 controller 类,如果由 “/index” 地址发送的请求的类型是 GET,则打印“处理 GET 请求”;如果由 “/index” 地址发送的请求的类型是 POST 请求,则打印“处理 POST 请求”,代码如下:
@Controller
public class BeanTestController {@RequestMapping(value = "/index", method = RequestMethod.GET)@ResponseBodypublic String get(){return "处理 GET 请求";}@RequestMapping(value = "/index", method = RequestMethod.POST)@ResponseBodypublic 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 属性的用法,我们修改 Controller:
@Controller
public class BeanTestController {@RequestMapping(value = "/index", params = { "name", "id" })@ResponseBodypublic String haveParams(){return "欢迎回来~";}@RequestMapping(value = "/index")@ResponseBodypublic String noParams(){return "忘传参数了...";}
}
使用 Postman 模拟用户通过 URL 地址发送的请求。查看结果:
headers 属性
headers 属性能够指定在用户通过 @RequestMapping 注解映射的 URL 地址发送的请求中须包含哪些指定的请求头。也就是说,在一个 headers 属性中,可以包含若干个请求头。通过这些请求头,服务器能够得知客户端环境以及与请求正文相关的一些信息,例如浏览器的版本、请求参数的长度等。
headers 属性在 @RequestMapping 注解中的格式如下:
@RequestMapping(headers = {"键1=值1", "键2=值2", ......})
说明:
请求头指的是 HTTP 请求中的头部信息,即用于 HTTP 通信的操作参数。
从 headers 属性在 @RequestMapping 注解中的格式,能够非常清晰地看到请求头在 headers 属性中的格式:
"键=值"
我们测试下,修改 Controller,在控制器类中编写 noParams() 和 haveParams() 这两个方法。如果用户通过 URL 地址发送的请求包含 headers 属性,就交由 noParams() 方法处理,让用户直接进入欢迎界面;否则,就交由 haveParams() 方法处理,让用户进入登录界面。
@Controller
public class BeanTestController {@RequestMapping(value = "/index")@ResponseBodypublic String haveParams(){return "请重新登录!";}@RequestMapping(value = "/index", headers = { "Cookie=JSESSIONID=123456789"})@ResponseBodypublic String noParams(){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 属性的用法。
修改 Controller 类,将 @RequestMapping 注解的 consumes 属性设置为“application/json”。在控制器类中编写 formatError() 和 hello() 这两个方法。如果用户发送的请求的数据类型是 JSON,就交由 hello() 方法处理,并提示“成功进入接口”的信息;否则,就交由 formatError() 方法处理,并提示“数据格式错误!”的信息。
@Controller
public class BeanTestController {@RequestMapping(value = "/index")@ResponseBodypublic String formatError(){return "数据格式错误!";}@RequestMapping(value = "/index", consumes = "application/json")@ResponseBodypublic String hello(){return "成功进入接口";}
}
使用 Postman 模拟用户通过URL地址发送的请求。如果直接访问 http://127.0.0.1:8080/index 地址,结果如下:
如果在请求体(Body)中填写 JSON 数据,再访问上述地址就可以看到下图结果:
1.2.2 映射包含层级关系的 URL 地址
通常一个 URL 地址不只是简单的一层地址,而是根据业务分类形成的多层地址。那么,如何理解在一个 URL 地址中包含多层地址呢?例如,在某电商平台通过访问 “/shop/books” 地址查看图书信息;其中,“/shop” 是这个电商平台的地址,“/books” 表示图书类。那么,应该如何使用 @RequestMapping 注解映射这个包含层级关系的 URL 地址呢?代码如下:
@Controller
public class BeanTestController {@RequestMapping("/shop/books")@ResponseBodypublic String books() {return "图书类";}
}
不难发现,在表示电商平台的控制器类中包含一个 book() 方法。通过使用 @RequestMapping 注解标注这个 book() 方法,就能够实现映射一个多层的 URL 地址的功能。
如果这个电商平台还卖服装,那么就会含有表示服装类的 “/clothes” 地址。那么,又应该如何使用 @RequestMapping 注解既映射 “/shop/books” 地址,又映射 “/shop/clothes” 地址呢?代码如下:
@Controller
public class BeanTestController {@RequestMapping("/shop/clothes")@ResponseBodypublic String cloths() {return "服饰类";}@RequestMapping("/shop/books")@ResponseBodypublic String books() {return "图书类";}
}
不难发现,“/shop/books” 和 “/shop/clothes” 这两个地址具有相同的上层地址 “/shop”。那么,有没有什么编码方式能够优化上述代码呢?答案是使用 @RequestMapping(“/shop”) 注解标注表示电商平台的控制器类。
优化后的代码如下:
@Controller
@RequestMapping("/shop")
public class BeanTestController {@RequestMapping("/clothes")@ResponseBodypublic String cloths() {return "服饰类";}@RequestMapping("/books")@ResponseBodypublic 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 BeanTestController {@RequestMapping("/index") // 映射 "/index" 地址,未标注 @ResponseBodypublic 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" 地址,未标注 @ResponseBodypublic String index(){return "/welcome"; // 跳转至 "/welcome" 地址}
}
通过上述代码,是不是就能够实现跳转页面的功能了呢?答案是否定的。为了实现跳转页面的功能,还需要向上述代码添加用于映射 “/welcome” 地址的方法,并且这个方法要被 @RequestMapping 和 @ResponseBody 注解同时标注。添加用于映射 “/welcome” 地址的方法后的代码如下:
@Controller
public class BeanTestController {@RequestMapping("/index")public String index(){return "/welcome";}@RequestMapping("/welcome")@ResponseBodypublic 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 注解可以简化上述代码。简化后的代码如下:
@RestControlle
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 地址,即可看到百度首页。