从原理层面掌握HandlerMethod、InvocableHandlerMethod、ServletInvocableHandlerMethod的使用【享学Spring MVC】(中)

简介: 从原理层面掌握HandlerMethod、InvocableHandlerMethod、ServletInvocableHandlerMethod的使用【享学Spring MVC】(中)

InvocableHandlerMethod

它是对HandlerMethod的扩展,增加了调用能力。这个能力在Spring MVC可是非常非常重要的,它能够在调用的时候,把方法入参的参数都封装进来(从HTTP request里,当然借助的必然是HandlerMethodArgumentResolver)


// @since 3.1
public class InvocableHandlerMethod extends HandlerMethod {
  private static final Object[] EMPTY_ARGS = new Object[0];
  // 它额外提供的几个属性,可以看到和数据绑定、数据校验就扯上关系了~~~
  // 用于产生数据绑定器、校验器
  @Nullable
  private WebDataBinderFactory dataBinderFactory;
  // HandlerMethodArgumentResolver用于入参的解析
  private HandlerMethodArgumentResolverComposite resolvers = new HandlerMethodArgumentResolverComposite();
  // 用于获取形参名
  private ParameterNameDiscoverer parameterNameDiscoverer = new DefaultParameterNameDiscoverer();
  ... // 省略构造函数 全部使用super的
  // 它自己的三大属性都使用set方法设置进来~~~并且没有提供get方法
  // 也就是说:它自己内部使用就行了~~~
  // 在给定请求的上下文中解析方法的参数值后调用该方法。 也就是说:方法入参里就能够自动使用请求域(包括path里的,requestParam里的、以及常规对象如HttpSession这种)
  // 解释下providedArgs作用:调用者可以传进来,然后直接doInvoke()的时候原封不动的使用它
  //(弥补了请求域没有所有对象的不足,毕竟有些对象是用户自定义的嘛~)
  @Nullable
  public Object invokeForRequest(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception {
    // 虽然它是最重要的方法,但是此处不讲,因为核心原来还是`HandlerMethodArgumentResolver`
    // 它只是把解析好的放到对应位置里去~~~
    // 说明:这里传入了ParameterNameDiscoverer,它是能够获取到形参名的。
    // 这就是为何注解里我们不写value值,通过形参名字来匹配也是ok的核心原因~
    Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
    if (logger.isTraceEnabled()) { // trace信息,否则日志也特多了~
      logger.trace("Arguments: " + Arrays.toString(args));
    }
    return doInvoke(args);
  }
  // doInvoke()方法就不说了,就是个普通的方法调用
  // ReflectionUtils.makeAccessible(getBridgedMethod());
  // return getBridgedMethod().invoke(getBean(), args); 
}


对于最后的invoke(),说明一点:这里可是执行的目标方法getBean()哦~~~

这个子类主要提供的能力就是提供了invoke调用目标Bean的目标方法的能力,在这个调用过程中可大有文章可为,当然最为核心的逻辑可是各种各样的HandlerMethodArgumentResolver来完成的,详见下文有分晓。

InvocableHandlerMethod这个子类虽然它提供了调用了能力,但是它却依旧还没有和Servlet的API绑定起来,毕竟使用的是Spring自己通用的的NativeWebRequest,so很容易想到它还有一个子类就是干这事的~


ServletInvocableHandlerMethod

它是对InvocableHandlerMethod的扩展,它增加了返回值和响应状态码的处理,另外在ServletInvocableHandlerMethod有个内部类ConcurrentResultHandlerMethod继承于它,支持异常调用结果处理,Servlet容器下Controller在查找适配器时发起调用的最终就是ServletInvocableHandlerMethod。


public class ServletInvocableHandlerMethod extends InvocableHandlerMethod {
  private static final Method CALLABLE_METHOD = ClassUtils.getMethod(Callable.class, "call");
  // 处理方法返回值
  @Nullable
  private HandlerMethodReturnValueHandlerComposite returnValueHandlers;
  // 构造函数略
  // 设置处理返回值的HandlerMethodReturnValueHandler
  public void setHandlerMethodReturnValueHandlers(HandlerMethodReturnValueHandlerComposite returnValueHandlers) {
    this.returnValueHandlers = returnValueHandlers;
  }
  // 它不是复写,但是是对invokeForRequest方法的进一步增强  因为调用目标方法还是靠invokeForRequest
  // 本处是把方法的返回值拿来进一步处理~~~比如状态码之类的
  public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception {
    Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
    // 设置HttpServletResponse返回状态码 这里面还是有点意思的  因为@ResponseStatus#code()在父类已经解析了  但是子类才用
    setResponseStatus(webRequest);
    // 重点是这一句话:mavContainer.setRequestHandled(true); 表示该请求已经被处理过了
    if (returnValue == null) {
      // Request的NotModified为true 有@ResponseStatus注解标注 RequestHandled=true 三个条件有一个成立,则设置请求处理完成并返回
      if (isRequestNotModified(webRequest) || getResponseStatus() != null || mavContainer.isRequestHandled()) {
        mavContainer.setRequestHandled(true);
        return;
      }
    // 返回值不为null,@ResponseStatus存在reason 同样设置请求处理完成并返回
    } else if (StringUtils.hasText(getResponseStatusReason())) {
      mavContainer.setRequestHandled(true);
      return;
    }
    // 前边都不成立,则设置RequestHandled=false即请求未完成
    // 继续交给HandlerMethodReturnValueHandlerComposite处理
    // 可见@ResponseStatus的优先级还是蛮高的~~~~~
    mavContainer.setRequestHandled(false);
    Assert.state(this.returnValueHandlers != null, "No return value handlers");
    try {
      // 关于对方法返回值的处理,参见:https://blog.csdn.net/f641385712/article/details/90370542
      this.returnValueHandlers.handleReturnValue(returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
    } catch (Exception ex) {
      if (logger.isTraceEnabled()) {
        logger.trace(formatErrorForReturnValue(returnValue), ex);
      }
      throw ex;
    }
  }
  // 设置返回的状态码到HttpServletResponse 里面去
  private void setResponseStatus(ServletWebRequest webRequest) throws IOException {
    HttpStatus status = getResponseStatus();
    if (status == null) { // 如果调用者没有标注ResponseStatus.code()此注解  此处就忽略它
      return;
    }
    HttpServletResponse response = webRequest.getResponse();
    if (response != null) {
      String reason = getResponseStatusReason();
      // 此处务必注意:若有reason,那就是sendError  哪怕你是200哦~
      if (StringUtils.hasText(reason)) {
        response.sendError(status.value(), reason);
      } else {
        response.setStatus(status.value());
      }
    }
    // 设置到request的属性,把响应码给过去。为了在redirect中使用
    // To be picked up by RedirectView
    webRequest.getRequest().setAttribute(View.RESPONSE_STATUS_ATTRIBUTE, status);
  }
  private boolean isRequestNotModified(ServletWebRequest webRequest) {
    return webRequest.isNotModified();
  }
  // 这个方法RequestMappingHandlerAdapter里有调用
  ServletInvocableHandlerMethod wrapConcurrentResult(Object result) {
    return new ConcurrentResultHandlerMethod(result, new ConcurrentResultMethodParameter(result));
  }
  // 内部类们
  private class ConcurrentResultMethodParameter extends HandlerMethodParameter {
    @Nullable
    private final Object returnValue;
    private final ResolvableType returnType;
    public ConcurrentResultMethodParameter(Object returnValue) {
      super(-1);
      this.returnValue = returnValue;
      // 主要是这个解析 兼容到了泛型类型 比如你的返回值是List<Person> 它也能把你的类型拿出来
      this.returnType = (returnValue instanceof ReactiveTypeHandler.CollectedValuesList ?
          ((ReactiveTypeHandler.CollectedValuesList) returnValue).getReturnType() :
          ResolvableType.forType(super.getGenericParameterType()).getGeneric());
    }
    // 若返回的是List  这里就是List的类型哦  下面才是返回泛型类型
    @Override
    public Class<?> getParameterType() {
      if (this.returnValue != null) {
        return this.returnValue.getClass();
      }
      if (!ResolvableType.NONE.equals(this.returnType)) {
        return this.returnType.toClass();
      }
      return super.getParameterType();
    }
    // 返回泛型类型
    @Override
    public Type getGenericParameterType() {
      return this.returnType.getType();
    }
    // 即使实际返回类型为ResponseEntity<Flux<T>>,也要确保对@ResponseBody-style处理从reactive 类型中收集值
    // 是对reactive 的一种兼容
    @Override
    public <T extends Annotation> boolean hasMethodAnnotation(Class<T> annotationType) {
      // Ensure @ResponseBody-style handling for values collected from a reactive type
      // even if actual return type is ResponseEntity<Flux<T>>
      return (super.hasMethodAnnotation(annotationType) ||
          (annotationType == ResponseBody.class && this.returnValue instanceof ReactiveTypeHandler.CollectedValuesList));
    }
  }
  // 这个非常有意思   内部类继承了自己(外部类) 进行增强
  private class ConcurrentResultHandlerMethod extends ServletInvocableHandlerMethod {
    // 返回值
    private final MethodParameter returnType;
    // 此构造最终传入的handler是个Callable
    // result方法返回值 它支持支持异常调用结果处理
    public ConcurrentResultHandlerMethod(final Object result, ConcurrentResultMethodParameter returnType) {
      super((Callable<Object>) () -> {
        if (result instanceof Exception) {
          throw (Exception) result;
        } else if (result instanceof Throwable) {
          throw new NestedServletException("Async processing failed", (Throwable) result);
        }
        return result;
      }, CALLABLE_METHOD);
      // 给外部类把值设置上  因为wrapConcurrentResult一般都先调用,是对本类的一个增强
      if (ServletInvocableHandlerMethod.this.returnValueHandlers != null) {
        setHandlerMethodReturnValueHandlers(ServletInvocableHandlerMethod.this.returnValueHandlers);
      }
      this.returnType = returnType;
    }
    ...
  }
}


andlerMethod用于封装Handler和处理请求的Method;InvocableHandlerMethod增加了方法参数解析和调用方法的能力;ServletInvocableHandlerMethod在此基础上在增加了如下三个能力:


1.对@ResponseStatus注解的支持

   1.当一个方法注释了@ResponseStatus后,响应码就是注解上的响应码。 并且,并且如果returnValue=null或者reason不为空(不为null且不为“”),将中断处理直接返回(不再渲染页面)


2.对返回值returnValue的处理

   1. 对返回值的处理是使用HandlerMethodReturnValueHandlerComposite完成的


3.对异步处理结果的处理

相关文章
|
25天前
|
Java 关系型数据库 数据库
深度剖析【Spring】事务:万字详解,彻底掌握传播机制与事务原理
在Java开发中,Spring框架通过事务管理机制,帮我们轻松实现了这种“承诺”。它不仅封装了底层复杂的事务控制逻辑(比如手动开启、提交、回滚事务),还提供了灵活的配置方式,让开发者能专注于业务逻辑,而不用纠结于事务细节。
|
5月前
|
存储 人工智能 自然语言处理
RAG 调优指南:Spring AI Alibaba 模块化 RAG 原理与使用
通过遵循以上最佳实践,可以构建一个高效、可靠的 RAG 系统,为用户提供准确和专业的回答。这些实践涵盖了从文档处理到系统配置的各个方面,能够帮助开发者构建更好的 RAG 应用。
2576 114
|
2月前
|
前端开发 Java API
Spring Cloud Gateway Server Web MVC报错“Unsupported transfer encoding: chunked”解决
本文解析了Spring Cloud Gateway中出现“Unsupported transfer encoding: chunked”错误的原因,指出该问题源于Feign依赖的HTTP客户端与服务端的`chunked`传输编码不兼容,并提供了具体的解决方案。通过规范Feign客户端接口的返回类型,可有效避免该异常,提升系统兼容性与稳定性。
177 0
|
2月前
|
缓存 安全 Java
Spring 框架核心原理与实践解析
本文详解 Spring 框架核心知识,包括 IOC(容器管理对象)与 DI(容器注入依赖),以及通过注解(如 @Service、@Autowired)声明 Bean 和注入依赖的方式。阐述了 Bean 的线程安全(默认单例可能有安全问题,需业务避免共享状态或设为 prototype)、作用域(@Scope 注解,常用 singleton、prototype 等)及完整生命周期(实例化、依赖注入、初始化、销毁等步骤)。 解析了循环依赖的解决机制(三级缓存)、AOP 的概念(公共逻辑抽为切面)、底层动态代理(JDK 与 Cglib 的区别)及项目应用(如日志记录)。介绍了事务的实现(基于 AOP
111 0
|
2月前
|
SQL Java 数据库连接
Spring、SpringMVC 与 MyBatis 核心知识点解析
我梳理的这些内容,涵盖了 Spring、SpringMVC 和 MyBatis 的核心知识点。 在 Spring 中,我了解到 IOC 是控制反转,把对象控制权交容器;DI 是依赖注入,有三种实现方式。Bean 有五种作用域,单例 bean 的线程安全问题及自动装配方式也清晰了。事务基于数据库和 AOP,有失效场景和七种传播行为。AOP 是面向切面编程,动态代理有 JDK 和 CGLIB 两种。 SpringMVC 的 11 步执行流程我烂熟于心,还有那些常用注解的用法。 MyBatis 里,#{} 和 ${} 的区别很关键,获取主键、处理字段与属性名不匹配的方法也掌握了。多表查询、动态
110 0
|
2月前
|
JSON 前端开发 Java
第05课:Spring Boot中的MVC支持
第05课:Spring Boot中的MVC支持
148 0
|
2月前
|
监控 架构师 NoSQL
spring 状态机 的使用 + 原理 + 源码学习 (图解+秒懂+史上最全)
spring 状态机 的使用 + 原理 + 源码学习 (图解+秒懂+史上最全)
|
4月前
|
前端开发 Java 数据库连接
Spring核心原理剖析与解说
每个部分都是将一种巨大并且复杂的技术理念传达为更易于使用的接口,而这就是Spring的价值所在,它能让你专注于开发你的应用,而不必从头开始设计每一部分。
165 32
|
4月前
|
Java 开发者 Spring
Spring框架 - 深度揭秘Spring框架的基础架构与工作原理
所以,当你进入这个Spring的世界,看似一片混乱,但细看之下,你会发现这里有个牢固的结构支撑,一切皆有可能。不论你要建设的是一座宏大的城堡,还是个小巧的花园,只要你的工具箱里有Spring,你就能轻松搞定。
187 9
|
5月前
|
安全 前端开发 Java
Spring Boot 项目中触发 Circular View Path 错误的原理与解决方案
在Spring Boot开发中,**Circular View Path**错误常因视图解析与Controller路径重名引发。当视图名称(如`login`)与请求路径相同,Spring MVC无法区分,导致无限循环调用。解决方法包括:1) 明确指定视图路径,避免重名;2) 将视图文件移至子目录;3) 确保Spring Security配置与Controller路径一致。通过合理设定视图和路径,可有效避免该问题,确保系统稳定运行。
343 0

热门文章

最新文章