从Spring源码看:HTTP请求如何匹配到Controller中的方法?携带的参数如何填充给方法入参?

360 阅读6分钟

dSpring源码版本 5.3.5

总体流程:

1.Spring容器初始化时,保存RequestMapping请求路径与方法的映射
2.根据Spring初始化的映射信息,找到Http请求路径对应的Controller内部的方法
3.调用方法前,使用参数解析器获取方法的参数名称

1 容器初始化阶段的主要操作

1.1 从Spring启动入口进入AbstractApplicationContext类的refresh()方法

// Instantiate all remaining (non-lazy-init) singletons.
初始化所有非懒加载的单例bean
finishBeanFactoryInitialization(beanFactory);
beanFactory.preInstantiateSingletons();

1.2 DefaultListableBeanFactorypreInstantiateSingletons()方法遍历所有beanName进行实例化

public void preInstantiateSingletons(){
    List<String> beanNames = new ArrayList<>(this.beanDefinitionNames);
    for(beanName:beanNames) {
        if(非抽象&&单例&&非懒加载){
            if(FactoryBean) {
                //FactoryBean实例化逻辑
            } else{
                getBean(beanName);
            }
        }
    }
}

1.3 实例化requestMappingHandlerMapping之后的afterPropertiesSet()方法

RequestMappingHandlerMapping 实现了接口InitializingBean,在设置完所有属性后会调用afterPropertiesSet()方法

public void afterPropertiesSet() {
   super.afterPropertiesSet();
}

执行父类AbstractHandlerMethodMappingafterPropertiesSet()方法

public void afterPropertiesSet() {
   initHandlerMethods();
}
protected void initHandlerMethods() {
   for (String beanName : getCandidateBeanNames()) {
      if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) {
         processCandidateBean(beanName);
      }
   }
   handlerMethodsInitialized(getHandlerMethods());
}

processCandidateBean()对Handler类型的bean处理

protected void processCandidateBean(String beanName) {
    Class<?> beanType = null;
    try {
        beanType = obtainApplicationContext().getType(beanName);
    } catch (Throwable ex) {
    }
    if (beanType != null && isHandler(beanType)) {
      detectHandlerMethods(beanName);
    }
}

isHandler()判断bean是否有@Controller注解或者@RequestMapping注解

protected boolean isHandler(Class<?> beanType) {
   return (AnnotatedElementUtils.hasAnnotation(beanType, Controller.class) ||
         AnnotatedElementUtils.hasAnnotation(beanType, RequestMapping.class));
}

1.4 AbstractHandlerMethodMappingdetectHandlerMethods()构建了Map<Method,RequestMappigInfo>,然后注册到mappingRegistry中,包含了请求路径与方法信息的映射

protected void detectHandlerMethods(Object handler) {
   Class<?> handlerType = (handler instanceof String ?
         obtainApplicationContext().getType((String) handler) : handler.getClass());

   if (handlerType != null) {
      Class<?> userType = ClassUtils.getUserClass(handlerType);
      Map<Method, T> methods = MethodIntrospector.selectMethods(userType,
            (MethodIntrospector.MetadataLookup<T>) method -> {
               try {
               //获取方法信息与请求路径的映射
                  return getMappingForMethod(method, userType);
               }
               catch (Throwable ex) {
                  throw new IllegalStateException("Invalid mapping on handler class [" +
                        userType.getName() + "]: " + method, ex);
               }
            });
      methods.forEach((method, mapping) -> {
         Method invocableMethod = AopUtils.selectInvocableMethod(method, userType);
         //注册请求路径与方法信息的映射
         registerHandlerMethod(handler, invocableMethod, mapping);
      });
   }
}
//注册请求路径与方法信息的映射
protected void registerHandlerMethod(Object handler, Method method, T mapping){
    this.mappingRegistry.register(mapping, handler, method);
}

注册信息如下
{ [/goods/chart_data], produces [application/json]}
->
com.goblet.goods.controller.GoodsPurchaseInfoController#chartData(HttpServletRequest, String, String)

mappingRegistry.png

遍历完一个Controller方法后,其请求路径与方法信息的映射就已经放在Spring容器中了,可以查看一下现在Spring容器中是否已经存在这些注册信息

image.png

问题:如果不在Spring初始化过程中保存映射信息会怎么样?

答:如果不在Spring初始化过程中保存映射信息,对于每个请求:
1)Spring都要遍历所有bean,判断其是否是Handler类型的bean;
2)再遍历其中的方法,用反射获取其注解,判断是否含有@requestMapping注解;
3)再判断该注解的路径是否与请求的路径一致

每个服务都有大量接口会被大量用户调用,每个服务的Spring容器中随随便便就有上百个bean,每个handler类型的bean(即Controller)有多个方法,如果走上面这个流程,性能无疑是极差的。

image.png

在Spring初始化过程中就保存好路径与方法的映射信息,可以大大提高服务运行过程中寻找请求所对应的方法的速度。

2 根据Spring初始化的映射信息,找到Http请求路径对应的Controller内部的方法

请求如何通过ip、端口找到tomcat,如何再转到Spring中可以参考下面两位大佬的文章(respect!):
分析http请求从浏览器到tomcat全过程
请求如何从Tomcat到Spring
这里主要关注Http请求到达Spring之后,Spring寻找对应方法的处理流程

2.1 DispatcherServletprotected void doDispatch(HttpServletRequest request, HttpServletResponse response)为处理请求的主要方法

// 找到请求对应的方法
// Determine handler for the current request.
mappedHandler = getHandler(processedRequest);

// 实际调用handler方法
// Actually invoke the handler.
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

继续向getHandler(processedRequest)内部debug,可以看到:

protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
   String lookupPath = initLookupPath(request);
   this.mappingRegistry.acquireReadLock();
   try {
      HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request);
      return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null);
   }
   finally {
      this.mappingRegistry.releaseReadLock();
   }
}

2.2 getHandlerInternal()方法中的lookupHandlerMethod()方法

this.mappingRegistry就是Spring初始化时存放映射信息的mappingRegistry,根据当前请求路径就能从中获取到对应的方法。

protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {
   List<Match> matches = new ArrayList<>();
   List<T> directPathMatches = this.mappingRegistry.getMappingsByDirectPath(lookupPath);
   if (directPathMatches != null) {
      addMatchingMappings(directPathMatches, matches, request);
   }
   Match bestMatch = matches.get(0);
   return bestMatch.getHandlerMethod();
}

3 调用方法前,使用参数解析器获取方法的参数名称

// 找到请求对应的方法
// Determine handler for the current request.
mappedHandler = getHandler(processedRequest);

// 实际调用handler方法
// Actually invoke the handler.
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

3.1 RequestMappingHandlerAdapterhandleInternal()方法

protected ModelAndView handleInternal(HttpServletRequest request,
      HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
   ModelAndView mav;
   
   mav = invokeHandlerMethod(request, response, handlerMethod);
   
   return mav;
}

设置argumentResolvers参数解析器

protected ModelAndView invokeHandlerMethod(HttpServletRequest request,
      HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
      
    ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);
    if (this.argumentResolvers != null) {
     invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
    }
    invocableMethod.invokeAndHandle(webRequest, mavContainer);
}

3.2 ServletInvocableHandlerMethodinvokeAndHandle()方法

public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer,
      Object... providedArgs) throws Exception {
   Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
}
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);
}

遍历方法参数

protected Object[] getMethodArgumentValues(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
      Object... providedArgs) throws Exception {
   //获取方法参数,并没有参数名称
   MethodParameter[] parameters = getMethodParameters();

   Object[] args = new Object[parameters.length];
   for (int i = 0; i < parameters.length; i++) {
      MethodParameter parameter = parameters[i];
      //获取参数名称及其值
      args[i] = this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory);
   }
   return args;
}

3.4 AbstractNamedValueMethodArgumentResolverresolveArgument()方法

public final Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
      NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
   //获取参数名称
   NamedValueInfo namedValueInfo = getNamedValueInfo(parameter);
   //根据参数名称从请求信息中获取参数的值
   Object arg = resolveName(resolvedName.toString(), nestedParameter, webRequest);
   return arg;
}

获取参数名称

private NamedValueInfo getNamedValueInfo(MethodParameter parameter) {
   //两个缓存对象,来自同一个类,useDefaultResolution为true或者false,分别处理有无注解的情况
   NamedValueInfo namedValueInfo = this.namedValueInfoCache.get(parameter);
   if (namedValueInfo == null) {
      //根据@RequestParam注解获取参数名称
      namedValueInfo = createNamedValueInfo(parameter);
      //没有@RequestParam时获取参数名称
      namedValueInfo = updateNamedValueInfo(parameter, namedValueInfo);
      this.namedValueInfoCache.put(parameter, namedValueInfo);
   }
   return namedValueInfo;
}

两个RequestParamMethodArgumentResolver,useDefaultResolution属性为true或者false,分别处理有无RequestParam注解的情况 image.png 根据@RequestParam注解获取参数名称

protected NamedValueInfo createNamedValueInfo(MethodParameter parameter) {
   RequestParam ann = parameter.getParameterAnnotation(RequestParam.class);
   return (ann != null ? new RequestParamNamedValueInfo(ann) : new RequestParamNamedValueInfo());
}

没有@RequestParam时获取参数名称

private NamedValueInfo updateNamedValueInfo(MethodParameter parameter, NamedValueInfo info) {
   String name = info.name;
   if (info.name.isEmpty()) {
      //获取参数名称
      name = parameter.getParameterName();
   }
   String defaultValue = (ValueConstants.DEFAULT_NONE.equals(info.defaultValue) ? null : info.defaultValue);
   return new NamedValueInfo(name, info.required, defaultValue);
}

getParameterName()

public String getParameterName() {
   ParameterNameDiscoverer discoverer = this.parameterNameDiscoverer;
   if (discoverer != null) {
      String[] parameterNames = null;
      if (this.executable instanceof Method) {
         //方法获取参数名称
         parameterNames = discoverer.getParameterNames((Method) this.executable);
      }
      //返回当前index的参数名称
      if (parameterNames != null) {
         this.parameterName = parameterNames[this.parameterIndex];
      }
      this.parameterNameDiscoverer = null;
   }
   return this.parameterName;
}

3.5 参数解析器的getParameterNames()方法

public String[] getParameterNames(Method method) {
   for (ParameterNameDiscoverer pnd : this.parameterNameDiscoverers) {
      String[] result = pnd.getParameterNames(method);
      if (result != null) {
         return result;
      }
   }
   return null;
}

两个参数解析器,第一个解析不到就用第二个 image.png StandardReflectionParameterNameDiscoverergetParameterNames()

public String[] getParameterNames(Method method) {
   return getParameterNames(method.getParameters());
}

method.getParameters()即为ExecutablegetParameters()

public Parameter[] getParameters() {
    // TODO: This may eventually need to be guarded by security
    // mechanisms similar to those in Field, Method, etc.
    //
    // Need to copy the cached array to prevent users from messing
    // with it.  Since parameters are immutable, we can
    // shallow-copy.
    return privateGetParameters().clone();
}

Executable的privateGetParameters()中的本地方法getParameters0()获取到了参数名称
private native Parameter[] getParameters0()

private Parameter[] privateGetParameters() {
    // Use tmp to avoid multiple writes to a volatile.
    Parameter[] tmp = parameters;

    if (tmp == null) {

        // Otherwise, go to the JVM to get them
        try {
            tmp = getParameters0();
        } catch(IllegalArgumentException e) {
            // Rethrow ClassFormatErrors
            throw new MalformedParametersException("Invalid constant pool index");
        }

        // If we get back nothing, then synthesize parameters
        if (tmp == null) {
            hasRealParameterData = false;
            tmp = synthesizeAllParams();
        } else {
            hasRealParameterData = true;
            verifyParameters(tmp);
        }

        parameters = tmp;
    }

    return tmp;
}

注意:

--Java8之前不支持反射获取参数名称,Java7的reflect包下面根本就没有Parameter这个类,只能通过Method类的getParameterTypes()获取参数类型。

--Java8才出现了Parameter类,并且需要在javac编译时添加参数-parameters才可以通过反射获取到真正的参数名称,否则获取到的是arg0、arg1这种形式。

--对于Java8之前的版本或者Java8未配置编译参数-parameters时,Spring使用的是上面提到的另一个参数解析器LocalVariableTableParameterNameDiscoverer,该类使用了ObjectWeb的ASM库获取参数名称

从下面的编译配置可以看到,编译参数中的确是存在-parameters
(不过我没有手动添加该参数,如果有同学知道这是怎么自动添加上去的可以评论分享一下^-^)

image.png

问题:为什么Spring不在初始化mappingRegistry的时候就把参数名称也保存进去呢?

有知道答案的同学可以分享一下❤❤❤