解惑springmvc中的路径匹配和参数解析

1,142 阅读6分钟

前面已经体会到了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,RequestMappingHandlerMappingRouterFunctionMapping,然后看看他是在哪里使用的

@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;参数,这个是对这个接口的的参数进行描述的,然后发起请求,来到了RequestMappingHandlerAdapterinvokeHandlerMethod方法

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属性,描述了这个参数是被哪些注解修饰的,因为可能不止一个,比如有时候还会加上验证什么的,所以也是一个数组,但现在在我们这里他就只有一个元素

image.png 然后进入下一步,判断是否支持对这种参数类型的解析

@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的封装也都是类似的,就不多说了。