文档章节

Spring MVC之RequestMappingHandlerAdapter详解

爱宝贝丶
 爱宝贝丶
发布于 2018/10/01 18:46
字数 5711
阅读 417
收藏 12

       RequestMappingHandlerAdapter实现了HandlerAdapter接口,顾名思义,表示handler的adapter,这里的handler指的是Spring处理具体请求的某个Controller的方法,也就是说HandlerAdapter指的是将当前请求适配到某个Handler的处理器。RequestMappingHandlerAdapter是HandlerAdapter的一个具体实现,主要用于将某个请求适配给@RequestMapping类型的Handler处理。

       如下是HandlerMapping接口的声明:

public interface HandlerAdapter {
    // 用于判断当前HandlerAdapter是否能够处理当前请求
	boolean supports(Object handler);
    
	// 如果当前HandlerAdapter能够用于适配当前请求,那么就会处理当前请求中
    // 诸如参数和返回值等信息,以便能够直接委托给具体的Handler处理
	ModelAndView handle(HttpServletRequest request, HttpServletResponse response, 
        Object handler) throws Exception;

    // 获取当前请求的最后更改时间,主要用于供给浏览器判断当前请求是否修改过,
    // 从而判断是否可以直接使用之前缓存的结果
	long getLastModified(HttpServletRequest request, Object handler);
}

1. supports()

       HandlerAdapter.supports()方法的主要作用在于判断当前的HandlerAdapter是否能够支持当前的handler的适配。这里的handler指的是某个Controller的方法,其是由HandlerExecutionChain HandlerMapping.getHandler(HttpServletRequest)方法获取到的。从这里可以看出,HandlerMapping的作用主要是根据request请求获取能够处理当前request的handler,而HandlerAdapter的作用在于将request中的各个属性,如request param适配为handler能够处理的形式。

       关于HandlerAdapter.supports()方法,有这个方法的主要原因是,HandlerMapping是可以有多种实现的,Spring会遍历这些具体的实现类,判断哪一个能够根据当前request产生一个handler,因而对于HandlerAdapter而言,其是不知道当前获取到的handler具体是什么形式的,不同的HandlerMapping产生的handler形式是不一样的,比如RequestMappingHandlerMapping产生的handler则是封装在HandlerMethod对象中的,因而这里HandlerAdapter需要一个方法能够快速过滤掉当前产生的handler是否为其能够进行适配的,这个方法就是HandlerAdapter.supports()方法。如下是该方法的实现:

// AbstractHandlerMethodAdapter
@Override
public final boolean supports(Object handler) {
    // 判断当前handler是否为HandlerMethod类型,并且判断supportsInternal()方法返回值是否为true,
    // 这里supportsInternal()方法是提供给子类实现的一个方法,对于RequestMappingHandlerAdapter
    // 而言,其返回值始终是true,因为其只需要处理的handler是HandlerMethod类型的即可
    return (handler instanceof HandlerMethod 
            && supportsInternal((HandlerMethod) handler));
}
// RequestMappingHandlerAdapter
@Override
protected boolean supportsInternal(HandlerMethod handlerMethod) {
    // 这里RequestMappingHandlerAdapter只是对supportsInternal()返回true,因为其只需要
    // 处理的handler类型是HandlerMethod类型即可
    return true;
}

2. handle()

       在supports()方法判断了所处理的handler是HandlerMethod类型之后,RequestMappingHandlerAdapter就会调用handle()方法处理当前请求。该方法的主要作用在于有五点:

  • 获取当前Spring容器中在方法上配置的标注了@ModelAttribute但是没标注@RequestMapping注解的方法,在真正调用具体的handler之前会将这些方法依次进行调用;
  • 获取当前Spring容器中标注了@InitBinder注解的方法,调用这些方法以对一些用户自定义的参数进行转换并且绑定;
  • 根据当前handler的方法参数标注的注解类型,如@RequestParam@ModelAttribute等,获取其对应的ArgumentResolver,以将request中的参数转换为当前方法中对应注解的类型;
  • 配合转换而来的参数,通过反射调用具体的handler方法;
  • 通过ReturnValueHandler对返回值进行适配,比如ModelAndView类型的返回值就由ModelAndViewMethodReturnValueHandler处理,最终将所有的处理结果都统一封装为一个ModelAndView类型的返回值,这也是RequestMappingHandlerAdapter.handle()方法的返回值类型。

       这里我们首先看看RequestMappingHandlerAdapter.handle()方法的实现源码:

@Override
protected ModelAndView handleInternal(HttpServletRequest request,
        HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {

    ModelAndView mav;
    checkRequest(request);

    // 判断当前是否需要支持在同一个session中只能线性地处理请求
    if (this.synchronizeOnSession) {
        // 获取当前请求的session对象
        HttpSession session = request.getSession(false);
        if (session != null) {
            // 为当前session生成一个唯一的可以用于锁定的key
            Object mutex = WebUtils.getSessionMutex(session);
            synchronized (mutex) {
                // 对HandlerMethod进行参数等的适配处理,并调用目标handler
                mav = invokeHandlerMethod(request, response, handlerMethod);
            }
        } else {
            // 如果当前不存在session,则直接对HandlerMethod进行适配
            mav = invokeHandlerMethod(request, response, handlerMethod);
        }
    } else {
        // 如果当前不需要对session进行同步处理,则直接对HandlerMethod进行适配
        mav = invokeHandlerMethod(request, response, handlerMethod);
    }

    // 判断当前请求头中是否包含Cache-Control请求头,如果不包含,则对当前response进行处理,
    // 为其设置过期时间
    if (!response.containsHeader(HEADER_CACHE_CONTROL)) {
        // 如果当前SessionAttribute中存在配置的attributes,则为其设置过期时间。
        // 这里SessionAttribute主要是通过@SessionAttribute注解生成的
        if (getSessionAttributesHandler(handlerMethod).hasSessionAttributes()) {
            applyCacheSeconds(response, this.cacheSecondsForSessionAttributeHandlers);
        } else {
            // 如果当前不存在SessionAttributes,则判断当前是否存在Cache-Control设置,
            // 如果存在,则按照该设置进行response处理,如果不存在,则设置response中的
            // Cache的过期时间为-1,即立即失效
            prepareResponse(response);
        }
    }

    return mav;
}

       上述代码主要做了两部分处理:①判断当前是否对session进行同步处理,如果需要,则对其调用进行加锁,不需要则直接调用;②判断请求头中是否包含Cache-Control请求头,如果不包含,则设置其Cache立即失效。可以看到,对于HandlerMethod的具体处理是在invokeHandlerMethod()方法中进行的,如下是该方法的具体实现:

@Nullable
protected ModelAndView invokeHandlerMethod(HttpServletRequest request,
        HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {

    ServletWebRequest webRequest = new ServletWebRequest(request, response);
    try {
        // 获取容器中全局配置的InitBinder和当前HandlerMethod所对应的Controller中
        // 配置的InitBinder,用于进行参数的绑定
        WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod);
        // 获取容器中全局配置的ModelAttribute和当前当前HandlerMethod所对应的Controller
        // 中配置的ModelAttribute,这些配置的方法将会在目标方法调用之前进行调用
        ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory);

        // 将handlerMethod封装为一个ServletInvocableHandlerMethod对象,
        // 该对象用于对当前request的整体调用流程进行了封装
        ServletInvocableHandlerMethod invocableMethod =
            createInvocableHandlerMethod(handlerMethod);
        if (this.argumentResolvers != null) {
            // 设置当前容器中配置的所有ArgumentResolver
            invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
        }
        if (this.returnValueHandlers != null) {
            // 设置当前容器中配置的所有ReturnValueHandler
            invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
        }
        // 将前面创建的WebDataBinderFactory设置到ServletInvocableHandlerMethod中
        invocableMethod.setDataBinderFactory(binderFactory);
        // 设置ParameterNameDiscoverer,该对象将按照一定的规则获取当前参数的名称
        invocableMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer);

        ModelAndViewContainer mavContainer = new ModelAndViewContainer();
        mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request));
        // 这里initModel()方法主要作用是调用前面获取到的@ModelAttribute标注的方法,
        // 从而达到@ModelAttribute标注的方法能够在目标Handler调用之前调用的目的
        modelFactory.initModel(webRequest, mavContainer, invocableMethod);
        mavContainer.setIgnoreDefaultModelOnRedirect(this.ignoreDefaultModelOnRedirect);

        // 获取当前的AsyncWebRequest,这里AsyncWebRequest的主要作用是用于判断目标
        // handler的返回值是否为WebAsyncTask或DefferredResult,如果是这两种中的一种,
        // 则说明当前请求的处理应该是异步的。所谓的异步,指的是当前请求会将Controller中
        // 封装的业务逻辑放到一个线程池中进行调用,待该调用有返回结果之后再返回到response中。
        // 这种处理的优点在于用于请求分发的线程能够解放出来,从而处理更多的请求,只有待目标任务
        // 完成之后才会回来将该异步任务的结果返回。
        AsyncWebRequest asyncWebRequest = WebAsyncUtils
            .createAsyncWebRequest(request, response);
        asyncWebRequest.setTimeout(this.asyncRequestTimeout);

        // 封装异步任务的线程池,request和interceptors到WebAsyncManager中
        WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
        asyncManager.setTaskExecutor(this.taskExecutor);
        asyncManager.setAsyncWebRequest(asyncWebRequest);
        asyncManager.registerCallableInterceptors(this.callableInterceptors);
        asyncManager.registerDeferredResultInterceptors(this.deferredResultInterceptors);

        // 这里就是用于判断当前请求是否有异步任务结果的,如果存在,则对异步任务结果进行封装
        if (asyncManager.hasConcurrentResult()) {
            Object result = asyncManager.getConcurrentResult();
            mavContainer = (ModelAndViewContainer) 
                asyncManager.getConcurrentResultContext()[0];
            asyncManager.clearConcurrentResult();
            if (logger.isDebugEnabled()) {
                logger.debug("Found concurrent result value [" + result + "]");
            }
            // 封装异步任务的处理结果,虽然封装的是一个HandlerMethod,但只是Spring简单的封装
            // 的一个Callable对象,该对象中直接将调用结果返回了。这样封装的目的在于能够统一的
            // 进行右面的ServletInvocableHandlerMethod.invokeAndHandle()方法的调用
            invocableMethod = invocableMethod.wrapConcurrentResult(result);
        }

        // 对请求参数进行处理,调用目标HandlerMethod,并且将返回值封装为一个ModelAndView对象
        invocableMethod.invokeAndHandle(webRequest, mavContainer);
        if (asyncManager.isConcurrentHandlingStarted()) {
            return null;
        }

        // 对封装的ModelAndView进行处理,主要是判断当前请求是否进行了重定向,如果进行了重定向,
        // 还会判断是否需要将FlashAttributes封装到新的请求中
        return getModelAndView(mavContainer, modelFactory, webRequest);
    } finally {
        // 调用request destruction callbacks和对SessionAttributes进行处理
        webRequest.requestCompleted();
    }
}

       上述代码是RequestMappingHandlerAdapter处理请求的主要流程,其主要包含四个部分:①获取当前容器中使用@InitBinder注解注册的属性转换器;②获取当前容器中使用@ModelAttribute标注但没有使用@RequestMapping标注的方法,并且在调用目标方法之前调用这些方法;③判断目标handler返回值是否使用了WebAsyncTask或DefferredResult封装,如果封装了,则按照异步任务的方式进行执行;④处理请求参数,调用目标方法和处理返回值。这里我们首先看RequestMappingHandlerAdapter是如何处理标注@InitBinder的方法的,如下是getDataBinderFactory()方法的源码:

private WebDataBinderFactory getDataBinderFactory(HandlerMethod handlerMethod) 
        throws Exception {
    // 判断当前缓存中是否缓存了当前bean所需要装配的InitBinder方法,如果存在,则直接从缓存中取,
    // 如果不存在,则在当前bean中进行扫描获取
    Class<?> handlerType = handlerMethod.getBeanType();
    Set<Method> methods = this.initBinderCache.get(handlerType);
    if (methods == null) {
        // 在当前bean中查找所有标注了@InitBinder注解的方法,这里INIT_BINDER_METHODS就是一个
        // 选择器,表示只获取使用@InitBinder标注的方法
        methods = MethodIntrospector.selectMethods(handlerType, INIT_BINDER_METHODS);
        this.initBinderCache.put(handlerType, methods);
    }
    
    // 这里initBinderAdviceCache是在RequestMappingHandlerAdapter初始化时同步初始化的,
    // 其内包含的方法有如下两个特点:①当前方法所在类使用@ControllerAdvice进行标注了;
    // ②当前方法使用@InitBinder进行了标注。也就是说其内保存的方法可以理解为是全局类型
    // 的参数绑定方法
    List<InvocableHandlerMethod> initBinderMethods = new ArrayList<>();
    this.initBinderAdviceCache.forEach((clazz, methodSet) -> {
        // 这里判断的是当前配置的全局类型的InitBinder是否能够应用于当前bean,
        // 判断的方式主要在@ControllerAdvice注解中进行了声明,包括通过包名,类所在的包,
        // 接口或者注解的形式限定的范围
        if (clazz.isApplicableToBeanType(handlerType)) {
            Object bean = clazz.resolveBean();
            for (Method method : methodSet) {
                initBinderMethods.add(createInitBinderMethod(bean, method));
            }
        }
    });
    
    // 这里是将当前HandlerMethod所在bean中的InitBinder添加到需要执行的initBinderMethods中。
    // 这里从添加的顺序可以看出,全局类型的InitBinder会在当前bean中的InitBinder之前执行
    for (Method method : methods) {
        Object bean = handlerMethod.getBean();
        initBinderMethods.add(createInitBinderMethod(bean, method));
    }
    
    // 将需要执行的InitBinder封装到InitBinderDataBinderFactory中
    return createDataBinderFactory(initBinderMethods);
}

       这里获取InitBinder的方式主要有两种,一种是获取全局配置的InitBinder,全局类型的InitBinder需要声明的类上使用@ControllerAdvice进行标注,并且声明方法上使用@InitBinder进行标注;另一种则是获取当前handler所在类中的使用@InitBinder注解标注的方法。这两种InitBinder都会执行,只不过全局类型的InitBinder会先于局部类型的InitBinder执行。关于使用@InitBinder标注的方法的执行时间点,需要说明的是,因为其与参数绑定有关,因而其只会在参数绑定时才会执行。

       这里我们继续看RequestMappingHandlerAdapter是如何获取@ModelAttribute标注的方法并且执行的,如下是getModelFactory()方法的源码:

private ModelFactory getModelFactory(HandlerMethod handlerMethod, 
        WebDataBinderFactory binderFactory) {
    // 这里SessionAttributeHandler的作用是声明几个属性,使其能够在多个请求之间共享,
    // 并且其能够保证当前request返回的model中始终保有这些属性
    SessionAttributesHandler sessionAttrHandler = 
        getSessionAttributesHandler(handlerMethod);
    
    // 判断缓存中是否保存有当前handler执行之前所需要执行的标注了@ModelAttribute的方法
    Class<?> handlerType = handlerMethod.getBeanType();
    Set<Method> methods = this.modelAttributeCache.get(handlerType);
    if (methods == null) {
        // 如果缓存中没有相关属性,那么就在当前bean中查找所有使用@ModelAttribute标注,但是
        // 没有使用@RequestMapping标注的方法,并将这些方法缓存起来
        methods = MethodIntrospector.selectMethods(handlerType, MODEL_ATTRIBUTE_METHODS);
        this.modelAttributeCache.put(handlerType, methods);
    }
    
    // 获取全局的使用@ModelAttribute标注,但是没有使用@RequestMapping标注的方法,
    // 这里全局类型的方法的声明方式需要注意的是,其所在的bean必须使用@ControllerAdvice进行标注
    List<InvocableHandlerMethod> attrMethods = new ArrayList<>();
    this.modelAttributeAdviceCache.forEach((clazz, methodSet) -> {
        // 判断@ControllerAdvice中指定的作用的bean范围与当前bean是否匹配,匹配了才会对其应用
        if (clazz.isApplicableToBeanType(handlerType)) {
            Object bean = clazz.resolveBean();
            for (Method method : methodSet) {
                attrMethods.add(createModelAttributeMethod(binderFactory, bean, method));
            }
        }
    });
    
    // 将当前方法中使用@ModelAttribute标注的方法添加到需要执行的attrMethods中。从这里的添加顺序
    // 可以看出,全局类型的方法将会先于局部类型的方法执行
    for (Method method : methods) {
        Object bean = handlerMethod.getBean();
        attrMethods.add(createModelAttributeMethod(binderFactory, bean, method));
    }
    
    // 将需要执行的方法等数据封装为ModelFactory对象
    return new ModelFactory(attrMethods, binderFactory, sessionAttrHandler);
}

       上述getModelFactory()方法主要工作还是获取当前需要先于目标handler执行的方法,并且获取的方式与前面的InitBinder非常的相似,这里就不再赘述。关于这里获取的方法,其具体的执行过程实际上是在后面的ModelFactory.initModel()方法中进行。这里我们直接阅读该方法的源码:

public void initModel(NativeWebRequest request, ModelAndViewContainer container,
       HandlerMethod handlerMethod) throws Exception {

    // 在当前request中获取使用@SessionAttribute注解声明的参数
    Map<String, ?> sessionAttributes = 
        this.sessionAttributesHandler.retrieveAttributes(request);
    // 将@SessionAttribute声明的参数封装到ModelAndViewContainer中
    container.mergeAttributes(sessionAttributes);
    // 调用前面获取的使用@ModelAttribute标注的方法
    invokeModelAttributeMethods(request, container);

    // 这里首先获取目标handler执行所需的参数中与@SessionAttribute同名或同类型的参数,
    // 也就是handler想要直接从@SessionAttribute中声明的参数中获取的参数。然后对这些参数
    // 进行遍历,首先判断request中是否包含该属性,如果不包含,则从之前的SessionAttribute缓存
    // 中获取,如果两个都没有,则直接抛出异常
    for (String name : findSessionAttributeArguments(handlerMethod)) {
        if (!container.containsAttribute(name)) {
            Object value = this.sessionAttributesHandler.retrieveAttribute(request, name);
            if (value == null) {
                throw new HttpSessionRequiredException("Expected session attribute '" 
                    + name + "'", name);
            }
            container.addAttribute(name, value);
        }
    }
}

       这里initModel()方法主要做了两件事:①保证@SessionAttribute声明的参数的存在;②调用使用@ModelAttribute标注的方法。我们直接阅读invokeModelAttributeMethods()方法的源码:

private void invokeModelAttributeMethods(NativeWebRequest request, 
       ModelAndViewContainer container) throws Exception {

    while (!this.modelMethods.isEmpty()) {
        // 这里getNextModelMethod()方法始终会获取modelMethods中的第0号为的方法,
        // 后续该方法执行完了之后则会将该方法从modelMethods移除掉,因而这里while
        // 循环只需要判断modelMethods是否为空即可
        InvocableHandlerMethod modelMethod = 
            getNextModelMethod(container).getHandlerMethod();
        // 获取当前方法中标注的ModelAttribute属性,然后判断当前request中是否有与该属性中name字段
        // 标注的值相同的属性,如果存在,并且当前ModelAttribute设置了不对该属性进行绑定,那么
        // 就直接略过当前方法的执行
        ModelAttribute ann = modelMethod.getMethodAnnotation(ModelAttribute.class);
        Assert.state(ann != null, "No ModelAttribute annotation");
        if (container.containsAttribute(ann.name())) {
            if (!ann.binding()) {
                container.setBindingDisabled(ann.name());
            }
            continue;
        }

        // 通过ArgumentResolver对方法参数进行处理,并且调用目标方法
        Object returnValue = modelMethod.invokeForRequest(request, container);
        
        // 如果当前方法的返回值不为空,则判断当前@ModelAttribute是否设置了需要绑定返回值,
        // 如果设置了,则将返回值绑定到请求中,后续handler可以直接使用该参数
        if (!modelMethod.isVoid()){
            String returnValueName = getNameForReturnValue(returnValue, 
                modelMethod.getReturnType());
            if (!ann.binding()) {
                container.setBindingDisabled(returnValueName);
            }
            
            // 如果request中不包含该参数,则将该返回值添加到ModelAndViewContainer中,
            // 供handler使用
            if (!container.containsAttribute(returnValueName)) {
                container.addAttribute(returnValueName, returnValue);
            }
        }
    }
}

       这里调用使用@ModelAttribute标注的方法的方式比较简单,主要需要注意的是,对于调用结果,如果当前request中没有同名的参数,则会将调用结果添加到ModelAndViewContainer中,以供给后续handler使用。

       在调用完使用上述方法之后,Spring会判断当前handler的返回值是否为WebAsyncTask或DefferredResult类型,如果是这两种类型的一种,那么就会将这些任务放入一个线程池中进行异步调用,而当前线程则可以继续进行请求的分发。这里这种设计的目的是,默认情况下Spring处理请求都是同步的,也就是说进行请求分发的线程是会调用用户所声明的handler方法的,那么如果用户声明的handler执行时间较长,就可能导致Spring用于请求处理的线程都耗在了处理这些业务代码上,也就导致后续的请求必须等待,这在高并发的场景中是不能被允许的,因而这里Spring提供了一种异步任务处理的方式,也就是进行请求分发的线程只需要将用户的业务任务放到线程池中执行即可,其自身可以继续进行其他的请求的分发。如果线程池中的任务处理完成,其会通知Spring将处理结果返回给调用方。关于异步任务的处理流程,我们后面会使用专门的章节进行讲解,这里只是简单的讲解其主要功能。

       在进行了相关前置方法调用和异步任务的判断之后,RequestMappingHandlerAdapter就会开始调用目标handler了。调用过程在ServletInvocableHandlerMethod.invokeAndHandle()方法中,如下是该方法的源码:

public void invokeAndHandle(ServletWebRequest webRequest, 
       ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception {

    // 对目标handler的参数进行处理,并且调用目标handler
    Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
    // 设置相关的返回状态
    setResponseStatus(webRequest);

    // 如果请求处理完成,则设置requestHandled属性
    if (returnValue == null) {
        if (isRequestNotModified(webRequest) || getResponseStatus() != null 
            || mavContainer.isRequestHandled()) {
            mavContainer.setRequestHandled(true);
            return;
        }
    } else if (StringUtils.hasText(getResponseStatusReason())) {
        // 如果请求失败,但是有错误原因,那么也会设置requestHandled属性
        mavContainer.setRequestHandled(true);
        return;
    }

    mavContainer.setRequestHandled(false);
    Assert.state(this.returnValueHandlers != null, "No return value handlers");
    try {
        // 遍历当前容器中所有ReturnValueHandler,判断哪种handler支持当前返回值的处理,
        // 如果支持,则使用该handler处理该返回值
        this.returnValueHandlers.handleReturnValue(
            returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
    } catch (Exception ex) {
        if (logger.isTraceEnabled()) {
            logger.trace(getReturnValueHandlingErrorMessage("Error handling return value", 
                returnValue), ex);
        }
        throw ex;
    }
}

       对于handler的调用过程,这里主要分为三个步骤:①处理请求参数进行处理,将request中的参数封装为当前handler的参数的形式;②通过反射调用当前handler;③对方法的返回值进行处理,以将其封装为一个ModleAndView对象。这里第一步和第二步封装在了invokeForRequest()方法中,我们首先看该方法的源码:

@Nullable
public Object invokeForRequest(NativeWebRequest request, @Nullable 
        ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception {

    // 将request中的参数转换为当前handler的参数形式
    Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
    if (logger.isTraceEnabled()) {
        logger.trace("Invoking '" + ClassUtils.getQualifiedMethodName(getMethod(), 
            getBeanType()) + "' with arguments " + Arrays.toString(args));
    }
    // 这里doInvoke()方法主要是结合处理后的参数,使用反射对目标方法进行调用
    Object returnValue = doInvoke(args);
    if (logger.isTraceEnabled()) {
        logger.trace("Method [" + ClassUtils.getQualifiedMethodName(getMethod(), 
            getBeanType()) + "] returned [" + returnValue + "]");
    }
    return returnValue;
}

// 本方法主要是通过当前容器中配置的ArgumentResolver对request中的参数进行转化,
// 将其处理为目标handler的参数的形式
private Object[] getMethodArgumentValues(NativeWebRequest request, @Nullable 
       ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception {

    // 获取当前handler所声明的所有参数,主要包括参数名,参数类型,参数位置,所标注的注解等等属性
    MethodParameter[] parameters = getMethodParameters();
    Object[] args = new Object[parameters.length];
    for (int i = 0; i < parameters.length; i++) {
        MethodParameter parameter = parameters[i];
        parameter.initParameterNameDiscovery(this.parameterNameDiscoverer);
        // providedArgs是调用方提供的参数,这里主要是判断这些参数中是否有当前类型
        // 或其子类型的参数,如果有,则直接使用调用方提供的参数,对于请求处理而言,默认情况下,
        // 调用方提供的参数都是长度为0的数组
        args[i] = resolveProvidedArgument(parameter, providedArgs);
        if (args[i] != null) {
            continue;
        }
        
        // 如果在调用方提供的参数中不能找到当前类型的参数值,则遍历Spring容器中所有的
        // ArgumentResolver,判断哪种类型的Resolver支持对当前参数的解析,这里的判断
        // 方式比较简单,比如RequestParamMethodArgumentResolver就是判断当前参数
        // 是否使用@RequestParam注解进行了标注
        if (this.argumentResolvers.supportsParameter(parameter)) {
            try {
                // 如果能够找到对当前参数进行处理的ArgumentResolver,则调用其
                // resolveArgument()方法从request中获取对应的参数值,并且进行转换
                args[i] = this.argumentResolvers.resolveArgument(
                    parameter, mavContainer, request, this.dataBinderFactory);
                continue;
            } catch (Exception ex) {
                if (logger.isDebugEnabled()) {
                    logger.debug(getArgumentResolutionErrorMessage("Failed to resolve", 
                        i), ex);
                }
                throw ex;
            }
        }
        
        // 如果进行了参数处理之后当前参数还是为空,则抛出异常
        if (args[i] == null) {
            throw new IllegalStateException("Could not resolve method parameter at index " 
                + parameter.getParameterIndex() + " in " 
                + parameter.getExecutable().toGenericString() 
                + ": " + getArgumentResolutionErrorMessage("No suitable resolver for",i));
        }
    }
    return args;
}

       关于handler的调用,可以看到,这里的实现也是比较简单的,首先是遍历所有的参数,并且查找哪种ArgumentResolver能够处理当前参数,找到了则按照具体的Resolver定义的方式进行处理即可。在所有的参数处理完成之后,RequestMappingHandlerAdapter就会使用反射调用目标handler。

       对于返回值的处理,其形式与对参数的处理非常相似,都是对ReturnValueHandler进行遍历,判断哪种Handler能够支持当前返回值的处理,如果找到了,则按照其规则进行处理即可。如下是该过程的主要流程代码:

@Override
public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
       ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {

    // 获取能够处理当前返回值的Handler,比如如果返回值是ModelAndView类型,那么这里的handler就是
    // ModelAndViewMethodReturnValueHandler
    HandlerMethodReturnValueHandler handler = selectHandler(returnValue, returnType);
    if (handler == null) {
        throw new IllegalArgumentException("Unknown return value type: " 
            + returnType.getParameterType().getName());
    }
    
    // 通过获取到的handler处理返回值,并将其封装到ModelAndViewContainer中
    handler.handleReturnValue(returnValue, returnType, mavContainer, webRequest);
}

// 本方法的主要作用是获取能够处理当前返回值的ReturnValueHandler
private HandlerMethodReturnValueHandler selectHandler(@Nullable Object value, 
        MethodParameter returnType) {
    // 判断返回值是否为异步类型的返回值,即WebAsyncTask或DefferredResult
    boolean isAsyncValue = isAsyncReturnValue(value, returnType);
    
    // 对所有的ReturnValueHandler进行遍历,判断其是否支持当前返回值的处理。这里如果当前返回值
    // 是异步类型的返回值,还会判断当前ReturnValueHandler是否为
    // AsyncHandlerMethodReturnValueHandler类型,如果不是,则会继续查找
    for (HandlerMethodReturnValueHandler handler : this.returnValueHandlers) {
        if (isAsyncValue && !(handler instanceof AsyncHandlerMethodReturnValueHandler)) {
            continue;
        }
        
        // 判断是否支持返回值处理的主要位置,比如ModelAndViewMethodReturnValueHandler就会
        // 判断返回值是否为ModelAndView类型,如果是,则表示其是当前ReturnValuleHandler所支持的类型
        if (handler.supportsReturnType(returnType)) {
            return handler;
        }
    }
    return null;
}

3. 小结

       本文首先讲解了RequestMappingHandlerMapping所做的工作与RequestMappingHandlerAdapter的区别,然后讲解RequestMappingHandlerAdapter是如何判断当前的handler是否为其所支持的类型的,最后详细讲解了其是如果将request适配为目标handler能够调用的形式的。总的来讲,RequestMappingHandlerAdapter的主要作用就是调用RequestMappingHandlerMapping所获取到的handler,然后将返回值封装为一个ModelAndView对象,该对象中保存了所要渲染的视图名称和渲染视图时所需要的参数值,而具体的渲染过程则是通过View对象进行的。

© 著作权归作者所有

爱宝贝丶

爱宝贝丶

粉丝 333
博文 136
码字总数 456053
作品 0
武汉
程序员
私信 提问
加载中

评论(1)

Reets
Reets
写的太棒了~
Spring MVC标签小结

< mvc:annotation-driven >的作用 Spring 3.0.x中使用了< mvc:annotation-driven >后,默认会帮我们注册默认处理请求,参数和返回值的类,其中最主要的两个类:DefaultAnnotationHandlerMap...

前世疯狂
2017/04/17
7.5K
0
spring web 4.1处理json

Spring mvc处理json,我们都知道使用@ResponseBody,处理xml也是用此注解。如果想spring mvc的使用@ResponseBody注解处理json,我们需要加入一些处理bean,也可以使用默认spring提供的。 通过...

引鸩怼孑
2015/07/09
1K
0
Spring MVC 3.2+ @ResponseBody 导致的中文乱码处理

问题原因是spring mvc中竟然使用了ISO-编码 这个问题看了好几个,有的配置AnnotationMethodHandlerAdapter,http://bjzhkuang.iteye.com/blog/1886400 有的重写某个类,但我的问题就是没解决...

Nob
2014/07/04
3.5K
1
HttpMessageConverter的注册

简介 Spring MVC遇到一个Unknown return value type XXX的错误,很多有经验的同学可能会会说这简单在Controller的方法上加上ResponseBody注解就可以了。 对于很多时候是有效的,因为这是我们...

trayvon
03/21
60
0
spring vmc3.1.1 上,通过AnnotationMethodHandlerAdap...

spring vmc3.1.1 下,通过AnnotationMethodHandlerAdapter配置webBindingInitializer失效解决方案 问题: spring vmc3.1.1 下,通过AnnotationMethodHandlerAdapter配置webBindingInitializ......

爪哇小贩
2013/03/23
812
2

没有更多内容

加载失败,请刷新页面

加载更多

【0911】linux软件包安装和卸载

【0911】linux软件包安装和卸载 一、安装软件包的三种方法 1、rpm工具:与win中的exe安装包类似,红帽子公司包管理系统 2、yum工具:属于一种用python开发的工具,支持自动的安装依赖的包 3、...

飞翔的竹蜻蜓
27分钟前
3
0
【外行学IT】手机网页自适应之rem和viewport

在写手机网页时,对于像素的问题会非常困惑,初学者很多时候会因为那么一个小点的问题解决不了,或者无法理解透彻就放弃了学习。 我在学习写手机网页时也困惑了许久,出现过下面的问题: 图片...

前端老手
38分钟前
5
0
三、Java设计模式之单一职责原则

定义:不要存在多于一个导致类变更的原因。 一个类、接口、方法只负责一项职责 优点:降低类的复杂度、提高类的可读性,提高系统的可维护性、降低变更引起的风险

东风破2019
46分钟前
4
0
搭建高可用MongoDB集群(分片)

搭建高可用MongoDB集群(分片) KaliArch关注1人评论28269人阅读2017-12-04 21:57:41 MongoDB基础请参考:https://blog.51cto.com/kaliarch/2044423 MongoDB(replica set)请参考:https:/...

linjin200
今天
6
0
Pandas DataFrame创建方法大全

Pandas是Python的数据分析利器,DataFrame是Pandas进行数据分析的基本结构,可以把DataFrame视为一个二维数据表,每一行都表示一个数据记录。本文将介绍创建Pandas DataFrame的6种方法。 创建...

汇智网教程
今天
6
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部