注解相关
@AliasFor:.在同个注解中为同一个功能定义两个名称不一样的属性,那么这两个属性彼此互为别名
@RequestMapping
注解里面的代码
@AliasFor("path")String[] value() default {};@AliasFor("value")String[] path() default {};
@GetMapping
@PostMapping
@DeleteMapping
等都是@RequestMapping
修饰
@RequestMapping(method = RequestMethod.GET)
public @interface GetMapping {}
MVC处理流程
一:springmvc请求方式的选择
如果是用的表单提交, 不能支持delete那些方式, 需要开启一下过滤器。但是现在前后端分离, 直接就可以发delete那些方式。
在WebMvcAutoConfiguration
中, 配置了一个命令隐藏方法过滤器
一直进去可以看见这里默认的参数是_method
回到WebMvcAutoConfiguration
中, 我们看见, 如果默认过滤器是false
, 未开启, 我们去配置文件中开启
配置
spring:mvc:hiddenmethod:filter:enabled: true
前端<form action="/test" method="post"><input type="hidden" name="_method" value="DELETE"><input type="submit" value="提交">
</form>
后端@ResponseBody@RequestMapping(value = "/test", method = RequestMethod.DELETE)public String getTest(){return "DELETE";}
结果就是成功请求到, 如果没有开启, 就会失败
现在我们来看一下请求过程:
请求进来, 判断是不是post请求, 并且是否正常,如果是就进入if里面
进来后获取到this.methodParam里面的值, 点进去发现就是_method,
所以: 这里就是获取我们过程名字为_method的input标签里面的值
拿到以后会判断这个值是不是正常, 正常的话就变成大写,所有我们前端大写小写都可以
然后去判断ALLOWED_METHODS里面是不是包含这个值, 如果包含,就new一个HttpMethodRequestWrapper返回
HttpMethodRequestWrapper就是重写了ServletRequest,进行了包装
这里可以看见mvc支持这些请求
特别注意
: 因为表单只能发post和get, 所有才会进入if里面, 如果是客户端工具, 直接发送delete就不用进去, 直接就用的requestToUse
自定义_method
这里给了我们方法, 我们只需要修改一下, 给他注入到容器里面就可以了。
@Configuration(proxyBeanMethods = false)//@Configuration(proxyBeanMethods = false),提高Spring启动速度
public class WebConfig {@Beanpublic HiddenHttpMethodFilter hiddenHttpMethodFilter(){HiddenHttpMethodFilter hiddenHttpMethodFilter1 = new HiddenHttpMethodFilter();hiddenHttpMethodFilter1.setMethodParam("_myMethod");return hiddenHttpMethodFilter1;}
}
二:请求映射原理
所有请求都会来到中央处理器DispatcherServlet
, 我们去里面找到doGet和doPost,在FrameworkServlet中, 我们找到了
再doGet里面processRequest再调用他本类里面的doService方法处理rqquest和response
这里的doService
是一个抽象方法, 并没有实现, 所有一定在子类里面实现。
在中央处理器DispatcherServlet
里面, doService
被实现了
经过一系列的初始化过程, doService里面
调用了doDispatch(request, response);
每个请求进来都会调用doDispatch
方法, 这才是我们要研究的方法。
综上所述:经过系列的辗转反侧, 我们终于找到了最终处理request, response的方法, 就是doDispatch, 我们继续研究doDispatch方法
请求过来以后, 会把请求包装一下,然后判断是不是文件上传multipartRequestParsed
请求, 默认是false.
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
判断是不是异步, 是的话就使用一个异步管理器。
再往下走:
走到核心这里, 这就是找到映射地址的关键: 这里调用本类的一个getHandler
方法, 在这个方法里面找到映射地址
进入这个方法, 我们看见这里是一个for循环的方式来找寻找。
这里的handermappers一共有5个, 我们需要的就是第一个
在这里面我们找到了所有的映射地址, 包括系统给我们写的, 当然, 也有这次要找的。
在这里面就可以找到,这个映射地址的方法叫getTest, 在helloController这个类里面
在for循环的Mapping.getHander()方法里面, 调用了一个方法,在这个方法里面找到最终的结果
我们一直往下运行, 找到RequestMappingInfoHandlerMapping
类, 这里面有一个方法处理了request请求
进入这个方法, 我们看见这里面找到了路径
但是路径可能相同, 但是请求方式不同,这里把路径和requst再进行处理
我们发现这里相同的路径找到了两个请求
然后又把这两个结果放到一个方法里面去找最佳匹配
经过这里的循环, 就找到了最终的结果
三:参数解析原理
走到了这一步
接着上面的, 拿到具体的方法以后, 我们就要去到一个适配器
进去后发现, 这里有4个处理器适配器
- 支持RequestMapping,(我们的controller都是这个)
- 支持函数式编程
这里返回的就是RequestMappingHandlerAdapter
返回以后(中央处理器)我们继续往下走, 我们会走到一个核心方法,Actually invoke the handler
.实际调用处理程序
。
进入这个方法, 我们会走到RequestMappingHandlerAdapter
, 这里做一些真正的处理, 在这里,执行目标方法
在这个方法里面有一个参数解析器
参数解析器里面有这些,这里确定我们将要执行的目标方法的每一个参数的值是什么
。我们平时写的@RequestParam
,就在这里第一个处理的。
这里还有返回值处理器
继续往下,处理请求的一个方法就在这里,
进去这个方法里面, 就会来到我们的controller
真真真执行目标方法ServletInvocableHandlerMethod
类里面。
这里面再进去, 会到InvocableHandlerMethod
, 这里面获取参数的值,代码如下
protected Object[] getMethodArgumentValues(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,Object... providedArgs) throws Exception {MethodParameter[] parameters = getMethodParameters();if (ObjectUtils.isEmpty(parameters)) {return EMPTY_ARGS;}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) {continue;}if (!this.resolvers.supportsParameter(parameter)) {throw new IllegalStateException(formatArgumentError(parameter, "No suitable resolver"));}try {args[i] = this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory);}catch (Exception ex) {// Leave stack trace for later, exception may actually be resolved and handled...if (logger.isDebugEnabled()) {String exMsg = ex.getMessage();if (exMsg != null && !exMsg.contains(parameter.getExecutable().toGenericString())) {logger.debug(formatArgumentError(parameter, exMsg));}}throw ex;}}return args;}
总结:
请求进来以后会先去走过滤器
拿到它是哪一种请求方式,
拿到以后走service
方法去走一个请求转发,通过if else找到请求方式, 去调用doGet, doPost等方法。
然后就是上面的去寻找具体方法。