文章目录
1. Web请求参数处理 Rest映射源码解析
1.1 OrderedHiddenHttpMethodFilter对象原理
Rest风格:
- 注意这里是Rest风格,并不是Restful风格不要混淆!
- 可以直接认为Rest风格就是@RestController和@ResponseBody。
package com.itholmes.boot.controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
//RestController就是rest风格,还有@Responsebody
@RestController
public class HelloController {
//相同的路径/user,来处理不同的请求类型。
@RequestMapping(value = "/user" ,method = RequestMethod.GET)
//等同于@GetMapping("/user")
public String getUser(){
return "GET-张三";
}
@RequestMapping(value = "/user" ,method = RequestMethod.POST)
//等同于@PostMapping("/user")
public String saveUser(){
return "POST-张三";
}
@RequestMapping(value = "/user" ,method = RequestMethod.PUT)
//等同于@PutMapping("/user")
public String putUser(){
return "PUT-张三";
}
@RequestMapping(value = "/user" ,method = RequestMethod.DELETE)
//等同于@DeleteMapping("/user")
public String deleteUser(){
return "DELETE-张三";
}
}
对于rest风格的参数类型处理,必须要给容器中注入一个OrderedHiddenHttpMethodFilter对象,这个对象也是在WebMvcAutoConfiguration类自动配置注入。
在WebMvcAutoConfiguration类下,有一个hiddenHttpMethodFilter(排序隐藏Http方法过滤器)方法,这个方法的作用是过滤请求参数的方法类型,例如:get,post,delete,put等等。注意这个hiddenHttpMethodFilter装配到容器中,该对象本身是一个filter过滤器!
@Bean
@ConditionalOnMissingBean({HiddenHttpMethodFilter.class})
@ConditionalOnProperty(
prefix = "spring.mvc.hiddenmethod.filter",
name = {"enabled"}
)
public OrderedHiddenHttpMethodFilter hiddenHttpMethodFilter() {
return new OrderedHiddenHttpMethodFilter();
}
但是在@ConditionalOnProperty注解中有一个matchIfMissing注解属性,默认是false的!这个属性的功能如下:
/**
*该属性为true时,配置文件中缺少对应的value或name的对应的属性值,也会注入成功
*/
boolean matchIfMissing() default false;
因为是matchIfMissing默认是false,所以开始的HiddenHttpMethodFilter过滤器是没有装配到容器中的,因此我们要设置spring.mvc.hiddenmethod.filter.enabled=true来启动该过滤器(装配到容器中)。
可以理解的是spring.mvc.hiddenmethod.filter.enabled=true是专门用来处理表单的Rest功能。
spring:
mvc:
hiddenmethod:
filter:
enabled: true # 启用HiddenHttpMethodFilter过滤器,专门用来处理表单传送过来的_method的参数数据,进而来出来delete,put等类型参数。
1.2 表单提交要使用Rest的使用
对应上面内容。
这样我们在前端发送不同类型,后台就可以处理了:
- 表单提交要带上_method=PUT , 这样的方式来能让SpringBoot自动配置的hiddenHttpMethodFilter方法识别到!
- 再次提醒,不要忘记启动hiddenHttpMethodFilter组件对象,也就是必须要装配到容器中(被启动)。因为上面的这种方式必须基于该组件对象。
测试REST风格:
<form action="/user" method="get">
<input type="submit" value="REST-GET">
</form>
<form action="/user" method="POST">
<input type="submit" value="REST-POST">
</form>
<!--
form表单是不能提交delete,put等类型的,直接定义这些类型,form表单还是走默认的get方法。
根据hiddenHttpMethodFilter方法源码:通过传递一个name为"_method"值的参数来指定,并且方法的类型必须是post方法,因为它的源码规定这里必须是post方式!。
-->
<form action="/user" method="post">
<input name="_method" type="hidden" value="DELETE">
<input type="submit" value="REST-delete">
</form>
<form action="/user" method="post">
<input name="_method" type="hidden" value="PUT">
<input type="submit" value="REST-put">
</form>
hiddenHttpMethodFilter在容器中本质是一个OrderedHiddenHttpMethodFilter对象,该对象又继承了HiddenHttpMethodFilter对象,在HiddenHttpMethodFilter对象中,有一个doFilterInternal方法:
private static final List<String> ALLOWED_METHODS;
static {
//这里定义支持哪几种请求,有delete,put,patch这三种。也就是允许这三种请求。
ALLOWED_METHODS = Collections.unmodifiableList(Arrays.asList(HttpMethod.PUT.name(), HttpMethod.DELETE.name(), HttpMethod.PATCH.name()));
}
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
//拿到当前的请求request
HttpServletRequest requestToUse = request;
//先判断是不是post方法,也说明了前面form表单传递_method=PUT形式为什么要用post,这里规定的就是post。并且判断请求是否正常,有没有error。
if ("POST".equals(request.getMethod()) && request.getAttribute("javax.servlet.error.exception") == null) {
//在request获取参数,这里的this.methodParam是整个类写死的一个值就是"_method";
//也就是在request中获取到了_method的参数了,也就是form表单传的delete,put等等。
String paramValue = request.getParameter(this.methodParam);
//这里判断有没有长度,也就是判断是不是空
if (StringUtils.hasLength(paramValue)) {
//将值进行一个大写转换。也就迎合了前端发送大写小的方法名都可以。
String method = paramValue.toUpperCase(Locale.ENGLISH);
//对应上面ALLOWED_METHODS,判断是否属于ALLOWED_METHODS,仅能支持delete,put,patch。
if (ALLOWED_METHODS.contains(method)) {
//将原生request(post请求) , 包装模式requestWrapper重写了getMethod方法,返回的是重写后的request(就是这里的method请求[delete,put,patch请求])
requestToUse = new HiddenHttpMethodFilter.HttpMethodRequestWrapper(request, method);
}
}
}
//之后就是将处理后的requestToUse放行了。过滤器放行。
filterChain.doFilter((ServletRequest)requestToUse, response);
}
==========================================================================================
//对应上面的HttpMethodRequestWrapper
private static class HttpMethodRequestWrapper extends HttpServletRequestWrapper {
private final String method;
public HttpMethodRequestWrapper(HttpServletRequest request, String method) {
super(request);
this.method = method;
}
//这里将request的值重写了,也就是原来是request的method是post,现在重写为了根据_method传入的类型(delete,put,patch)
public String getMethod() {
return this.method;
}
}
这样也就将原来的post请求类型,通过OrderedHiddenHttpMethodFilter过滤器转成了不同的请求类型。
1.3 Rest使用客户端工具发送不同类型的请求
例如:使用postman发送的put参数。
也就是不影响原生的功能。
1.4 如何改变默认的_method?
自己创建一个HiddenHttpMethodFilter对象,放入容器中。
自己写一个配置类,注入到容器中:
package com.itholmes.boot.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.filter.HiddenHttpMethodFilter;
@Configuration(proxyBeanMethods = false)
public class WebConfig {
@Bean
//注意这里有两个HiddenHttpMethodFilter对象,一个是普通servlet使用的,一个是响应式使用的。
public HiddenHttpMethodFilter hiddenHttpMethodFilter(){
HiddenHttpMethodFilter hiddenHttpMethodFilter = new HiddenHttpMethodFilter();
//设置为自己想要的名字。
hiddenHttpMethodFilter.setMethodParam("_m");
return hiddenHttpMethodFilter;
}
}
2. Web请求参数处理 请求映射原理
DispatcherServlet类实现的过程,例如,实现get方法的过程,也是从上往下:
SpringMvc功能分析都从org.springframework.web.servlet.DispatcherServlet =》doDispatch()方法实现。
分析doDispatch方法:
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;
boolean multipartRequestParsed = false;
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
try {
try {
ModelAndView mv = null;
Object dispatchException = null;
try {
processedRequest = this.checkMultipart(request);
multipartRequestParsed = processedRequest != request;
//找到当前请求使用哪个Handler(Controller的方法)处理。
mappedHandler = this.getHandler(processedRequest);
=================================================================
//查看getHandler方法,里面有一个HandlerMapping处理器映射。
@Nullable
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
if (this.handlerMappings != null) {
Iterator var2 = this.handlerMappings.iterator();
while(var2.hasNext()) {
HandlerMapping mapping = (HandlerMapping)var2.next();
HandlerExecutionChain handler = mapping.getHandler(request);
if (handler != null) {
return handler;
}
}
}
return null;
}
通过发送一个路径请求,查看handlerMappings中的内容:
- RequestMappingHandlerMapping作用就是处理所有@RequestMapping注解 和 handler的映射规则。
- WelcomePageHandlerMapping是处理欢迎页功能的映射。
SpringBoot自动配置欢迎页的WelcomePageHandlerMapping的源码分析(访问 " / " 能访问到index.html):
因为这里while循环的遍历,依次尝试所有的HandlerMapping看是否有对应路径的请求信息
- 如果找到了对应的请求handler就会return handler。
- 如果没有找到就继续遍历下一个handler。
总结起来就是知道RequestMappingHandlerMappin和WelcomePageHandlerMapping在SpringMvc中什么作用!剩余的三个了解
此外,还可以自定义映射处理,我们也可以自己给容器中放HandlerMapping。自定义HandlerMapping。
3. 请求处理常用的参数注解
3.1 SpringMvc常用的一些注解
@PathVariable的用法:
- @PathVariable(“id”) Integer id://@PathVariable(“id”)拿到对应的值。
- @PathVariable Map<String,String> path_value://拿到所有的@PathVariable注解对应的key-value(也就是指定@PathVariable注解的value拿指定的,没有指定就拿所有和@PathVariable绑定的key-value。)
@RequestHeader的用法:
- @RequestHeader(“User-Agent”) String userAgent://@RequestHeader注解拿到请求头的信息。
- @RequestHeader Map<String,String> headers://和上面一样,拿到所有的请求头信息。指定@RequestHeader注解的value值就拿到对应指定的,没有指定就拿全部的。
@RequestParam的用法:
- @RequestParam(“age”) Integer age://接受form表单格式单个。
- @RequestParam(“inters”) List inters://当form表单对一个key指定了多个value可以使用list来接受,例如: ?inters=1&inters=2&inters=4。
- @RequestParam Map<String,String> params://和上面一样,获取全部的form表单形式的参数。
@CookieValue的用法:
- @CookieValue(“token”) String value://在cookie中拿到token的value值。
- @CookieValue(“token”) Cookie cookie://拿到token的整个cookie。
@RequestBody的用法:
- @RequestBody String content://如果是form表单提交,例如:name=zhangsan&pwd=123123 , 这里的content就会变成{“name”:“zhangsan”,“pwd”:“123123”}
@RequestAttribute的用法:
3.2 SpringBoot的矩阵变量 和 UrlPathHelper类
什么是矩阵变量,矩阵变量以及对比其他的方式?
- /cars/path?xxx=xxx&aaa=ccc 这可以是queryString查询字符串,后台用@RequestParam接受。
- /cars/sell;low=34;brand=byd,audi,yd 使用" ; "的这种形式叫做矩阵变量。
页面开发,cookie禁用了,session里面的内容怎么使用?
- 可以使用url重写:/abc;jsessionid=xxxx 把cookie的值使用矩阵变量的方式进行传递。
/boss/1/2 对比 /boss/1;age=20/2;age=18 分号;前面是路径,后面是矩阵变量。
SpringBoot默认是禁用了矩阵变量的功能。
原理:在WebMvcAutoConfiguration中,有一个configurePathMatch方法,在该方法中有一个UrlPathHelper(Url路径帮助器) 类,在这个类中,有一个removeSemicolonContent(Semicolon分号)成员变量属性,这个属性就是规定SpringBoot支不支持矩阵变量的(也就是是否移除分号后面的内容。)。
UrlPathHelper类有很多的功能:
如何开启SpringBoot支持矩阵变量的功能?
- 可以在配置类中自定义配置WebMvcConfigurer容器对象,来修改UrlPathHelper对象中的removeSemicolonContent变量。
package com.itholmes.boot.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.filter.HiddenHttpMethodFilter;
import org.springframework.web.servlet.config.annotation.PathMatchConfigurer;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.util.UrlPathHelper;
@Configuration(proxyBeanMethods = false)
/*
第一种方式:直接implement的WebMvcConfigurer。
并且重写configure
*/
public class WebConfig implements WebMvcConfigurer {
@Override
public void configurePathMatch(PathMatchConfigurer configurer) {
UrlPathHelper urlPathHelper = new UrlPathHelper();
//设置为false,也就是不移除分号后面的内容,这样矩阵变量可以生效。
urlPathHelper.setRemoveSemicolonContent(false);
configurer.setUrlPathHelper(urlPathHelper);
}
/*
第二种方式:
使用@Bean,直接向容器中装配一个WebMvcConfigurer。
@Bean
public WebMvcConfigurer webMvcConfigurer(){
return new WebMvcConfigurer() {
@Override
public void configurePathMatch(PathMatchConfigurer configurer) {
UrlPathHelper urlPathHelper = new UrlPathHelper();
//设置为false,也就是不移除分号后面的内容,这样矩阵变量可以生效。
urlPathHelper.setRemoveSemicolonContent(false);
configurer.setUrlPathHelper(urlPathHelper);
}
};
}
*/
}
获取矩形变量也有格式上的一些要求:
- 例如: “/cars/{path}” 。
- @PathVariable(“path”) String path拿到的就是path里面分号前面的路径。
- @MatrixVariable(“xxx”) 拿到的就是矩形变量xxx对应的内容。
/*
矩形变量注意点:
1.SpringBoot默认是禁用了矩形变量的功能。
2.矩形变量必须有url路径变量才能被解析,也就是/cars/sell;low=34 => {path} sell就是路径,low就是矩形变量。
*/
//第一种情况: 一个key对应一个值,一个key对应多个值。
// 例如矩阵变量的语法为: /cars/sell;low=34;brand=byd,audi,yd
@GetMapping("/cars/{path}")
public Map carsSell(
//获取path的路径。
@PathVariable("path") String path,
//拿到path中的矩阵变量。
@MatrixVariable("low") Integer low,
@MatrixVariable("brand") List<String> brand
){
HashMap<String, Object> map = new HashMap<>();
map.put("low",low);
map.put("brand",brand);
map.put("path",path);
return map;
}
//第二种情况: 两个{}下的相同key。
// /boss/1;age=20/2;age=18
@GetMapping("/boss/{bossId}/{empId}")
public Map boss(
//通过pathVar来指定获取哪一个的age。
@MatrixVariable(value = "age" , pathVar = "bossId") Integer bossAge,
@MatrixVariable(value = "age", pathVar = "empId") Integer empAge
){
HashMap<String, Object> map = new HashMap<>();
map.put("bossAge",bossAge);
map.put("empAge",empAge);
return map;
}
4. 请求参数处理 类型参数的解析原理
4.1 找到合适的HandlerAdapter处理器的源码分析
同样和HandlerMapping一样,HandlerAdapter也会是在DispatcherServlet的doDispatch方法中运行:
接下来说的是Handler适配器,HandlerAdapter如何处理一些参数。
对于各种类型参数的解析原理,使用的是适配器HandlerAdapter来解决。
- handlerMapping中找到能处理请求的handler(controller)。为当前Handler(controller)找一个适配器HandlerAdapter。
上图是SpringMvc默认的4个HandlerAdapter,handler适配器:
- 0 - RequestMappingHandlerAdapter是支持方法上标注@RequestMapping。
- 1 - HandlerFunctionAdapter是支持函数式变成的。
执行的核心又在这里:
//拿到能处理的handlerAdapter
HandlerAdapter ha = this.getHandlerAdapter(mappedHandler.getHandler());
//在这里执行handlerAdapter
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
4.2 HandlerAdapter的执行目标方法
//在这里执行handlerAdapter
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
mappedHandler.getHandler()的源码解析:
handle方法源码:
继续step into深入,就会看到继承了abstractHandlerMethodAdapter的RequestMappingHandlerAdapter类,在该类有一个handleInternal方法,该方法如下代码:
//这段代码就是来执行目标方法的。就是controller那些类里面的方法。
mav = this.invokeHandlerMethod(request, response, handlerMethod);
在invokeHandlerMethod方法中有一个参数解析器:
- 每一个argumentResolvers参数解析器分别是对应了处理不同注解接受参数的形式。像@PathVariable,@RequestParam等等注解。
- SpringMVC目标方法能写多少种参数类型。取决于参数解析器。
argumentResolvers参数解析器的实现接口。
public interface HandlerMethodArgumentResolver {
//当前解析器会判断是否支持解析这种参数
boolean supportsParameter(MethodParameter parameter);
//支持就调用resolveArgument方法。
@Nullable
Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer, NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception;
}
还有一个returnValueHandlers返回值处理器,决定能返回哪些类型:
最终参数解析器和返回值处理器都会集中到invocableMethod变量中。
//invocableMethod会调用invokeAndHandle方法。
invocableMethod.invokeAndHandle(webRequest, mavContainer, new Object[0]);
====================================
//在里面的源码中,有这段代码,这段代码会执行请求指向的handler(Controller层)的方法内容。
//同样在这个invokeForRequest中就会获取请求handler(controller)对应方法的参数值。
Object returnValue = this.invokeForRequest(webRequest, mavContainer, providedArgs);
====================================
@Nullable
public Object invokeForRequest(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception {
//args就是拿到对应请求handler方法中的所有参数。
Object[] args = this.getMethodArgumentValues(request, mavContainer, providedArgs);
if (logger.isTraceEnabled()) {
logger.trace("Arguments: " + Arrays.toString(args));
}
return this.doInvoke(args);
}
//getMethodArgumentValues方法对应上面执行的方法。
protected Object[] getMethodArgumentValues(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception {
//paramters存放了每个参数的详细信息。
MethodParameter[] parameters = this.getMethodParameters();
if (ObjectUtils.isEmpty(parameters)) {
return EMPTY_ARGS;
} else {
Object[] args = new Object[parameters.length];
for(int i = 0; i < parameters.length; ++i) {
MethodParameter parameter = parameters[i];
parameter.initParameterNameDiscovery(this.parameterNameDiscoverer);
args[i] = findProvidedArgument(parameter, providedArgs);
if (args[i] == null) {
//判断这20多个解析器中有能支持该参数的解析器么。
if (!this.resolvers.supportsParameter(parameter)) {
throw new IllegalStateException(formatArgumentError(parameter, "No suitable resolver"));
}
try {
//使用参数解析器来解析参数paramter
args[i] = this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory);
} catch (Exception var10) {
if (logger.isDebugEnabled()) {
String exMsg = var10.getMessage();
if (exMsg != null && !exMsg.contains(parameter.getExecutable().toGenericString())) {
logger.debug(formatArgumentError(parameter, exMsg));
}
}
throw var10;
}
}
}
return args;
}
}
paramters存放了每个参数的详细信息:
通过判断是否有RequestParam注解,从而判断该参数解析器是否支持:
随后一步步解析,拿到请求发送过来的参数。
5. 请求参数处理 Servlet API参数解析原理
ServletRequestMethodArgumentResolver解析器是专门处理将原生的request发给handler方法。
此外,还支持一下几种方式:
6. 请求参数处理 Model和Map原理
handler方法参数为map,使用MapMethodProcessor解析器来处理。
Map类型的参数,底层会调用mavContainer.getModel(); ==> BindingAwareModelMap是Model也是Map。
ModelMethodProcessor处理器是处理handler方法参数为Model类型的参数。
Model类型的参数,底层也会调用mavContainer.getModel()方法 最终获取到值的。
目标方法执行完成:将所有的数据放在ModelAndViewContainer,也包含要去的页面地址view,还包含Model数据。
跳过35-42。