前面已经体会到了springmvc的魅力了,而且在servlet3.0之后的版本加持下,我们已经不需要任何xml文件了,所有的配置可以全部通过hard code完成,而且在配合spring的包扫描机制,极大地简化了我们的web开发过程。但是有没有想过springmvc是怎么做到精确匹配每一个controller的每一个方法,以及我们的类似于使用@RequestBody这样的dto参数是如何绑定并生效的呢?
路径匹配HandlerMapping
在前面说DispatcherServlet初始化的时候,最后会执行一个onrefresh()方法,我们现在要说的就是这个方法中的其中一个initHandlerMappings(context);,其中传入的这个context就是springmvc自己这个web子容器。首先去看一下HandlerMapping,他是一个接口,里面只有一个方法。HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception;,他的返回值是一个HandlerExecutionChain,里面包含了一个handler和若干个HandlerInterceptor。然后回过头再来看这个initHandlerMappings(context);方法,他看起来就干了一件事,就是如果我们没有定义HandlerMapping,那么默认就会去DispatcherServlet.properties这个文件里面去找默认的那三个实现并配置,分别是BeanNameUrlHandlerMapping,RequestMappingHandlerMapping和RouterFunctionMapping,然后看看他是在哪里使用的
@Nullable
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
if (this.handlerMappings != null) {
for (HandlerMapping mapping : this.handlerMappings) {
// 会在 AbstractHandlerMapping中找到并返回,自己可以调试验证
HandlerExecutionChain handler = mapping.getHandler(request);
if (handler != null) {
return handler;
}
}
}
return null;
}
这个getHandler方法会在doDispatch里面执行,意味着所有的http请求都会走到这里来,然后对通过当前request来获取HandlerExecutionChain,所以接下来就是真正的寻找handler,就是前面说的那个HandlerExecutionChain getHandler(HttpServletRequest request)方法,具体实现就在AbstractHandlerMapping里面,
public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
// 这一步实现在AbstractHandlerMethodMapping里面
Object handler = getHandlerInternal(request);
if (handler == null) {
handler = getDefaultHandler();
}
if (handler == null) {
return null;
}
// Bean name or resolved handler?
if (handler instanceof String) {
String handlerName = (String) handler;
handler = obtainApplicationContext().getBean(handlerName);
}
// 这一步过后该对象里面包含的handler就是HandlerMrethod
HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);
if (logger.isTraceEnabled()) {
logger.trace("Mapped to " + handler);
}
else if (logger.isDebugEnabled() && !request.getDispatcherType().equals(DispatcherType.ASYNC)) {
logger.debug("Mapped to " + executionChain.getHandler());
}
if (hasCorsConfigurationSource(handler)) {
CorsConfiguration config = (this.corsConfigurationSource != null ? this.corsConfigurationSource.getCorsConfiguration(request) : null);
CorsConfiguration handlerConfig = getCorsConfiguration(handler, request);
config = (config != null ? config.combine(handlerConfig) : handlerConfig);
executionChain = getCorsHandlerExecutionChain(request, executionChain, config);
}
return executionChain;
}
首先第一步回返回一个对应这个请求对的HandlerMethod,这个方法的实现在AbstractHandlerMethodMapping里面,然后这个class实现了sprinted扩展接口InitializingBean,注意看他的重写方法里面就干了一件事,那就是注册handler,也就是我们的那些controller接口,后续springmvc会在这里注册过的handler里面找到对应的handler,不信请看processCandidateBean这个方法里面有一个关键方法isHandler()
@Override
protected boolean isHandler(Class<?> beanType) {
return (AnnotatedElementUtils.hasAnnotation(beanType, Controller.class) ||
AnnotatedElementUtils.hasAnnotation(beanType, RequestMapping.class));
}
看到这里是不是懂起了些什么,然后回过头再来看这第一步的方法获取,其实现可以去看一下,就是根据参数,方法等一起进行匹配,找到我们需要的那个handler,在这一步就完成了路径匹配,返回一个请求对应的HandlerMethod,她描述了这个请求所在的class,以及她所依赖的bean,这个请求在这个class中对应的方法等等,参数很多,其实只要自己这一步打个断点就很清晰了,然后第二步就是通过getHandlerExecutionChain这个方法来获取前面说到的这个HandlerExecutionChain,此时里面这个Object类型handler就是HandlerMethod,然后由于我们没有定义任何的HandlerInterceptor,所以就直接跳过,关于他的使用我们后续再说,然后接下来就是跨域的处理,可以看到springmvc对跨域的处理也是通过HandlerInterceptor来完成的。
参数解析HandlerMethodArgumentResolver
接着前面的doDisptch往下看一步,就会看到会根据得到的HandlerMethod来获取对应的HandlerAdapter,然后通过断点观察返回的是RequestMappingHandlerAdapter,他同时实现了InitializingBean这个接口,我们知道这是spring提供的对bean生命周期控制的一个扩展接口,在这个afterPropertiesSet方法中,springmvc默认添加了二十几种参数解析器,他们都是HandlerMethodArgumentResolver的实现类
private List<HandlerMethodArgumentResolver> getDefaultArgumentResolvers() {
List<HandlerMethodArgumentResolver> resolvers = new ArrayList<>();
// Annotation-based argument resolution
resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), false));
resolvers.add(new RequestParamMapMethodArgumentResolver());
resolvers.add(new PathVariableMethodArgumentResolver());
resolvers.add(new PathVariableMapMethodArgumentResolver());
resolvers.add(new MatrixVariableMethodArgumentResolver());
resolvers.add(new MatrixVariableMapMethodArgumentResolver());
resolvers.add(new ServletModelAttributeMethodProcessor(false));
resolvers.add(new RequestResponseBodyMethodProcessor(getMessageConverters(), this.requestResponseBodyAdvice));
resolvers.add(new RequestPartMethodArgumentResolver(getMessageConverters(), this.requestResponseBodyAdvice));
resolvers.add(new RequestHeaderMethodArgumentResolver(getBeanFactory()));
resolvers.add(new RequestHeaderMapMethodArgumentResolver());
resolvers.add(new ServletCookieValueMethodArgumentResolver(getBeanFactory()));
resolvers.add(new ExpressionValueMethodArgumentResolver(getBeanFactory()));
resolvers.add(new SessionAttributeMethodArgumentResolver());
resolvers.add(new RequestAttributeMethodArgumentResolver());
// Type-based argument resolution
resolvers.add(new ServletRequestMethodArgumentResolver());
resolvers.add(new ServletResponseMethodArgumentResolver());
resolvers.add(new HttpEntityMethodProcessor(getMessageConverters(), this.requestResponseBodyAdvice));
resolvers.add(new RedirectAttributesMethodArgumentResolver());
resolvers.add(new ModelMethodProcessor());
resolvers.add(new MapMethodProcessor());
resolvers.add(new ErrorsMethodArgumentResolver());
resolvers.add(new SessionStatusMethodArgumentResolver());
resolvers.add(new UriComponentsBuilderMethodArgumentResolver());
// Custom arguments
if (getCustomArgumentResolvers() != null) {
resolvers.addAll(getCustomArgumentResolvers());
}
// Catch-all
resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), true));
resolvers.add(new ServletModelAttributeMethodProcessor(true));
return resolvers;
}
我这里就举例第一个进行说明,其他的都是一样的,RequestParamMethodArgumentResolver是添加的第一个解析器,看名字就能很自然得想到这肯定是跟@RequestParam这个注解进行关联的,进到该class首先看实现的第一个方法supportsParameter
public boolean supportsParameter(MethodParameter parameter) {
if (parameter.hasParameterAnnotation(RequestParam.class)) {
if (Map.class.isAssignableFrom(parameter.nestedIfOptional().getNestedParameterType())) {
RequestParam requestParam = parameter.getParameterAnnotation(RequestParam.class);
return (requestParam != null && StringUtils.hasText(requestParam.name()));
}
else {
return true;
}
}
else {
if (parameter.hasParameterAnnotation(RequestPart.class)) {
return false;
}
parameter = parameter.nestedIfOptional();
if (MultipartResolutionDelegate.isMultipartArgument(parameter)) {
return true;
}
else if (this.useDefaultResolution) {
return BeanUtils.isSimpleProperty(parameter.getNestedParameterType());
}
else {
return false;
}
}
}
上面这段代码先混个眼熟,他是用来判断是否使用该解析器来解析请求参数的,然后我们写一个test然后断点调试
@GetMapping("/print")
public String print(@RequestParam("name") String name) {
return "hello" + name;
}
将断点打在DispatchServlet中的doDisptach中,前面已经知道了会为每一个请求生成一个HandlerMethod,在这里我们只需要他包含了一个MethodParameter[] parameters;参数,这个是对这个接口的的参数进行描述的,然后发起请求,来到了RequestMappingHandlerAdapter的invokeHandlerMethod方法
ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);
这一步就构造了一个最终处理该请求的对象,然后接下来这里就是解析并处理该请求的过程
invocableMethod.invokeAndHandle(webRequest, mavContainer);
@Nullable
public Object invokeForRequest(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
Object... providedArgs) throws Exception {
// 解析参数
Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
if (logger.isTraceEnabled()) {
logger.trace("Arguments: " + Arrays.toString(args));
}
return doInvoke(args);
}
Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
上面连续三步最后进入InvocableHandlerMethod的getMethodArgumentValues方法,很显然根据名字就知道这是在解析参数,进去就看到首先会获取前面说到的那个MethodParameter[] parameters;属性,他代表了这个接口的所有参数列表的详细信息,所以是个List,MethodParameter里面又有个parameterAnnotations属性,描述了这个参数是被哪些注解修饰的,因为可能不止一个,比如有时候还会加上验证什么的,所以也是一个数组,但现在在我们这里他就只有一个元素
然后进入下一步,判断是否支持对这种参数类型的解析
@Nullable
private HandlerMethodArgumentResolver getArgumentResolver(MethodParameter parameter) {
HandlerMethodArgumentResolver result = this.argumentResolverCache.get(parameter);
if (result == null) {
for (HandlerMethodArgumentResolver resolver : this.argumentResolvers) {
if (resolver.supportsParameter(parameter)) {
result = resolver;
this.argumentResolverCache.put(parameter, result);
break;
}
}
}
return result;
}
前面我们知道了springmvc借助扩展接口添加了默认的20多个解析器,就包含了对@RequestParam的解析,前面那个supportsParameter方法就在这里被调用了,不是让混个眼熟嘛,现在熟了吧,我们有了@RequestParam这个注解,就会直接返回这个解析器,同时将其缓存起来,避免每一次都去遍历,这个做法值得借鉴的,最后就是拿着解析起去解析参数了,这个过程就不说了,可以自己去看每一种解析器分别是怎么实现的,同时如果默认的解析器不满足我们对开发需求,也可以自己去定义解析器,只需要实现HandlerMethodArgumentResolver这个接口并重写连个方法就行了,因为这里已经分析过了他们的调用时机,所以该怎么写心里应该也有数了
args[i] = this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory);
最后还有返回值的封装,全局拦截ControllerAdvice的封装也都是类似的,就不多说了。