mvc路由注册与匹配

272 阅读16分钟

我们都知道Spring mvc基于请求信息, 根据handlerMapping查找对应mapping 及 handler,使用适配器对handler进行处理,返回对应的视图或响应参数,那么我们从源码的角度看下这句话是什么意思

本文重点:

  1. Spring 如何基于注解注册handlerMapping
  2. Spring 如何基于请求信息找到的handlerMapping及handler 是什么
  3. Spring 下的Restful 一定好吗
  4. Spring 如何进行参数转换,使得Controller层可以直接获取相应参数
  5. @DateTimeFormat 和 JsonFormat 原理 和 区别
  6. 如何添加自定义的路由规则

1.路由注册

1.handlerMapping

HandlerMapping.png

可以看到handlerMapping有很多实现类,简单介绍几种

RequestMappingHandlerMapping : 基于@Controller ,@RequestMapping 注解 ,我们主要和这个类打交道

BeanNameUrlHandlerMapping :基于beanName 是不是以 /开始

SimpleUrlHandlerMapping :需要自行注册 自定义 SimpleUrlHandlerMapping

ControllerEndpointHandlerMapping :actuator

1.RequestMappingHandlerMapping

先看注解 @RequestMapping

public @interface RequestMapping {
    //这三个一致 就是url
    String name() default "";
    @AliasFor("path")
    String[] value() default {};
    @AliasFor("value")
    String[] path() default {};
    
    //POST,GET,PUT、、、、
    RequestMethod[] method() default {};
​
    //包含特定param  如paramA=asd
    String[] params() default {};
   
    //包含特定请求头的值 如 headA = asd
    String[] headers() default {};
​
    //请求类型 如application/json
    String[] consumes() default {};
​
    //响应类型 如application/json
    String[] produces() default {};
}
我们日常使用过的 无非就是 url,method, 其他是匹配相关的其他参数

RequestMappingHandlerMapping间接实现了 InitializingBean ,我们看他的afterPropertiesSet方法

public void afterPropertiesSet() {
    ...
    //AbstractHandlerMethodMapping#afterPropertiesSet 
    //其内又调用this.initHandlerMethods 开始进行初始化
    super.afterPropertiesSet();  
}

AbstractHandlerMethodMapping#initHandlerMethods

protected void initHandlerMethods() {
        //获取容器中所有的beanName 循环判断
        for (String beanName : getCandidateBeanNames()) {
            if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) {
            //在此处获取bean 的类类型beanType 调用 isHandler(beanType) 判断是不是 有@Controller 或者 @RequestMapping  注解 如果有,调用 detectHandlerMethods方法
                processCandidateBean(beanName);
            }
        }
        ...
    }
detectHandlerMethods
protected void detectHandlerMethods(Object handler) {
    //handler是beanName
   Class<?> handlerType = (handler instanceof String ?
         obtainApplicationContext().getType((String) handler) : handler.getClass());
   if (handlerType != null) {
      Class<?> userType = ClassUtils.getUserClass(handlerType);
      //这里是获取当前bean类型 在方法内部 获取其所有接口及自己 的所有方法, 自己的私有方法也是可以的,然后遍历调用下方的getMappingForMethod ,将方法与返回结果绑定起来 ,保存为一个map 
      Map<Method, T> methods = MethodIntrospector.selectMethods(userType,
            (MethodIntrospector.MetadataLookup<T>) method -> {
               try {
                  //根据method返回一个特定对象
                  return getMappingForMethod(method, userType);
               }
               catch (Throwable ex) {
                  ....
               }
            });
      ....
      methods.forEach((method, mapping) -> {
         Method invocableMethod = AopUtils.selectInvocableMethod(method, userType);
         //内部调用 AbstractHandlerMethodMapping.MappingRegistry#register
         registerHandlerMethod(handler, invocableMethod, mapping);
      });
   }
}
RequestMappingHandlerMapping#getMappingForMethod
protected RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType) {
   //可以看到最终返回的是RequestMappingInfo对象,那么上述维护的map就是method 与 RequestMappingInfo 的映射关系
   //createRequestMappingInfo接收AnnotatedElement参数 即method 和class都可以,这一步获取方法上的RequestMapping注解,包装成RequestMappingInfo 
     如果你点开这个方法内部看 发现其还根据element类型 调用了 RequestMappingHandlerMapping.getCustomMethodCondition /getCustomTypeCondition 返回RequestCondition对象,默认为null,这意味着我们可以重写这两个方法
   RequestMappingInfo info = createRequestMappingInfo(method);
   if (info != null) {
      //这里就是获取根据 方法所在类型 获取类上的 RequestMapping 包装成 RequestMappingInfo 
      RequestMappingInfo typeInfo = createRequestMappingInfo(handlerType);
      if (typeInfo != null) {
         //合二为一 这就是我们常用的 类上的注解 @RequestMapping("/A"),方法上的 @RequestMapping("/B"),那么真实的匹配路径就是/A/B
         info = typeInfo.combine(info);
      }
      ....
   }
   return info;
}

RequestMappingInfo

public final class RequestMappingInfo implements RequestCondition<RequestMappingInfo> {
   public RequestMappingInfo(@Nullable String name, @Nullable PatternsRequestCondition patterns,
         @Nullable RequestMethodsRequestCondition methods, @Nullable ParamsRequestCondition params,
         @Nullable HeadersRequestCondition headers, @Nullable ConsumesRequestCondition consumes,
         @Nullable ProducesRequestCondition produces, @Nullable RequestCondition<?> custom) {
​
      this.name = (StringUtils.hasText(name) ? name : null);
      this.patternsCondition = (patterns != null ? patterns : EMPTY_PATTERNS);
      this.methodsCondition = (methods != null ? methods : EMPTY_REQUEST_METHODS);
      this.paramsCondition = (params != null ? params : EMPTY_PARAMS);
      this.headersCondition = (headers != null ? headers : EMPTY_HEADERS);
      this.consumesCondition = (consumes != null ? consumes : EMPTY_CONSUMES);
      this.producesCondition = (produces != null ? produces : EMPTY_PRODUCES);
     //这就是上文所说的 RequestMappingHandlerMapping.getCustomMethodCondition /getCustomTypeCondition 返回RequestCondition对象  ,而其他属性 就基本和 @RequestMapping一一对应了。而@RequestMapping每个属性都可以作为路由匹配的依据,这也就意味着,如果RequestMapping注解中预留的属性不够业务开发使用,我们可以通过重写getCustomMethodCondition /getCustomTypeCondition方法自定义 RequestCondition
      this.customConditionHolder = (custom != null ? new RequestConditionHolder(custom) : EMPTY_CUSTOM);
​
      this.hashCode = calculateHashCode(
            this.patternsCondition, this.methodsCondition, this.paramsCondition, this.headersCondition,
            this.consumesCondition, this.producesCondition, this.customConditionHolder);
   }
   
    //可以看到重写了 hashcode 和 equals方法,这往往意味着我们需要去重,作比较
    public boolean equals(@Nullable Object other) {
        if (this == other) {
            return true;
        }
        if (!(other instanceof RequestMappingInfo)) {
            return false;
        }
        RequestMappingInfo otherInfo = (RequestMappingInfo) other;
        return (this.patternsCondition.equals(otherInfo.patternsCondition) &&
                this.methodsCondition.equals(otherInfo.methodsCondition) &&
                this.paramsCondition.equals(otherInfo.paramsCondition) &&
                this.headersCondition.equals(otherInfo.headersCondition) &&
                this.consumesCondition.equals(otherInfo.consumesCondition) &&
                this.producesCondition.equals(otherInfo.producesCondition) &&
                this.customConditionHolder.equals(otherInfo.customConditionHolder));
    }
}

AbstractHandlerMethodMapping.MappingRegistry

class MappingRegistry {
  //通过下面的代码示例 我们知道 T 是RequestMappingInfo对象 ,HandlerMethod 是@RequestMapping所在的method及bean
   private final Map<T, HandlerMethod> mappingLookup = new LinkedHashMap<>();
   private final MultiValueMap<String, T> urlLookup = new LinkedMultiValueMap<>();
}
AbstractHandlerMethodMapping.MappingRegistry#register
//mapping就是RequestMappingInfo   handler是beanName
public void register(T mapping, Object handler, Method method) {
   ...
   this.readWriteLock.writeLock().lock();
   try {
      //这里就是new 了一个 HandlerMethod对象,根据handler是否为String 注入beanFactory, HandlerMethod对象 就是bean加指定的method
      HandlerMethod handlerMethod = createHandlerMethod(handler, method);
      //这里进行重复路由校验 校验的方式就是根据 mapping到 mappingLookup查找,如果和handlerMethod不一致,就抛出Ambiguous mapping异常,这往往是因为多个方法上的 @RequestMapping注解完全一致导致的 ,有什么方法@RequestMapping 完全一致 也不抛出异常呢?
      validateMethodMapping(handlerMethod, mapping);
      //保存RequestMappingInfo 与 HandlerMethod 的关系
      this.mappingLookup.put(mapping, handlerMethod);
​
      List<String> directUrls = getDirectUrls(mapping);
      for (String url : directUrls) {
         //添加路径 与mapping 的映射,MultiValueMap,这里可以看出 一个路径可以缓存多个mapping对象
         this.urlLookup.add(url, mapping);
      }
      ...
      @CrossOrigin 跨域相关
      CorsConfiguration corsConfig = initCorsConfiguration(handler, method, mapping);
      if (corsConfig != null) {
         this.corsLookup.put(handlerMethod, corsConfig);
      }
      this.registry.put(mapping, new MappingRegistration<>(mapping, handlerMethod, directUrls, name));
   }
   finally {
      this.readWriteLock.writeLock().unlock();
   }
}

HandlerMethod构造方法

public HandlerMethod(String beanName, BeanFactory beanFactory, Method method) {
   this.bean = beanName;
   this.beanFactory = beanFactory;
   Class<?> beanType = beanFactory.getType(beanName);
   if (beanType == null) {
      throw new IllegalStateException("Cannot resolve bean type for bean with name '" + beanName + "'");
   }
   this.beanType = ClassUtils.getUserClass(beanType);
   this.method = method;
   this.bridgedMethod = BridgeMethodResolver.findBridgedMethod(method);
   //之前的文章有提到过 Spring 基于 MethodParameter 进行方法的参数注入, MethodParameter类似BeanDefinition一样的概念,是 方法参数的元信息,这里init的主要是创建MethodParameter(HandlerMethodParameter),标注参数index,嵌套层级。MethodParameter的get方法大多都是调用是才进行初始化获取属性。
   this.parameters = initMethodParameters();
   evaluateResponseStatus();
   this.description = initDescription(this.beanType, this.method);
}

MethodParameter

public class MethodParameter {
    private static final Annotation[] EMPTY_ANNOTATION_ARRAY = new Annotation[0];
    private final Executable executable;
    private final int parameterIndex;
    private volatile Parameter parameter;
    private int nestingLevel;
    private volatile Class<?> parameterType;
    private volatile Type genericParameterType;
    private volatile Annotation[] parameterAnnotations;
    private volatile ParameterNameDiscoverer parameterNameDiscoverer;
    private volatile String parameterName;
    private volatile MethodParameter nestedMethodParameter;
    ....
}

至此 我们看到 RequestMappingHandlerMapping在初始化阶段,获取容器中所有的beanName, 将方法上的RequestMapping和类上的RequestMapping注解整合,包装为RequestMappingInfo对象,同时将method和bean包装为HandlerMethod对象,RequestMappingInfo和

HandlerMethod的绑定关系会维护在MappingRegistry 的mappingLookup map中,除此之外,还会将url和 RequestMappingInfo的关系维护在

MappingRegistry urlLookup中,这两个缓存将是后续匹配路由的关键。

2.DispatcherServlet

在日常开发中,如果需要基于请求前后进行拦截,通常的做法是过滤器 或者拦截器 , 它们的一个区别是过滤器作用于进入DispatcherServlet前,拦截器作用 DispatcherServlet基于handlerAdapter调用目标方法前

我们声明的或者自带的Filter被封装为一个ApplicationFilterConfig对象,多个过滤器构成数组 + Servlet构成了 ApplicationFilterChain 对象

我们熟悉的chain.doFilter(request, response);

org.apache.catalina.core.ApplicationFilterChain#doFilter

public void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException {
    if (Globals.IS_SECURITY_ENABLED) {
        ...
    } else {
        this.internalDoFilter(request, response);
    }
}

internalDoFilter

private void internalDoFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException {
    if (this.pos < this.n) {
        ApplicationFilterConfig filterConfig = this.filters[this.pos++];
        try {
            Filter filter = filterConfig.getFilter();
            if (request.isAsyncSupported() && "false".equalsIgnoreCase(filterConfig.getFilterDef().getAsyncSupported())) {
                request.setAttribute("org.apache.catalina.ASYNC_SUPPORTED", Boolean.FALSE);
            }
            ...
            } else {
                 //按顺序执行过滤器链 
                filter.doFilter(request, response, this);
            }
        } catch (ServletException | RuntimeException | IOException var15) {
            ....
        }
    } else {
        try {
            ....
            } else {
                //过滤器执行完毕 进入DispatcherServlet (父类FrameworkServlet) 
                this.servlet.service(request, response);
            }
        }  
        ...
    }
}

FrameworkServlet#processRequest

protected final void processRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    ...
    RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();
    //request, response被包装进ServletRequestAttributes 
    ServletRequestAttributes requestAttributes = this.buildRequestAttributes(request, response, previousAttributes);
    ...
    //我们常用的 ((ServletRequestAttributes)RequestContextHolder.getRequestAttributes()).getRequest(); 就是在这里放入到线程变量中
    this.initContextHolders(request, localeContext, requestAttributes);
    try {
        //正式进入DispatcherServlet 表演时间  方法内部调用 doDispatch方法
        this.doService(request, response);
    } catch (IOException | ServletException var16) {
        ..
    } finally { 
        //清空线程变量里保存的请求信息
        this.resetContextHolders(request, previousLocaleContext, previousAttributes);
        if (requestAttributes != null) {
            requestAttributes.requestCompleted();
        }
        ...
    }
}

3.路由匹配

DispatcherServlet#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 {
      ModelAndView mv = null;
      Exception dispatchException = null;
​
      try {
        //如果是multipart(multipart/form-data)请求 即上传文件 解析成MultipartHttpServletRequest对象,从Content-Disposition(类似Content-Disposition: attachment; filename="filename.xls")请求头中解析出文件,封装进MultiValueMap<String, MultipartFile> multipartFiles中
         processedRequest = checkMultipart(request);
         multipartRequestParsed = (processedRequest != request);
         //根据请求信息返回handler,这里的handler内部是包含拦截器的 获取handlerMapping 根据handlerMapping 查找handlerMethod在方法内部
         mappedHandler = getHandler(processedRequest);
         if (mappedHandler == null) {
            noHandlerFound(processedRequest, response);
            return;
         }
         //选择合适的HandlerAdapter
         HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
         //拦截器前置处理  注意这里的逻辑是 某个拦截器不放行时,已放行过的拦截器会立即逆序执行afterCompletion方法
         if (!mappedHandler.applyPreHandle(processedRequest, response)) {
            return;
         }
         // Actually invoke the handler.
         mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
         applyDefaultViewName(processedRequest, mv);
         //拦截器后置处理
         mappedHandler.applyPostHandle(processedRequest, response, mv);
      }
     ....
}

getHandler

protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
   if (this.handlerMappings != null) {
      //基于内部维护的handlerMappings进行处理,处理逻辑是只要有一个符合就返回 我们只需要关心 RequestMappingHandlerMapping
      for (HandlerMapping mapping : this.handlerMappings) {
         //可以看到这里返回的是HandlerExecutionChain对象
         HandlerExecutionChain handler = mapping.getHandler(request);
         if (handler != null) {
            return handler;
         }
      }
   }
   return null;
}

AbstractHandlerMapping#getHandler

public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
   //内部调用   AbstractHandlerMethodMapping#lookupHandlerMethod 返回 HandlerMethod
   Object handler = getHandlerInternal(request);
   ...
   if (handler instanceof String) {
      String handlerName = (String) handler;
      handler = obtainApplicationContext().getBean(handlerName);
   }
  
   HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);
   ..
   if (hasCorsConfigurationSource(handler) || CorsUtils.isPreFlightRequest(request)) {
      //跨域相关
   }
​
   return executionChain;
}
AbstractHandlerMethodMapping#lookupHandlerMethod
protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {
        //Match就是 RequestMappingInfo + HandlerMethod
        List<Match> matches = new ArrayList<>();
        // 先根据url路径查找 去urlLookup 中查找 RequestMappingInfo
        List<T> directPathMatches = this.mappingRegistry.getMappingsByUrl(lookupPath);
        if (directPathMatches != null) {
            addMatchingMappings(directPathMatches, matches, request);
        }
        if (matches.isEmpty()) {
             //如过根据url查找不到 那么就根据其他条件遍历判断 上文介绍的 RequestMappingInfo每个condition都是判断条件,也就是需要遍历维护的所有RequestMappingInfo对象,遍历其内部的condition看是否满足条件。
             匹配不到基本上是使用了Restful形式的调用,如用@PutMapping@PostMapping来标注方法,而又不填充url值,写法简洁了,但毫无疑问,这会带来不必要的开销
            addMatchingMappings(this.mappingRegistry.getMappings().keySet(), matches, request);
        }
​
        if (!matches.isEmpty()) {
            Match bestMatch = matches.get(0);
            if (matches.size() > 1) {
                //查找到多个对象 只有当我们重写了getCustomMethodCondition /getCustomTypeCondition 方法才会进入这个if 
                Comparator<Match> comparator = new MatchComparator(getMappingComparator(request));
                // 按顺序调用RequestCondition的compareTo方法执行,==0比较不出来就 继续下一个RequestCondition就行比较 ,最后是自定义的
                matches.sort(comparator);
                bestMatch = matches.get(0);
                if (CorsUtils.isPreFlightRequest(request)) {
                    return PREFLIGHT_AMBIGUOUS_MATCH;
                }
                Match secondBestMatch = matches.get(1);
                //重写getCustomMethodCondition /getCustomTypeCondition 方法返回了RequestCondition对象,这必然是我们自行实现的一个对象,对象需要实现compareTo方法
                if (comparator.compare(bestMatch, secondBestMatch) == 0) {
                    ...
                    throw new IllegalStateException(
                            "Ambiguous handler methods mapped for '" + uri + "': {" + m1 + ", " + m2 + "}");
                }
            }
            request.setAttribute(BEST_MATCHING_HANDLER_ATTRIBUTE, bestMatch.handlerMethod);
            handleMatch(bestMatch.mapping, lookupPath, request);
            return bestMatch.handlerMethod;
        }
        else {
            return handleNoMatch(this.mappingRegistry.getMappings().keySet(), lookupPath, request);
        }
    }

至此 我们根据请求信息找到了HandlerMethod ,也就是对应controller中的method ,接下来就是结合拦截器了

AbstractHandlerMapping#getHandlerExecutionChain
protected HandlerExecutionChain getHandlerExecutionChain(Object handler, HttpServletRequest request) {
   HandlerExecutionChain chain = (handler instanceof HandlerExecutionChain ?
         (HandlerExecutionChain) handler : new HandlerExecutionChain(handler));
   String lookupPath = this.urlPathHelper.getLookupPathForRequest(request, LOOKUP_PATH);
   for (HandlerInterceptor interceptor : this.adaptedInterceptors) {
      if (interceptor instanceof MappedInterceptor) {
         //一般我们自定义的拦截器属于MappedInterceptor 根据url判断是否需要调用
         MappedInterceptor mappedInterceptor = (MappedInterceptor) interceptor;
         if (mappedInterceptor.matches(lookupPath, this.pathMatcher)) {
            chain.addInterceptor(mappedInterceptor.getInterceptor());
         }
      }
      else {
         //这里关心一个 ConversionServiceExposingInterceptor :pre阶段将 ConversionService放入到  Request维护的map中
         chain.addInterceptor(interceptor);
      }
   }
   return chain;
}

getHandlerAdapter

protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
   if (this.handlerAdapters != null) {
      for (HandlerAdapter adapter : this.handlerAdapters) {
         if (adapter.supports(handler)) {
            //如果命中的RequestMappingHandlerMapping 那这里就是返回 RequestMappingHandlerAdapter了
            return adapter;
         }
      }
   }
  ....
}

至此我们看到了如何基于请求信息 找到controller 中的method,那么接下来 我们看看 spring 如果自动封装成我们需要的参数

同样 以 RequestMappingHandlerAdapter 为例

4.参数封装

RequestMappingHandlerAdapter#invokeHandlerMethod

protected ModelAndView invokeHandlerMethod(HttpServletRequest request,
      HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
​
   ServletWebRequest webRequest = new ServletWebRequest(request, response);
   try {
      //InitBinder 注册controller层的参数解析 :扫描handlerMethod的bean,也就是对应controller中带@InitBinder注解的方法,包装成InvocableHandlerMethod 。最终返回ServletRequestDataBinderFactory对象
      WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod);
      ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory);
      //包装 可以填充参数处理组件等 
      ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);
      //请求参数处理组件
      if (this.argumentResolvers != null) {
         invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
      }
      //返回参数处理组件 我们主要关心 RequestResponseBodyMethodProcessor 就行
      if (this.returnValueHandlers != null) {
         invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
      }
      invocableMethod.setDataBinderFactory(binderFactory);
      //开始参数处理 反射调用目标方法 返回值处理  
      invocableMethod.invokeAndHandle(webRequest, mavContainer);
      if (asyncManager.isConcurrentHandlingStarted()) {
         return null;
      }
​
      return getModelAndView(mavContainer, modelFactory, webRequest);
   }
   finally {
      webRequest.requestCompleted();
   }
}

1649929146722.png

这些都是参数处理组件,会从上至下判断,一个能处理就返回,可以看到 ServletModelAttributeMethodProcessor 是最后的兜底组件

ServletInvocableHandlerMethod#invokeAndHandle

public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer,
      Object... providedArgs) throws Exception {
   //参数处理及反射调用  内部调用 InvocableHandlerMethod#getMethodArgumentValues获取参数
   Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
   setResponseStatus(webRequest);
   ...
   try {
      //处理返回值
      this.returnValueHandlers.handleReturnValue(
            returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
   }
   catch (Exception ex) {
      
   }
}

InvocableHandlerMethod#getMethodArgumentValues

protected Object[] getMethodArgumentValues(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
      Object... providedArgs) throws Exception {
   //providedArgs 是 空数组[]
   MethodParameter[] parameters = getMethodParameters(); 
   Object[] args = new Object[parameters.length];
   //这里是个循环 每个参数都可能会有不同的resolver进行处理
   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) {
        //有参数传入的时候 就不去请求信息中获取参数了。这也就是@InitBinder方法中能获取到WebDataBinder参数的原因,因为是手动传入的
           continue;
        }
      //遍历上面截图的参数处理组件,看是否能支持解析该类型的参数 比如 普通类型,有 @RequestParam,@Requesbody注解等 ...,很多类型的参数是可以被多个处理器处理的,所以上面的处理器的排序是有一定讲究的,
        同时会发现有同样类型的resolver出现多次,但是其中的属性值不一样,所以会有不同的处理逻辑。 任意处理器满足就返回,并将parameterresolver缓存起来,下次就不用遍历了
      if (!this.resolvers.supportsParameter(parameter)) {
         throw new IllegalStateException(formatArgumentError(parameter, "No suitable resolver"));
      }
      try {
          //这里拿到具体的resolver了 开始进行参数处理 转换成我们需要的参数类型
         args[i] = this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory);
      }
      catch (Exception ex) {
         ...
         throw ex;
      }
   }
   return args;
}

下面我们以这四个例子来看看 参数处理过程


@PostMapping("/getInfoA")
public Object getInfoA(Long id, Date startTime){
    return startTime;
}

@PostMapping("/getInfoB")
public Object getInfoB(@RequestParam("id") Long id,@RequestParam("startTime") Date startTime){
    return startTime;
}

@PostMapping("/getInfoC")
public Object getInfoC(OrderParams orderParams){
    return orderParams;
}

@PostMapping("/getInfoD")
public Object getInfoIII(@RequestBody OrderParams orderParams){
    return orderParams;
}

@Data
static class OrderParams{
    private Long id;
    private Date startTime;
}

case 1 无注解,简单参数类型

Long id 属于无注解简单类型 进入第二个 RequestParamMethodArgumentResolver :可以处理简单类型 及 简单类型数组的参数 简单类型:8大类型,Enum ,Number,CharSequence,Date等 (第一个主要RequestParamMethodArgumentResolver主要处理 @RequestParam注解)

父类是多个处理器的共同父类

AbstractNamedValueMethodArgumentResolver
public final Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
      NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
   //尝试获取相关注解中的配置@RequestParam @PathVariable @RequestHeader等,由子类实现方法
   NamedValueInfo namedValueInfo = getNamedValueInfo(parameter);
   //没有相关注解 就用参数名称 这里是  "id"
   MethodParameter nestedParameter = parameter.nestedIfOptional();
   ...
   //这里同样由子类实现 RequestParamMethodArgumentResolver 在这里的逻辑是尝试先取MultipartFile,取不到就调用 request.getParameterValues(name) 方法获取参数  
   Object arg = resolveName(resolvedName.toString(), nestedParameter, webRequest);
   if (arg == null) {
       //从请求信息获取不到指定名称参数的逻辑 可被子类重写 
   }

   if (binderFactory != null) {
      //WebDataBinder内部维护了一个SimpleTypeConverter(PropertyEditorRegistry),在此处调用controller的带@InitBinder注解方法,往Map<Class<?>, PropertyEditor> customEditors中注册PropertyEditor
      WebDataBinder binder = binderFactory.createBinder(webRequest, null, namedValueInfo.name);
      try {
         //binder进行参数转换时,调用维护的SimpleTypeConverter,而SimpleTypeConverter再调用维护的TypeConverterDelegate的convertIfNecessary方法。 
         arg = binder.convertIfNecessary(arg, parameter.getParameterType(), parameter);
      }
      catch (ConversionNotSupportedException ex) {
         ...
      }
   }
   //可被子类重写 唯一实现 PathVariableMethodArgumentResolver
   handleResolvedValue(arg, namedValueInfo.name, parameter, mavContainer, webRequest);

   return arg;
}
TypeConverterDelegate#convertIfNecessary
public <T> T convertIfNecessary(@Nullable String propertyName, @Nullable Object oldValue, @Nullable Object newValue, @Nullable Class<T> requiredType, @Nullable TypeDescriptor typeDescriptor) throws IllegalArgumentException {
    //根据MethodParameter描述的请求类型,查看controller是不是有自定义的PropertyEditor(也就是有无@InitBinder方法)
    PropertyEditor editor = this.propertyEditorRegistry.findCustomEditor(requiredType, propertyName);
    ConversionFailedException conversionAttemptEx = null;
    ConversionService conversionService = this.propertyEditorRegistry.getConversionService();
    //无自定义PropertyEditor的就调用ConversionService进行转换
    if (editor == null && conversionService != null && newValue != null && typeDescriptor != null) {
        //ConversionService 内部维护大量GenericConverter  基于 TypeDescriptor类型描述符判断是否可以进行参数转换
        TypeDescriptor sourceTypeDesc = TypeDescriptor.forObject(newValue);
        //sourceTypeDesc一般为String ,sourceTypeDesc和typeDescriptor公同组成ConverterCacheKey,描述本次请求拿到的类型和需要的类型,ConverterCacheKey重写了hashcode和equals方法,因此可以缓存,下次可以快速找到对应的converter。这里会基于请求拿到的类型 和需要的类型,递归查找其父类 和 接口,两两组合 进行查找可以处理的Converter。案例中第一次查找到的两个Converter属于 ConditionalGenericConverter(多了一个matches条件判断方法),分别需要DateTimeFormat和NumberFormat注解。判断不通过,第二次使用String和Long的父类Number就成功匹配了。这里即使有多个满足也只会返回匹配上的第一个。
        if (conversionService.canConvert(sourceTypeDesc, typeDescriptor)) {
           try {
                return conversionService.convert(newValue, sourceTypeDesc, typeDescriptor);
            } catch (ConversionFailedException var14) {
                //有可能抛异常
                conversionAttemptEx = var14;
            }
        }
    }
    Object convertedValue = newValue;
    if (editor != null || requiredType != null && !ClassUtils.isAssignableValue(requiredType, newValue)) {
        ...
        //基于PropertyEditor
        convertedValue = this.doConvertValue(oldValue, convertedValue, requiredType, editor);
         ..尝试 按集合 枚举 解等操作
    }
     
    return convertedValue;
}

Date startTime 属于无注解简单类型 同样进入第二个 RequestParamMethodArgumentResolver 同样由于无自定义PropertyEditor的就调用ConversionService进行转换 最终匹配到Object -> Object的 Converter 这里直接调用的是Date的构造方法

case 2 带注解,简单参数类型

同case1 因为携带注解@RequestParam 且不是 Map,进入第一个 RequestParamMethodArgumentResolver

case 3 无注解pojo

进入兜底 ServletModelAttributeMethodProcessor 父类 ModelAttributeMethodProcessor

public final Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
      NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {

   ...ModelAttribute相关...
   else {
      // Create attribute instance
      try {
         //尝试按参数名从请求uri,parameters中获取参数,如果没有 ,根据类型调用构造方法,创建对象
         attribute = createAttribute(name, parameter, binderFactory, webRequest);
      }
      catch (BindException ex) {
        ...
      }
   }

   if (bindingResult == null) {
      //这里创建的空对象直接维护到了binder的target
      WebDataBinder binder = binderFactory.createBinder(webRequest, attribute, name);
      if (binder.getTarget() != null) {
         if (!mavContainer.isBindingDisabled(name)) {
            //这里 遍历请求信息中parameters挨个 调用TypeConverterDelegate#convertIfNecessary方法进行参数转换,然后调用set方法进行赋值
            bindRequestParameters(binder, webRequest);
         }
         //Validated 参数校验
         validateIfApplicable(binder, parameter);
         if (binder.getBindingResult().hasErrors() && isBindExceptionRequired(binder, parameter)) {
            throw new BindException(binder.getBindingResult());
         }
      }
      ...
   return attribute;
}

前三个例子都是表单类型的请求,通过源码我们可以发现,可基于 PropertyEditor 或 ConversionService (GenericConverter) 扩展参数转换逻辑,我们先看 @DateTimeFormat,spring提供的一个时间格式类型conveter AnnotationParserConverter

@DateTimeFormat

前文说过 String ->Date 最终匹配到Object -> Object的 Converter 如果参数/属性 带 @DateTimeFormat,那匹配的就是 AnnotationParserConverter了 AnnotationParserConverter属于ConditionalGenericConverter,在这里必须有DateTimeFormat注解才会匹配到

private class AnnotationParserConverter implements ConditionalGenericConverter {

   @Override
   public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) {
      //annotationType  : DateTimeFormat
      return targetType.hasAnnotation(this.annotationType);
   }


   public Object convert(@Nullable Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
      Annotation ann = targetType.getAnnotation(this.annotationType);
      if (ann == null) {
         throw ...
      }
      AnnotationConverterKey converterKey = new AnnotationConverterKey(ann, targetType.getObjectType());
      GenericConverter converter = cachedParsers.get(converterKey);
      if (converter == null) {
         Parser<?> parser = this.annotationFormatterFactory.getParser(
               converterKey.getAnnotation(), converterKey.getFieldType());
         converter = new ParserConverter(this.fieldType, parser, FormattingConversionService.this);
         cachedParsers.put(converterKey, converter);
      }
      return converter.convert(source, sourceType, targetType);
   }
 
}

自定义Converter

这也能实现String->Date 自定义格式转换。但是灵活性就没有 @DateTimeFormat高了。优先匹配自定义的

@Component
public class DateConvert implements Converter<String, Date> {
    @Override
    public Date convert(String stringDate) {
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        try {
            return simpleDateFormat.parse(stringDate);
        } catch (ParseException e) {
            e.printStackTrace();
        }
        return null;
    }
}

自定义PropertyEditor

在controller 类中定义,作用范围为当前controller

@InitBinder(value可为空或指定的参数名称)
public void initBinder(WebDataBinder webDataBinder){
    webDataBinder.registerCustomEditor(Date.class, new CustomDateEditor(new SimpleDateFormat("yyyy-MM-dd HH:mm"), false));
}

case 4 RequestBody

因为参数携带@RequestBody注解,所以进入的是 RequestResponseBodyMethodProcessor 这个处理器能同时处理请求和响应

public boolean supportsParameter(MethodParameter parameter) {
   return getArgumentResolver(parameter) != null;
}

public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
      NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {

   parameter = parameter.nestedIfOptional();
   //和之前表单类型/uri参数 基于ConversionService(GenericConverter)处理不同 这里调用的是HttpMessageConverter
   Object arg = readWithMessageConverters(webRequest, parameter, parameter.getNestedGenericParameterType());
   String name = Conventions.getVariableNameForParameter(parameter);
   if (binderFactory != null) {
      WebDataBinder binder = binderFactory.createBinder(webRequest, arg, name);
      if (arg != null) {
         validateIfApplicable(binder, parameter);
         if (binder.getBindingResult().hasErrors() && isBindExceptionRequired(binder, parameter)) {
            throw new MethodArgumentNotValidException(parameter, binder.getBindingResult());
         }
      }
      if (mavContainer != null) {
         mavContainer.addAttribute(BindingResult.MODEL_KEY_PREFIX + name, binder.getBindingResult());
      }
   }

   return adaptArgumentIfNecessary(arg, parameter);
}

HttpMessageConverter image.png

父类 AbstractMessageConverterMethodArgumentResolver#readWithMessageConverters
protected <T> Object readWithMessageConverters(HttpInputMessage inputMessage, MethodParameter parameter,
      Type targetType) throws IOException, HttpMediaTypeNotSupportedException, HttpMessageNotReadableException {
      ...
   Class<?> contextClass = parameter.getContainingClass();
   Class<T> targetClass = (targetType instanceof Class ? (Class<T>) targetType : null);
   ...
   HttpMethod httpMethod = (inputMessage instanceof HttpRequest ? ((HttpRequest) inputMessage).getMethod() : null);
   Object body = NO_VALUE;

   EmptyBodyCheckingHttpInputMessage message;
   try {
      message = new EmptyBodyCheckingHttpInputMessage(inputMessage);
      for (HttpMessageConverter<?> converter : this.messageConverters) {
         Class<HttpMessageConverter<?>> converterType = (Class<HttpMessageConverter<?>>) converter.getClass();
         GenericHttpMessageConverter<?> genericConverter =
               (converter instanceof GenericHttpMessageConverter ? (GenericHttpMessageConverter<?>) converter : null);
         //同ConversionService处理一样 有一个converter能处理就行 主要判断 canRead方法
         if (genericConverter != null ? genericConverter.canRead(targetType, contextClass, contentType) :
               (targetClass != null && converter.canRead(targetClass, contentType))) {
            if (message.hasBody()) {
               HttpInputMessage msgToUse =
               //getAdvice返回的是RequestResponseBodyAdviceChain 内部维护了两个List集合 可分别在作用于读取数据前后,spring在这里相当于给我们留下了自行扩展的空间,我们可以自定义advice
                     getAdvice().beforeBodyRead(message, parameter, targetType, converterType);
               body = (genericConverter != null ? genericConverter.read(targetType, contextClass, msgToUse) :
                     ((HttpMessageConverter<T>) converter).read(targetClass, msgToUse));
               body = getAdvice().afterBodyRead(body, msgToUse, parameter, targetType, converterType);
            }
            else {
              //后置处理
               body = getAdvice().handleEmptyBody(null, message, parameter, targetType, converterType);
            }
            break;
         }
      }
   }
   catch (IOException ex) {
       ...
   }
  ...
   return body;
}

读取inputStream转化为参数,这里spring直接调用了jackson的ObjectMapper,所以具体的转换过程其实是jackson做完了,jackso本身也提供了一些注解给开发者使用 ,@JsonFormat @JsonIgnore @JsonIgnoreProperties... 同样,我们以Date类型举例 ,简单 看下@JsonFormat 怎么作用的

ObjectMapper#_readMapAndClose
protected Object _readMapAndClose(JsonParser p0, JavaType valueType)
    throws IOException
{
    try (JsonParser p = p0) {
        Object result;
        JsonToken t = _initForReading(p, valueType);
        final DeserializationConfig cfg = getDeserializationConfig();
        final DeserializationContext ctxt = createDeserializationContext(p, cfg);
        if (t == JsonToken.VALUE_NULL) {
            ...
        } else if (t == JsonToken.END_ARRAY || t == JsonToken.END_OBJECT) {
          ...
        } else {
            //解析对象属性 包装成 BeanDeserializerBase,解析的过程就扫描了类、属性上的 @JsonIgnoreProperties,@JsonFormat 
            JsonDeserializer<Object> deser = _findRootDeserializer(ctxt, valueType);
            if (cfg.useRootWrapping()) {
                ...
            } else {
                //无参构造创建bean,遍历MethodProperty,  MethodProperty 调用自己维护的TypeDeserializer#deserialize 方法转换参数 并set到bean 中,例子中Date类型属性最终调用了DateDeserializers ,JsonFormat注解在这里被解析成了例子中Date类型属性最终调用了DateDeserializers的属性 DateFormat(_customFormat),如果_customFormat不为空,就_customFormat.parse(str),否则调用父类的parseDate方法
                result = deser.deserialize(p, ctxt);
            }
            ctxt.checkUnresolvedObjectId();
        }
        ....
        return result;
    }
}

image.png

最后 我们参考内置实现 JsonViewRequestBodyAdvice 封装一个

自定义的增强 aes解密
@Component
@RestControllerAdvice
public class MyAnnotationAdvice extends RequestBodyAdviceAdapter {

    @Override
    public boolean supports(MethodParameter methodParameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
        return (AbstractJackson2HttpMessageConverter.class.isAssignableFrom(converterType) &&
                methodParameter.getParameterAnnotation(MyAnnotation.class) != null);
    }

    @Override
    public HttpInputMessage beforeBodyRead(HttpInputMessage inputMessage, MethodParameter methodParameter, Type targetType, Class<? extends HttpMessageConverter<?>> selectedConverterType) throws IOException {
        InputStream body = inputMessage.getBody();
        HttpHeaders headers = inputMessage.getHeaders();
        headers.remove(HttpHeaders.CONTENT_LENGTH);
        String encrypt = StreamUtils.copyToString(body, StandardCharsets.UTF_8);
        //方便演示 直接做aes解密....
        String decode = URLDecoder.decode(encrypt, StandardCharsets.UTF_8.name());
        InputStream byteArrayInputStream = new ByteArrayInputStream(decode.getBytes(StandardCharsets.UTF_8));
        //headers.setContentType(MediaType.APPLICATION_JSON);
        return new MappingJacksonInputMessage(byteArrayInputStream, inputMessage.getHeaders());
    }
}

5.总结

ApplicationFilterChain  内部维护了一个ApplicationFilterConfig数组,ApplicationFilterConfig是Filter的封装,请求经过 过滤器链的循环判断后,调用维护的servlet的service方法,默认的实现是DispatcherServlet,DispatcherServlet最终调用doDispatch方法。

在doDispatch方法内部,维护了List handlerMappings 和 List handlerAdapters 集合,HandlerMapping是处理路由的,常见的有RequestMappingHandlerMapping ,BeanNameUrlHandlerMapping ,SimpleUrlHandlerMapping,

HandlerAdater主要是进行参数解析,调用目标方法,处理返回值等,常见的有RequestMappingHandlerAdapter、HandlerFunctionAdapter、HttpRequestHandlerAdapter,这里我们说下RequestMappingHandlerMapping ,RequestMappingHandlerAdapter。

RequestMappingHandlerMapping  先根据url 筛选出合适的 RequestMappingInfo ,我们的@RequestMapping 注解 最终被注册为RequestMappingInfo 对象,如果找不到合适的,会根据method,header,param等其他RequestCondition属性 ,筛选出其他合适的路由。我们还可以自定义RequestCondition,来添加自定义的匹配规则,不过这需要重写RequestMappingHandlerMapping 的getCustomMethodCondition或者getCustomTypeCondition方法。

筛选出 RequestMappingInfo 后根据 mappingLookup 找到对应的 HandlerMethod, 然后根据url 添加匹配的拦截器 和默认参数解析拦截器等,返回一个HandlerMethod + HandlerInterceptor[] interceptors 也就是 HandlerExecutionChain对象,然后顺序调用拦截器,RequestMappingHandlerAdapter 就HandlerMethod反射执行 获取请求结果 。 程序继续 降序调用拦截器postHandle, 以及过滤器链的后续逻辑代码。

HandlerMethod反射执行,调用目标方法前后,处理请求参数和响应报文,spring都留下了可供开发者扩展的的地方。比如表单类型的请求参数可自定义PropertyEditor 或 GenericConverter。json类型的请求参数可以自定义HttpMessagesConverter或 继承RequestBodyAdviceAdapter 实现解析前后方法增强。

6.自定义路由规则

案例代码

自定义注解

@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ApiVersion {
    double value() default 1.0;
}

重写 RequestMappingHandlerMapping

public class CustomRequestMappingHandlerMapping extends RequestMappingHandlerMapping {
    @Override
    protected RequestCondition<?> getCustomMethodCondition(Method method) {
        ApiVersion annotation = AnnotationUtils.getAnnotation(method, ApiVersion.class);
        //要么不写注解就是最高版本 要么就是最低版本
        double value = 9999;
        if(annotation != null) {
            value = annotation.value();
        }
        return new HeaderRequestCondition(value);
    }
}

实例化

@Component
public  class InterceptorConfig extends WebMvcConfigurationSupport {
   @Override
   protected RequestMappingHandlerMapping createRequestMappingHandlerMapping() {
      return new CustomRequestMappingHandlerMapping();
   }
}

自定义RequestCondition

public class HeaderRequestCondition implements RequestCondition<HeaderRequestCondition> {

    private Double apiVersion;

    public HeaderRequestCondition(Double apiVersion) {
        this.apiVersion = apiVersion;
    }

    @Override
    public HeaderRequestCondition combine(HeaderRequestCondition headerRequestCondition) {
        //这里的调用逻辑是typeInfo.combine(info) 即类注解 拼接 方法注解,这里直接返回方法注解,我们的自定义注解只打在方法上
        return headerRequestCondition;
    }

    @Override
    public HeaderRequestCondition getMatchingCondition(HttpServletRequest httpServletRequest) {
        //这里的调用逻辑是 this.customConditionHolder.getMatchingCondition(request);
        // 也就是this(this是根据方法上的ApiVerion创建的路由信息) 比较 请求对象
        String header = httpServletRequest.getHeader("apiVersion");
        if(StringUtils.isBlank(header)){
            //不传版本号 默认放行 方法上的注解值是 1.0 ,2.0都认为匹配上
            return this;
        }
        Double requestApiVersion = Double.parseDouble(header);
        //this版本高于请求版本 不命中
        if(this.apiVersion.compareTo(requestApiVersion) > 0){
            return null;
        }
        return this;
    }

    @Override
    public int compareTo(HeaderRequestCondition headerRequestCondition, HttpServletRequest httpServletRequest) {
        //这里的调用是 return info1.compareTo(info2, request);  版本高的优先
        return headerRequestCondition.apiVersion.compareTo(this.apiVersion);
    }
}

测试

@RestController
@RequestMapping("/test/controller")
public class TestController {

    @PutMapping
    @ApiVersion
    public String testPuttingMapping1(){
        return "1";
    }

    @PutMapping
    @ApiVersion(2.0)
    public String testPuttingMapping2(){
        return "2";
    }
}