请求处理与响应处理
声明:本文章属于学习笔记,根据尚硅谷雷丰阳老师的SpringBoot编写
Spring官方文档
这里写目录标题
一丶Rest映射及源码解析
1丶Rest映射
我们都知道rest风格在我们进行野页面问的时候风格是这个样子的:/getUser 获取用户 /deleteUser 删除用户 /editUser 修改用户 /saveUser 保存用户
但是现在我们要是访问方式是这样的:
现在: /user GET-获取用户 DELETE-删除用户 PUT-修改用户 POST-保存用户
所以我们在前台表单是这样的:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<form action="/user" method="get">
<input value="REST-GET 提交" type="submit"/>
</form>
<form action="/user" method="post">
<input value="REST-POST 提交" type="submit"/>
</form>
<form action="/user" method="delete">
<input value="REST-DELETE 提交" type="submit"/>
</form>
<form action="/user" method="put">
<input value="REST-PUT 提交" type="submit"/>
</form>
</body>
</html>
我们以前在使用rest风格的时候只要Controller提供相应的映射就可以访问的到:/getUser 获取用户 /deleteUser 删除用户 /editUser 修改用户 /saveUser 保存用户
但我我们改变了之后我们要怎么才可以访问到呢?
我们来进行测试一下:
package com.kdy.boot05web01.controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
/**
* @author kdy
* @create 2021-07-28 14:10
*/
@RestController
public class HelloController {
@RequestMapping(value = "/user", method = RequestMethod.GET)
public String getUser() {
return "GET-张三";
}
@RequestMapping(value = "/user", method = RequestMethod.POST)
public String saveUser() {
return "POST-张三";
}
@RequestMapping(value = "/user", method = RequestMethod.PUT)
public String putUser() {
return "PUT-张三";
}
@RequestMapping(value = "/user", method = RequestMethod.DELETE)
public String deleteUser() {
return "DELETE-张三";
}
}
我们分别点击这个四个按钮:
我们发现我们点击DELETE和 PUT都出现了一个情况:
所以在这种情况下我们还要加以改进。
我们看底层源码:
所以说我们获取前台的表单 this.methodParam之后这个指其实就是_method。
前端界面修改:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<form action="/user" method="get">
<input value="REST-GET 提交" type="submit"/>
</form>
<form action="/user" method="post">
<input value="REST-POST 提交" type="submit"/>
</form>
<form action="/user" method="post">
//必须在这里添加隐藏页
<input name="_method" value="DELETE" type="hidden"/>
<input value="REST-DELETE 提交" type="submit"/>
</form>
<form action="/user" method="post">
<input name="_method" value="PUT" type="hidden"/>
<input value="REST-PUT 提交" type="submit"/>
</form>
</body>
</html>
ymal配置:
spring:
mvc:
hiddenmethod:
filter:
enabled: true
运行结果:
我们点击DELETE是显示出这个来了。
但是为什么我们要这么写?我们必须从源码进行解读。
2丶源码解析
if ("POST".equals(request.getMethod()) && request.getAttribute(WebUtils.ERROR_EXCEPTION_ATTRIBUTE) == null) {
String paramValue = request.getParameter(this.methodParam);
if (StringUtils.hasLength(paramValue)) {
String method = paramValue.toUpperCase(Locale.ENGLISH);
if (ALLOWED_METHODS.contains(method)) {
requestToUse = new HttpMethodRequestWrapper(request, method);
}
}
}
我们进行debug:
首先我们可以看见:
这里就是程序能否继续向下执行的条件。这也照应了上面的表单请求,必须为post请求才可以。
这个参数再上面也看见过他其实就是_method。所以说,我们首先要看看上面的条件语句也就是请求是否为post,之后才可以执行下面的操作。获取_method的值。在这里也就是DELETE。
之后就是进行转换,我在前台表单中无论写的value值是大还是小,都是一样转化成大写的DELETE。之后进行判断这个值中是否含有DELETE这个值,我们点进去之后会发现:
只包括DELETE,PUT,PATCH这三个值。
之后转到这个请求转发里,我们进入源码其实可以看出,他就是原生的HttpServletRequest
之后调用重写的getMrthod方法,把method传入到HttpMethodRequestWrapper构造方法中,返回给requestToUse。之后通过doFileter方法拦截请求:
二丶常用参数注解使用
首先我们看这样的一个案例:
1丶@PathVariable(路径变量)
package com.kdy.boot05web01.controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import java.util.HashMap;
import java.util.Map;
/**
* @author kdy
* @create 2021-08-15 20:40
*/
@RestController
public class ParameterController {
@GetMapping("car/{id}/owner/{username}")
public Map tset(@PathVariable("id") Integer id,@PathVariable("username") String name,
@PathVariable Map<String,String> map){
HashMap<String, Object> hashMap = new HashMap<>();
hashMap.put("id",id);
hashMap.put("name",name);
hashMap.put("map",map);
return hashMap;
}
}
之后我们看这个前台请求的链接:
<ul>
<a href="car/4/owner/kdy">/car/{id}/owner/{username}</a>
</ul>
我们看见这个是可以获取到的。
2丶@RequestHeader (获取响应头):
3丶@RequestParam (获取请求参数):
当我们在路径变量中没有写入某个变量的时候,我们可以用@RequestParam获取请求参数
<a href="car/4/owner/kdy?age=18&inters=basketball">/car/{id}/owner/{username}</a>
package com.kdy.boot05web01.controller;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.Cookie;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* @author kdy
* @create 2021-08-15 20:40
*/
@RestController
public class ParameterController {
@GetMapping("/car/{id}/owner/{username}")
public Map<String,Object> getCar(@PathVariable("id") Integer id,
@PathVariable("username") String name,
@PathVariable Map<String,String> pv,
@RequestHeader("User-Agent") String userAgent,
@RequestHeader Map<String,String> header,
@RequestParam("age") Integer age,
@RequestParam("inters") List<String> inters,
@RequestParam Map<String,String> params){
Map<String,Object> map = new HashMap<>();
// map.put("id",id);
// map.put("name",name);
// map.put("pv",pv);
// map.put("userAgent",userAgent);
// map.put("headers",header);
map.put("age",age);
map.put("inters",inters);
map.put("params",params);
return map;
}
}
4丶@CookieValue(获取cooki的值):
package com.kdy.boot05web01.controller;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.Cookie;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* @author kdy
* @create 2021-08-15 20:40
*/
@RestController
public class ParameterController {
@GetMapping("/car/{id}/owner/{username}")
public Map<String,Object> getCar(@PathVariable("id") Integer id,
@PathVariable("username") String name,
@PathVariable Map<String,String> pv,
@RequestHeader("User-Agent") String userAgent,
@RequestHeader Map<String,String> header,
@RequestParam("age") Integer age,
@RequestParam("inters") List<String> inters,
@RequestParam Map<String,String> params,
@CookieValue("ga") String ga,
@CookieValue("ga") Cookie cookie){
Map<String,Object> map = new HashMap<>();
// map.put("id",id);
// map.put("name",name);
// map.put("pv",pv);
// map.put("userAgent",userAgent);
// map.put("headers",header);
map.put("age",age);
map.put("inters",inters);
map.put("params",params);
map.put("ga",ga);
System.out.println(cookie.getName()+"===>"+cookie.getValue());
return map;
}
}
5丶@RequestAttribute(获取request域的属性)
我们可以通过跳转的方式来获取request域的属性:
package com.kdy.boot05web01.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestAttribute;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Map;
/**
* @author kdy
* @create 2021-08-16 13:33
*/
@Controller
public class RequestAttritubeController {
@GetMapping("/goto")
public String into(HttpServletRequest request){
request.setAttribute("msg","kdy");
request.setAttribute("code","adada");
return "forward:/success";
}
@ResponseBody
@GetMapping("/success")
public Map<String, Object> success(@RequestAttribute("msg") String msg, @RequestAttribute("code") String code ,
HttpServletRequest request){
Object msg1 = request.getAttribute("msg");
HashMap<String, Object> map = new HashMap<>();
map.put("msg1",msg1);
map.put("msg",msg);
return map;
}
}
运行结果:
6丶@MatrixVariable(矩阵变量)
首先我们要明白这个三种请求方式:
queryString请求方式
/request?username=admin&password=123456&age=20
rest风格请求
/request/admin/12/20
MatrixVariable矩阵变量
/request;username=admin;password=1236;age=20
由于SpringBoot中默认并没有开启矩阵变量的支持,直接关闭了矩阵变量。因此在使用的时候我们需要对SpringBoot自动装配的Bean对象进行手动的配置更改:
package com.kdy.boot05web01.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.PathMatchConfigurer;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.util.UrlPathHelper;
/**
* @author kdy
* @create 2021-08-16 19:29
*/
@Configuration(proxyBeanMethods = false)
public class WebConfig implements WebMvcConfigurer {
//方法二:
/* @Bean
public WebMvcConfigurer webMvcConfigurer(){
return new WebMvcConfigurer() {
@Override
public void configurePathMatch(PathMatchConfigurer configurer) {
UrlPathHelper pathHelper = new UrlPathHelper();
//不移除。默认的方法才不生效
pathHelper.setRemoveSemicolonContent(false);
configurer.setUrlPathHelper(pathHelper);
}
};
}*/
//方法一:
@Override
public void configurePathMatch(PathMatchConfigurer configurer) {
UrlPathHelper pathHelper = new UrlPathHelper();
//不移除。默认的方法才不生效
pathHelper.setRemoveSemicolonContent(false);
configurer.setUrlPathHelper(pathHelper);
}
}
由于MatrixVariable矩阵变量是根据分号进行区分的,而底层默认是移除的也就是说不生效,所以我们才需要进行配置。
前台:
测试:
@GetMapping("/cars/{path}")
public Map carsSell(@MatrixVariable("kk") Integer kk,
@MatrixVariable("brand") List<String> brand,
@PathVariable("path") String path){
Map<String,Object> map = new HashMap<>();
map.put("kk",kk);
map.put("brand",brand);
map.put("path",path);
return map;
}
运行结果:
三丶数据响应与内容协商
1丶响应JSON
首先我们是怎么样给前端响应json的,我们还要结合案例和源码一起探究。
导入依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
json依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-json</artifactId>
<version>2.3.4.RELEASE</version>
<scope>compile</scope>
</dependency>
我们必须要导入json依赖才可以进行案例分析。
@Controller
public class ResponsePersonController {
@ResponseBody
@GetMapping("/test/person")
public Person person(){
Person person = new Person();
person.setName("kdy");
person.setAge(21);
return person;
}
}
所以这段代码是怎们运行的?
我们一起看一看
debug进入源码到返回值解析器(我们在探究json的时候看的是返回值解析器,其实就像我们在探究各种参数注解时候时候用的参数解析器一样,是一个道理。):
@Override
public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
HandlerMethodReturnValueHandler handler = selectHandler(returnValue, returnType);
if (handler == null) {
throw new IllegalArgumentException("Unknown return value type: " + returnType.getParameterType().getName());
}
handler.handleReturnValue(returnValue, returnType, mavContainer, webRequest);
}
首先要看对象可以让那个返回值处理器处理,之后再交给返回值处理器进行处理。
在这里要看这个对象是不是异步的也即是不是json,当然现在还没有处理肯定就不是,所以返回false。之后就会交给返回值解析器进行处理,看这个对象类型是否支持处理:
之后会依次判断那个可以处理这个Person对象:
ModelAndView
Model
View
ResponseEntity
ResponseBodyEmitter
StreamingResponseBody
HttpEntity
HttpHeaders
Callable
DeferredResult
ListenableFuture
CompletionStage
WebAsyncTask
RequestResponseBodyMethodProcessor;
到最后只有RequestResponseBodyMethodProcessor支持:
他是要判断是这个类中有@ResponseBody注解,才可以处理返回值。最后时候消息转换器进行写出操作转换成json串:MessageConverters.
我们看这样的一张图片:
浏览器默认会以请求头的方式告诉服务器他能接受什么样的内容类型。其中Accept就是浏览器想要接收的类型。也就是内容协商。之后服务器最终根据自己自身的能力,决定服务器能生产出什么样内容类型的数据
一旦匹配上就开始执行。之后SpringMVC会挨个遍历所有容器底层HttpMessageConverter 看谁能处理:
最终 MappingJackson2HttpMessageConverter 把对象转为JSON(利用底层的jackson的objectMapper转换的),这就是person转换成json串的底层原理。
我们也可以发现返回值解析器原理:
1、返回值处理器判断是否支持这种类型返回值 supportsReturnType
2、返回值处理器调用 handleReturnValue 进行处理
3、RequestResponseBodyMethodProcessor 可以处理返回值标了@ResponseBody 注解的。
- 利用 MessageConverters 进行处理 将数据写为json
1、内容协商(浏览器默认会以请求头的方式告诉服务器他能接受什么样的内容类型)
2、服务器最终根据自己自身的能力,决定服务器能生产出什么样内容类型的数据,
3、SpringMVC会挨个遍历所有容器底层的 HttpMessageConverter ,看谁能处理:
(1)、得到MappingJackson2HttpMessageConverter可以将对象写为json
(2)、利用MappingJackson2HttpMessageConverter将对象转为json再写出去。
2丶内容协商
上面已经简单的介绍过内容协商了。现在我们要进一步的了解探究一下。
首先我们看这样的一个案例:
我们现在看到的是这样的格式:
我们可以看见请求头接收的先后顺序,他是比json要早的。我们可以用postman进行测试一下:
我们可以看见Accept接收的是* / * 所以返回的是什么形式都行,但是当我们修改的时候,我们可以看见:
不同的Accept会产生不同的文件形式,这也就是SpringMvc在底层提供的内容协商功能。他会根据客户端的能力的不同来处理数据。
原理:
HeaderContentNegotiationStrategy 确定客户端可以接收的内容类型
之后遍历循环所有当前系统的 MessageConverter,看谁支持操作这个对象(Person)
找到支持操作Person的converter,把converter支持的媒体类型统计出来。客户端需要【application/xml】。服务端能力【10种、json、xml】,进行内容协商的最佳匹配媒体类型
用 支持 将对象转为 最佳匹配媒体类型 的converter。调用它进行转化 。
四丶视图解析
1丶 模板引擎-Thymeleaf
首先我们用Thymeleaf来看一个小案例:
我们前端首先用一下这个Thymeleaf的模板:
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1 th:text="${msg}">哈哈</h1>
<h2>
<a href="www.atguigu.com" th:href="${link}">去小破站</a> <br/>
<a href="www.atguigu.com" th:href="@{/link}">去小破站2</a>
</h2>
</body>
</html>
controller:
package com.kdy.boot05web01.controller;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
/**
* @author kdy
* @create 2021-08-21 11:34
*/
@Controller
public class SuccessController {
@GetMapping("/su")
public String success(Model model){
model.addAttribute("msg","kdy");
model.addAttribute("link","https://www.bilibili.com/");
return "success";
}
}
我们看下面的一张图:
${link}也就是你要去的地址,@{/link}也就是你要去的当前项目的根路径下的link,也就是我们本项目的的link,这也就是 $ 和 @的区别。