首页 > 编程语言 >Spring MVC 源码 - HandlerAdapter 组件(二)之 ServletInvocableHandlerMethod

Spring MVC 源码 - HandlerAdapter 组件(二)之 ServletInvocableHandlerMethod

时间:2023-12-06 18:31:38浏览次数:46  
标签:... providedArgs Spring Object mavContainer HandlerAdapter ServletInvocableHandl

HandlerAdapter 组件

HandlerAdapter 组件,处理器的适配器。因为处理器 handler 的类型是 Object 类型,需要有一个调用者来实现 handler 是怎么被执行。Spring 中的处理器的实现多变,比如用户的处理器可以实现 Controller 接口或者 HttpRequestHandler 接口,也可以用 @RequestMapping 注解将方法作为一个处理器等,这就导致 Spring MVC 无法直接执行这个处理器。所以这里需要一个处理器适配器,由它去执行处理器


HandlerAdapter 组件(二)之 ServletInvocableHandlerMethod

本文是接着《HandlerAdapter 组件(一)之 HandlerAdapter》一文来分享 ServletInvocableHandlerMethod 组件。在 HandlerAdapter 执行处理器的过程中,主要的任务还是交由它来完成的。ServletInvocableHandlerMethod 封装 HandlerMethod 处理器对象,它还包含 HandlerMethodArgumentResolver 参数解析器和 HandlerMethodReturnValueHandler 返回值处理器等组件。虽然内容不多,但还是有必要另一篇进行分析。


回顾

先来回顾一下 RequestMappingHandlerAdapter 是如何创建 ServletInvocableHandlerMethod 对象的,可以回到 《HandlerAdapter 组件(一)之 HandlerAdapter》 中 RequestMappingHandlerAdapter 小节下面的 invokeHandlerMethod 方法,如下:

@Nullable
protected ModelAndView invokeHandlerMethod(HttpServletRequest request, HttpServletResponse response, 
                                           HandlerMethod handlerMethod) throws Exception {
    // ... 省略相关代码
    // <4> 创建 ServletInvocableHandlerMethod 对象,并设置其相关属性
    ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);
    if (this.argumentResolvers != null) {
        invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
    }
    if (this.returnValueHandlers != null) {
        invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
    }
    invocableMethod.setDataBinderFactory(binderFactory);
    invocableMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer);
    // ... 省略相关代码
    // <9> 执行调用
    invocableMethod.invokeAndHandle(webRequest, mavContainer);
    // ... 省略相关代码
}
 
protected ServletInvocableHandlerMethod createInvocableHandlerMethod(HandlerMethod handlerMethod) {
    return new ServletInvocableHandlerMethod(handlerMethod);
}
  • 将 HandlerMethod 处理器封装成 ServletInvocableHandlerMethod 对象,然后设置参数解析器和返回值处理器
  • 这里设置了 ServletInvocableHandlerMethod 对象的 resolvers、parameterNameDiscoverer 和 returnValueHandlers 相关属性
  • 调用invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer, Object... providedArgs)方法,执行处理器


类图

ServletInvocableHandlerMethod 的整体类图如下:

Spring MVC 源码 - HandlerAdapter 组件(二)之 ServletInvocableHandlerMethod_状态码

依次分析

HandlerMethod

org.springframework.web.method.HandlerMethod,处理器的方法的封装对象

在《HandlerMapping 组件(三)之 AbstractHandlerMethodMapping》的 AbstractHandlerMethodMapping 小节中已经分析过该对象


InvocableHandlerMethod

org.springframework.web.method.support.InvocableHandlerMethod,继承 HandlerMethod 类,可 invoke 调用的 HandlerMethod 实现类。

也就是说,HandlerMethod 只提供了处理器的方法的基本信息,不提供调用逻辑。

public class InvocableHandlerMethod extends HandlerMethod {
    /** 无参时的入参,空数组 */
    private static final Object[] EMPTY_ARGS = new Object[0];
    /** 数据绑定器工厂 */
    @Nullable
    private WebDataBinderFactory dataBinderFactory;
    /** 参数解析器组合对象 */
    private HandlerMethodArgumentResolverComposite resolvers = new HandlerMethodArgumentResolverComposite();
    /** 方法的参数名称发现器 */
    private ParameterNameDiscoverer parameterNameDiscoverer = new DefaultParameterNameDiscoverer();
}
invokeForRequest

invokeForRequest(NativeWebRequest request, ModelAndViewContainer mavContainer, Object... providedArgs) 方法,执行请求,方法如下:

@Nullable
public Object invokeForRequest(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
        Object... providedArgs) throws Exception {
 
    // <1> 解析参数
    Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
    if (logger.isTraceEnabled()) {
        logger.trace("Arguments: " + Arrays.toString(args));
    }
    // <2> 执行调用
    return doInvoke(args);
}

调用 getMethodArgumentValues(NativeWebRequest request, ModelAndViewContainer mavContainer, Object... providedArgs) 方法,解析方法的参数们,如下:

protected Object[] getMethodArgumentValues(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
        Object... providedArgs) throws Exception {
    // 获得方法的参数
    MethodParameter[] parameters = getMethodParameters();
    // 无参,返回空数组
    if (ObjectUtils.isEmpty(parameters)) {
        return EMPTY_ARGS;
    }
    // 将参数解析成对应的类型
    Object[] args = new Object[parameters.length];
    for (int i = 0; i < parameters.length; i++) {
        // 获得当前遍历的 MethodParameter 对象,并设置 parameterNameDiscoverer 到其中
        MethodParameter parameter = parameters[i];
        parameter.initParameterNameDiscovery(this.parameterNameDiscoverer);
        // <1> 先从 providedArgs 中获得参数。如果获得到,则进入下一个参数的解析,默认情况 providedArgs 不会传参
        args[i] = findProvidedArgument(parameter, providedArgs);
        if (args[i] != null) {
            continue;
        }
         // <2> 判断 resolvers 是否支持当前的参数解析
        if (!this.resolvers.supportsParameter(parameter)) {
            throw new IllegalStateException(formatArgumentError(parameter, "No suitable resolver"));
        }
        try {
            // 执行解析,解析成功后,则进入下一个参数的解析
            args[i] = this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory);
        }
        catch (Exception ex) {
            // Leave stack trace for later, exception may actually be resolved and handled...
            if (logger.isDebugEnabled()) {
                String exMsg = ex.getMessage();
                if (exMsg != null && !exMsg.contains(parameter.getExecutable().toGenericString())) {
                    logger.debug(formatArgumentError(parameter, exMsg));
                }
            }
            throw ex;
        }
    }
    return args;
}
  1. 先从 providedArgs 中获得参数。如果获得到,则进入下一个参数的解析。默认情况下,providedArgs 参数不会传递,所以可以暂时先忽略。保证核心逻辑的理解
  2. 判断 argumentResolvers 是否支持当前的参数解析。如果支持,则进行解析。

调用 doInvoke(Object... args) 方法,执行方法的调用,方法如下:

@Nullable
protected Object doInvoke(Object... args) throws Exception {
    // <1> 设置方法为可访问
    ReflectionUtils.makeAccessible(getBridgedMethod());
    try {
        // <2> 执行调用
        return getBridgedMethod().invoke(getBean(), args);
    }
    catch (IllegalArgumentException ex) {
        assertTargetBean(getBridgedMethod(), getBean(), args);
        String text = (ex.getMessage() != null ? ex.getMessage() : "Illegal argument");
        throw new IllegalStateException(formatInvokeError(text, args), ex);
    }
    catch (InvocationTargetException ex) {
        // Unwrap for HandlerExceptionResolvers ...
        Throwable targetException = ex.getTargetException();
        if (targetException instanceof RuntimeException) {
            throw (RuntimeException) targetException;
        }
        else if (targetException instanceof Error) {
            throw (Error) targetException;
        }
        else if (targetException instanceof Exception) {
            throw (Exception) targetException;
        }
        else {
            throw new IllegalStateException(formatInvokeError("Invocation failure", args), targetException);
        }
    }
}
  1. 设置方法为可访问
  2. 通过反射执行该方法

注意,这里获取到的 Method 对象可能是桥接方法,桥接方法:如果泛型对象,编译器则会自动生成一个桥接方法(java1.5向后兼容)


ServletInvocableHandlerMethod

org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod,继承 InvocableHandlerMethod 类,用于 DispatcherServlet 执行 HandlerMethod 处理器


构造方法

public class ServletInvocableHandlerMethod extends InvocableHandlerMethod {
    private static final Method CALLABLE_METHOD = ClassUtils.getMethod(Callable.class, "call");
    
    /** 返回结果处理器组合对象 */
    @Nullable
    private HandlerMethodReturnValueHandlerComposite returnValueHandlers;
    
    public ServletInvocableHandlerMethod(Object handler, Method method) {
        super(handler, method);
    }
 
    public ServletInvocableHandlerMethod(HandlerMethod handlerMethod) {
        super(handlerMethod);
    }
}

invokeAndHandle

invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer, Object... providedArgs) 方法,处理请求,执行处理器,并处理返回结果,方法如下:

public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception {
    // <1> 执行调用
    Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
    // <2> 设置响应状态码
    setResponseStatus(webRequest);
 
    // <3> 设置 ModelAndViewContainer 为请求已处理,返回,和 @ResponseStatus 注解相关
    if (returnValue == null) {
        if (isRequestNotModified(webRequest) || getResponseStatus() != null || mavContainer.isRequestHandled()) {
            disableContentCachingIfNecessary(webRequest);
            mavContainer.setRequestHandled(true);
            return;
        }
    } else if (StringUtils.hasText(getResponseStatusReason())) {
        mavContainer.setRequestHandled(true);
        return;
    }
 
    // <4> 设置 ModelAndViewContainer 为请求未处理
    mavContainer.setRequestHandled(false);
    Assert.state(this.returnValueHandlers != null, "No return value handlers");
    try {
        // <5> 处理返回值
        this.returnValueHandlers.handleReturnValue(returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
    } catch (Exception ex) {
        if (logger.isTraceEnabled()) {
            logger.trace(formatErrorForReturnValue(returnValue), ex);
        }
        throw ex;
    }
}
  1. 调用 InvocableHandlerMethod 父类的 invokeForRequest(NativeWebRequest request, ModelAndViewContainer mavContainer, Object... providedArgs) 方法,执行调用
  2. 调用 setResponseStatus(ServletWebRequest webRequest) 设置响应的状态码,方法如下:
private void setResponseStatus(ServletWebRequest webRequest) throws IOException {
    // 获得状态码,和 @ResponseStatus 注解相关
    HttpStatus status = getResponseStatus();
    if (status == null) {
        return;
    }
    // 设置响应的状态码
    HttpServletResponse response = webRequest.getResponse();
    if (response != null) {
        String reason = getResponseStatusReason();
        if (StringUtils.hasText(reason)) { // 有 reason ,则设置 status + reason
            response.sendError(status.value(), reason);
        } else { // 无 reason ,则仅设置 status
            response.setStatus(status.value());
        }
    }
 
    // To be picked up by RedirectView
    // 为了 RedirectView ,所以进行设置
    webRequest.getRequest().setAttribute(View.RESPONSE_STATUS_ATTRIBUTE, status);
}
  1. 设置 ModelAndViewContainer 为请求已处理,返回,和 @ResponseStatus 注解相关
  2. 设置 ModelAndViewContainer 为请求未处理
  3. 调用 HandlerMethodReturnValueHandlerComposite 的 handleReturnValue(Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) 方法,处理返回值。


总结

在 HandlerAdapter 执行 HandlerMethod 处理器的过程中,会将该处理器封装成 ServletInvocableHandlerMethod 对象,通过该对象来执行处理器。因为 HandlerMethod 处理器里面仅包含了方法的所有信息,如何解析参数、调用对应的方法、以及处理返回结果,它本身并不知道如何去处理,这里 Spring MVC 借助于 ServletInvocableHandlerMethod 对象来进行操作,原理就是通过反射机制执行该方法

其中涉及到的 HandlerMethodArgumentResolver 参数解析器和 HandlerMethodReturnValueHandler 返回值处理器是比较关键的两个组件,在接下来会逐个进行分析


标签:...,providedArgs,Spring,Object,mavContainer,HandlerAdapter,ServletInvocableHandl
From: https://blog.51cto.com/u_15668812/8708659

相关文章

  • Spring Boot 3.2项目中使用缓存Cache的正确姿势!!!
    你是否曾想过为什么在SpringBoot应用中缓存是如此重要?答案在于它通过减少数据检索时间来提高性能。在本文中,我们将深入探讨缓存对微服务模式的影响,并探讨根据操作易用性、速度、可用性和可观测性等因素选择正确缓存的重要性。我们还将探讨如何最大程度地提高缓存性能和可用性。......
  • SpringBoot Seata 死锁问题排查
    现象描述:SpringBoot项目,启动的时候卡住了,一直卡在那里不动,没有报错,也没有日志输出但是,奇怪的是,本地可以正常启动好吧,姑且先不深究为什么本地可以启动而部署到服务器上就无法启动的问题,这个不是重点,重点是怎么让它启动起来。(PS:我猜测可能是环境不同造成的,包括操作系统不同和JD......
  • VS2019编译PCL1.11.1源码
    最近在使用PCL的体素滤波器进行点云降采样时,遇到了 Leafsizeistoosmallfortheinputdataset的报错,出于某些原因,并不想简单的增大Leafsize来解决这个问题。尝试修改了PCL的源码,但是很可惜,对源码的改动并不能直接应用到我的项目中,于是只能被迫对PCL的sourcecode进行......
  • 2023最新Spring Boot面试题,包含答案。刷题必备!记录一下。
    好记性不如烂笔头内容来自面试宝典-SpringBoot面试题合集问:SpringBoot有哪些优点?SpringBoot具有一系列的优点,包括:简化配置:SpringBoot提供了许多自动配置,可以让你更方便地开发应用程序。快速开发:SpringBoot具有许多微服务构建工具,可以让你快速开发和部署应用程序......
  • C语言三维智能PACS系统源码,医学影像采集系统
    三维智能PACS系统源码,医学影像采集传输系统源码PACS系统以大型关系型数据库作为数据和图像的存储管理工具,以医疗影像的采集、传输、存储和诊断为核心,集影像采集传输与存储管理、影像诊断查询与报告管理、综合信息管理等综合应用于一体的综合应用系统。日常产生的各种医学影像通过国......
  • springbootlearn01 ERROR汇总
    (1)关于IDEA未生成pom.xml文件通过springinitialize要确认[type]选项中为Mavenproject类型,不可选择其他(没有pom.xml)而POM(只会生成pom.xml)(2)关于spring-boot-starter-parent报红的解决方法关于mavenreload后仍无法加载包通过mvn-Uidea:idea命令重新加载maven包,具体操作是......
  • Java Spring Boot 基本使用123
    今天简单学习下SpringBoot的基本使用,在此基础上,我们就可以改造为更加庞大的项目,话不多说,看示例吧。1.IDEA设置首先通过ideafile->NewProject:注意SDK和java版本一致,我们的依赖管理用的是Maven。然后next选择SpringWeb:到此,一个SpringBootWeb项目就......
  • Java Spring Boot controller的使用之参数解析
    SpringBoot作为Java中广受欢迎的框架,其controller的使用必须掌握了解,本篇的学习将从以下几个方面展开:动态URL组路由参数解析Restfulcontroller本篇假设你已经了解SpringBoot开发的基本流程,以下示例主要从controller角度学习。1.动态URL说到此,做过web开发的......
  • Java Spring Boot logback 日志配置与使用总结
    在项目开发中,日志是必不可少的,没有日志,怎么排查bug,而且日志也有助于我们看到相关的输入输出,总的来说,日志是日常项目开发必须要有的。今天我们学习SpringBoot中集成logback日志,这里主要会涉及到日志的配置和简单实现,更多的细节请移步官方文档,自己品读,此文档有助于初涉Sprin......
  • Java Spring Boot 常用配置总结
    在涉及项目开发时,通常我们会灵活地把一些配置项集中在一起,如果你的项目不是很大的情况下,那么通过配置文件集中不失为一个很好的解决方案。在SpringBoot中,我们可以方便地通过读取appliction.properties/application.yaml格式的配置文件,进而注入我们的项目中。1.多环境配置......