注解相关
@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 {
@Bean
public 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等方法。
然后就是上面的去寻找具体方法。