SpringMVC常见组件之HandlerMethodReturnValueHandler解析-2

本文涉及的产品
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
全局流量管理 GTM,标准版 1个月
云解析 DNS,旗舰版 1个月
简介: SpringMVC常见组件之HandlerMethodReturnValueHandler解析-2

SpringMVC常见组件之HandlerMethodReturnValueHandler解析-1  https://developer.aliyun.com/article/1382023

【8】ModelAttributeMethodProcessor


ModelAttributeMethodProcessor与子类ServletModelAttributeMethodProcessor主要用来处理标注了@ModelAttribute注解的方法,解析方法的参数并处理方法的返回结果。如果属性annotationNotRequired被设置为true,那么非简单类型的参数或返回结果将会被该处理器默认处理。

① supportsReturnType

处理标注了@ModelAttribute注解的返回结果或者属性annotationNotRequired 为true并且返回结果类型为非简单类型。

@Override
public boolean supportsReturnType(MethodParameter returnType) {
  return (returnType.hasMethodAnnotation(ModelAttribute.class) ||
      (this.annotationNotRequired && !BeanUtils.isSimpleProperty(returnType.getParameterType())));
}


什么是简单类型?如下图所示(前面我们在SpringMVC常见组件之HandlerMethodArgumentResolver解提到过)。


② handleReturnValue

如下所示如果返回结果不为null,则根据返回结果值与类型获取name,然后放到mavContainer的model中。

@Override
public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
    ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
  if (returnValue != nul  l) {
    String name = ModelFactory.getNameForReturnValue(returnValue, returnType);
    mavContainer.addAttribute(name, returnValue);
  }
}


获取name的方法如下所示,如果有注解ModelAttribute并且注解value属性不为null,则使用注解的value属性。否则的话根据方法与所属类获取返回结果类型Class,进而生成一个name。

public static String getNameForReturnValue(@Nullable Object returnValue, MethodParameter returnType) {
  ModelAttribute ann = returnType.getMethodAnnotation(ModelAttribute.class);
  if (ann != null && StringUtils.hasText(ann.value())) {
    return ann.value();
  }
  else {
    Method method = returnType.getMethod();
    Assert.state(method != null, "No handler method");
    Class<?> containingClass = returnType.getContainingClass();
    Class<?> resolvedType = GenericTypeResolver.resolveReturnType(method, containingClass);
    return Conventions.getVariableNameForReturnType(method, resolvedType, returnValue);
  }
}

【9】HttpHeadersReturnValueHandler

① supportsReturnType

处理返回结果类型是HttpHeaders的。

@Override
public boolean supportsReturnType(MethodParameter returnType) {
  return HttpHeaders.class.isAssignableFrom(returnType.getParameterType());
}

② handleReturnValue

@Override
@SuppressWarnings("resource")
public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
    ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
  mavContainer.setRequestHandled(true);
  Assert.state(returnValue instanceof HttpHeaders, "HttpHeaders expected");
  HttpHeaders headers = (HttpHeaders) returnValue;
  if (!headers.isEmpty()) {
    HttpServletResponse servletResponse = webRequest.getNativeResponse(HttpServletResponse.class);
    Assert.state(servletResponse != null, "No HttpServletResponse");
    ServletServerHttpResponse outputMessage = new ServletServerHttpResponse(servletResponse);
    outputMessage.getHeaders().putAll(headers);
    outputMessage.getBody();  // flush headers
  }
}

方法解释如下:


① 设置当前请求已经被处理mavContainer.setRequestHandled(true);

② 判断返回结果必须为HttpHeaders类型,否则抛出IllegalStateException异常;

③ 如果headers不为空,则获取ServletServerHttpResponse 然后将headers添加到响应头中;

④ 调用outputMessage.getBody();这里会进一步调用writeHeaders();,也就是所谓的flush headers。

【10】HttpEntityMethodProcessor


HttpEntityMethodProcessor继承自AbstractMessageConverterMethodProcessor,主要是解析HttpEntity和RequestEntity方法参数并处理HttpEntity类型和ResponseEntity类型的返回结果。


同ModelAndViewMethodReturnValueHandler一样,HttpEntityMethodProcessor应该被配置在某些处理器**前面(**支持标注了@ModelAttribute或@ResponseBody注解的方法的返回结果类型的),以免被覆盖。

① supportsReturnType

返回结果是HttpEntity类型并且不是RequestEntity子类型(RequestEntity是HttpEntity的子类)。

@Override
public boolean supportsReturnType(MethodParameter returnType) {
  return (HttpEntity.class.isAssignableFrom(returnType.getParameterType()) &&
      !RequestEntity.class.isAssignableFrom(returnType.getParameterType()));
}


② handleReturnValue

@Override
public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
    ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
  // 设置请求已经被处理
  mavContainer.setRequestHandled(true);
  // 如果返回结果为null,直接返回
  if (returnValue == null) {
    return;
  }
  // 根据NativeWebRequest获取ServletServerHttpRequest
  ServletServerHttpRequest inputMessage = createInputMessage(webRequest);
  //根据NativeWebRequest获取ServletServerHttpResponse 
  ServletServerHttpResponse outputMessage = createOutputMessage(webRequest);
  //如果不是HttpEntity类型则抛出异常
  Assert.isInstanceOf(HttpEntity.class, returnValue); 
  HttpEntity<?> responseEntity = (HttpEntity<?>) returnValue;
  // 获取原始响应头
  HttpHeaders outputHeaders = outputMessage.getHeaders();
  // 获取返回结果的头信息
  HttpHeaders entityHeaders = responseEntity.getHeaders();
  // 如果返回结果头信息不为空,则将其放入原始响应头中。注意这里对VARY进行了特殊处理
  if (!entityHeaders.isEmpty()) {
    entityHeaders.forEach((key, value) -> {
      if (HttpHeaders.VARY.equals(key) && outputHeaders.containsKey(HttpHeaders.VARY)) {
        List<String> values = getVaryRequestHeadersToAdd(outputHeaders, entityHeaders);
        if (!values.isEmpty()) {
          outputHeaders.setVary(values);
        }
      }
      else {
        outputHeaders.put(key, value);
      }
    });
  }
  // 如果返回结果是ResponseEntity类型,根据200或者3XX进行不同处理
  if (responseEntity instanceof ResponseEntity) {
    int returnStatus = ((ResponseEntity<?>) responseEntity).getStatusCodeValue();
    outputMessage.getServletResponse().setStatus(returnStatus);
    if (returnStatus == 200) {
      HttpMethod method = inputMessage.getMethod();
      if ((HttpMethod.GET.equals(method) || HttpMethod.HEAD.equals(method))
          && isResourceNotModified(inputMessage, outputMessage)) {
        outputMessage.flush();
        return;
      }
    }
    else if (returnStatus / 100 == 3) {
      String location = outputHeaders.getFirst("location");
      if (location != null) {
        saveFlashAttributes(mavContainer, webRequest, location);
      }
    }
  }
  // Try even with null body. ResponseBodyAdvice could get involved.
  writeWithMessageConverters(responseEntity.getBody(), returnType, inputMessage, outputMessage);
  // Ensure headers are flushed even if no body was written.
  outputMessage.flush();
}

方法首先进行了前置处理,设置RequestHandled为true标明请求已经处理完毕。如果判断返回结果为null则直接返回。


下面两行代码很有意思,这里webRequest是NativeWebRequest类型,实现类如ServletRequestAttributes、FacesWebRequest等。ServletRequestAttributes与FacesWebRequest拥有属性HttpServletRequest request、HttpServletResponse response与HttpSession session等。


在其createInputMessage方法和createOutputMessage中首先获取HttpServletRequest 、HttpServletResponse 然后分别包装成ServletServerHttpRequest与ServletServerHttpResponse返回。所以千万不要别参数名字误导,以为其只是一个单纯的request。

ServletServerHttpRequest inputMessage = createInputMessage(webRequest);
ServletServerHttpResponse outputMessage = createOutputMessage(webRequest);
protected ServletServerHttpRequest createInputMessage(NativeWebRequest webRequest) {
  HttpServletRequest servletRequest = webRequest.getNativeRequest(HttpServletRequest.class);
  Assert.state(servletRequest != null, "No HttpServletRequest");
  return new ServletServerHttpRequest(servletRequest);
}
protected ServletServerHttpResponse createOutputMessage(NativeWebRequest webRequest) {
  HttpServletResponse response = webRequest.getNativeResponse(HttpServletResponse.class);
  Assert.state(response != null, "No HttpServletResponse");
  return new ServletServerHttpResponse(response);
}

然后就是对entityHeaders进行了处理,基本就是把entityHeaders放入原始ServletServerHttpResponse的HttpHeaders中。注意,这里对HttpHeaders.VARY做了特殊处理。


如果返回结果是ResponseEntity类型,那么根据状态码是200或者3XX系列(重定向)进行不同处理。


如果status是200,且方法是(GET方法或HEAD方法)&&ResourceNotModified,那么会调用响应的flush方法(写入响应头、刷空缓冲区);

如果状态码为3XX,获取响应头中的location。如果location不为空,则进行“闪存属性”处理。简单来说就是重定向属性的更新。

writeWithMessageConverters这一步很关键,会进行message的转换,也就是所谓的格式化。将响应内容转换为需要的格式。


outputMessage.flush();会确保headers被flushed即时响应体为空。这里我们可以看下flush方法,如下所示其首先会处理header。然后判断当前body是否被使用,如果被使用则调用flushBuffer方法将缓冲区的内容刷到客户端。

@Override
public void flush() throws IOException {
  writeHeaders();
  if (this.bodyUsed) {
    this.servletResponse.flushBuffer();
  }
}

【11】RequestResponseBodyMethodProcessor


你所使用的@RequestBody注解或者@ResponseBody注解起作用就是该Processor在工作。该Processor会处理标注了@RequestBody的方法参数和标注了@ResponseBody注解的方法返回结果。其将会从请求中读入信息并将返回结果写入到响应体中,在这其中会使用HttpMessageConverter进行必要的格式转换。


如果使用@Valid注解,那么@RequestBody的方法参数同样被校验。如果配置了DefaultHandlerExceptionResolver,那么校验失败时会抛出状态码为400的异常。

① supportsReturnType

使用了@ResponseBody注解或者方法标注了@ResponseBody注解。

@Override
public boolean supportsReturnType(MethodParameter returnType) {
  return (AnnotatedElementUtils.hasAnnotation(returnType.getContainingClass(), ResponseBody.class) ||
      returnType.hasMethodAnnotation(ResponseBody.class));
}

② handleReturnValue

@Override
public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
    ModelAndViewContainer mavContainer, NativeWebRequest webRequest)
    throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {
  mavContainer.setRequestHandled(true);
  ServletServerHttpRequest inputMessage = createInputMessage(webRequest);
  ServletServerHttpResponse outputMessage = createOutputMessage(webRequest);
  // Try even with null return value. ResponseBodyAdvice could get involved.
  writeWithMessageConverters(returnValue, returnType, inputMessage, outputMessage);
}

相比HttpEntityMethodProcessor,这里返回结果处理就简单多了。


① 设置请求被处理完毕;

② 获取ServletServerHttpRequest 和ServletServerHttpResponse

③ 进行信息格式转换,然后将转换后的message写入到响应体中,最后会调用outputMessage.getBody().flush();方法,将缓冲区的内容刷冲到客户端。

③ createInputMessage

我们看一下其ServletServerHttpRequest inputMessage = createInputMessage(webRequest);。如下所示其根据NativeWebRequest 获取到HttpServletRequest 然后包装为ServletServerHttpRequest实例对象返回。

protected ServletServerHttpRequest createInputMessage(NativeWebRequest webRequest) {
  HttpServletRequest servletRequest = webRequest.getNativeRequest(HttpServletRequest.class);
  Assert.state(servletRequest != null, "No HttpServletRequest");
  return new ServletServerHttpRequest(servletRequest);
}

如下图所示,ServletServerHttpRequest 实现了ServerHttpRequest接口(后者继承了HttpRequest, HttpInputMessage接口)

我们再顺便看一下这几个接口继承示意与各自提供的方法:

public interface HttpMessage {
  HttpHeaders getHeaders();
}
public interface HttpInputMessage extends HttpMessage {
  InputStream getBody() throws IOException;
}
public interface HttpRequest extends HttpMessage {
  @Nullable
  default HttpMethod getMethod() {
    return HttpMethod.resolve(getMethodValue());
  }
  String getMethodValue();
  URI getURI();
}
public interface ServerHttpRequest extends HttpRequest, HttpInputMessage {
  @Nullable
  Principal getPrincipal();
  InetSocketAddress getLocalAddress();
  InetSocketAddress getRemoteAddress();
  ServerHttpAsyncRequestControl getAsyncRequestControl(ServerHttpResponse response);
}

④ createOutputMessage(webRequest)

我们继续看一下ServletServerHttpResponse outputMessage = createOutputMessage(webRequest);,放入如下所示根据NativeWebRequest 拿到HttpServletResponse (我们经常使用的),然后作为构造函数入参实例化一个ServletServerHttpResponse对象。

protected ServletServerHttpResponse createOutputMessage(NativeWebRequest webRequest) {
  HttpServletResponse response = webRequest.getNativeResponse(HttpServletResponse.class);
  Assert.state(response != null, "No HttpServletResponse");
  return new ServletServerHttpResponse(response);
}

我们再顺便看一下这几个接口继承示意与各自提供的方法:

public interface HttpMessage {
  HttpHeaders getHeaders();
}
public interface HttpOutputMessage extends HttpMessage {
  OutputStream getBody() throws IOException;
}
public interface ServerHttpResponse extends HttpOutputMessage, Flushable, Closeable {
  void setStatusCode(HttpStatus status);
  @Override
  void flush() throws IOException;
  @Override
  void close();
}


⑤ writeWithMessageConverters

HttpEntityMethodProcessor和RequestResponseBodyMethodProcessor都是AbstractMessageConverterMethodProcessor的子类,其继承自AbstractMessageConverterMethodArgumentResolver,提供了能力-使用HttpMessageConverter处理方法返回结果,将转换后的结果写入到response。

这里我们先看一下writeWithMessageConverters方法执行前各个参数实例。

我们展开看一下MethodParameter returnType

protected <T> void writeWithMessageConverters(@Nullable T value, MethodParameter returnType,
    ServletServerHttpRequest inputMessage, ServletServerHttpResponse outputMessage)
    throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {
  Object body;
  Class<?> valueType;
  Type targetType;
// 如果返回结果是String
  if (value instanceof CharSequence) {
    body = value.toString();
    valueType = String.class;
    targetType = String.class;
  }
  else {// 返回结果不是string
    body = value;
    // 获取值类型
    valueType = getReturnValueType(body, returnType);
    // 获取返回结果目标类型
    targetType = GenericTypeResolver.resolveType(getGenericType(returnType), returnType.getContainingClass());
  }
// 如果返回结果类型是Resource,但不是InputStreamResource
  if (isResourceType(value, returnType)) {
    outputMessage.getHeaders().set(HttpHeaders.ACCEPT_RANGES, "bytes");
    if (value != null && inputMessage.getHeaders().getFirst(HttpHeaders.RANGE) != null &&
        outputMessage.getServletResponse().getStatus() == 200) {
      Resource resource = (Resource) value;
      try {
        List<HttpRange> httpRanges = inputMessage.getHeaders().getRange();
        outputMessage.getServletResponse().setStatus(HttpStatus.PARTIAL_CONTENT.value());
        body = HttpRange.toResourceRegions(httpRanges, resource);
        valueType = body.getClass();
        targetType = RESOURCE_REGION_LIST_TYPE;
      }
      catch (IllegalArgumentException ex) {
        outputMessage.getHeaders().set(HttpHeaders.CONTENT_RANGE, "bytes */" + resource.contentLength());
        outputMessage.getServletResponse().setStatus(HttpStatus.REQUESTED_RANGE_NOT_SATISFIABLE.value());
      }
    }
  }
  MediaType selectedMediaType = null;
  // 获取响应的contentType 
  MediaType contentType = outputMessage.getHeaders().getContentType();
  // 判断contentType是不是具体的--即不带通配符*
  boolean isContentTypePreset = contentType != null && contentType.isConcrete();
  if (isContentTypePreset) {
    if (logger.isDebugEnabled()) {
      logger.debug("Found 'Content-Type:" + contentType + "' in response");
    }
    // 如果返回结果是具体的,则直接赋予selectedMediaType 
    selectedMediaType = contentType;
  }
  else {
  // 否则就要根据acceptableTypes 与producibleTypes 选择一个合适的selectedMediaType 
    HttpServletRequest request = inputMessage.getServletRequest();
    // 通过contentNegotiationManager解析request获取mediaType,其实就是根据HttpHeaders.ACCEPT
    List<MediaType> acceptableTypes = getAcceptableMediaTypes(request);
    // 1.从request中获取HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE属性对应的值;
    // 2.获取能够对当前返回结果进行写操作的converter支持的mediaType
    List<MediaType> producibleTypes = getProducibleMediaTypes(request, valueType, targetType);
    if (body != null && producibleTypes.isEmpty()) {
      throw new HttpMessageNotWritableException(
          "No converter found for return value of type: " + valueType);
    }
    List<MediaType> mediaTypesToUse = new ArrayList<>();
    for (MediaType requestedType : acceptableTypes) {
      for (MediaType producibleType : producibleTypes) {
        if (requestedType.isCompatibleWith(producibleType)) {
          mediaTypesToUse.add(getMostSpecificMediaType(requestedType, producibleType));
        }
      }
    }
    // 没有找到能够使用的mediaTypes,就抛出异常HttpMediaTypeNotAcceptableException
    if (mediaTypesToUse.isEmpty()) {
      if (body != null) {
        throw new HttpMediaTypeNotAcceptableException(producibleTypes);
      }
      if (logger.isDebugEnabled()) {
        logger.debug("No match for " + acceptableTypes + ", supported: " + producibleTypes);
      }
      return;
    }
    //对mediaTypesToUse进行排序 具体的在前
    MediaType.sortBySpecificityAndQuality(mediaTypesToUse);
    for (MediaType mediaType : mediaTypesToUse) {
      if (mediaType.isConcrete()) {
        selectedMediaType = mediaType;
        break;
      }
      else if (mediaType.isPresentIn(ALL_APPLICATION_MEDIA_TYPES)) {
        selectedMediaType = MediaType.APPLICATION_OCTET_STREAM;
        break;
      }
    }
    if (logger.isDebugEnabled()) {
      logger.debug("Using '" + selectedMediaType + "', given " +
          acceptableTypes + " and supported " + producibleTypes);
    }
  }
// 如果selectedMediaTypebuw
  if (selectedMediaType != null) {
    // 移除掉mediaType中的q
    selectedMediaType = selectedMediaType.removeQualityValue();
    // 遍历messageConverters 找到一个合适的HttpMessageConverter
    for (HttpMessageConverter<?> converter : this.messageConverters) {
     // 判断当前converter是不是GenericHttpMessageConverter类型
      GenericHttpMessageConverter genericConverter = (converter instanceof GenericHttpMessageConverter ?
          (GenericHttpMessageConverter<?>) converter : null);
      if (genericConverter != null ?
          ((GenericHttpMessageConverter) converter).canWrite(targetType, valueType, selectedMediaType) :
          converter.canWrite(valueType, selectedMediaType)) {
          // 在向outputmessage写入body前,获取adviceBean,对响应结果进行处理
        body = getAdvice().beforeBodyWrite(body, returnType, selectedMediaType,
            (Class<? extends HttpMessageConverter<?>>) converter.getClass(),
            inputMessage, outputMessage);
        if (body != null) {
          Object theBody = body;
          LogFormatUtils.traceDebug(logger, traceOn ->
              "Writing [" + LogFormatUtils.formatValue(theBody, !traceOn) + "]");
              // 尝试添加HttpHeaders.CONTENT_DISPOSITION
          addContentDispositionHeader(inputMessage, outputMessage);
          if (genericConverter != null) {
          // 使用genericConverter 处理body然后写入到outputmessage
            genericConverter.write(body, targetType, selectedMediaType, outputMessage);
          }
          else {
          // 使用HttpMessageConverter处理body然后写入到outputmessage
            ((HttpMessageConverter) converter).write(body, selectedMediaType, outputMessage);
          }
        }
        else {
          if (logger.isDebugEnabled()) {
            logger.debug("Nothing to write: null body");
          }
        }
        return;
      }
    }
  }
// 这里只会抛出异常
  if (body != null) {
    Set<MediaType> producibleMediaTypes =
        (Set<MediaType>) inputMessage.getServletRequest()
            .getAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);
    if (isContentTypePreset || !CollectionUtils.isEmpty(producibleMediaTypes)) {
      throw new HttpMessageNotWritableException(
          "No converter for [" + valueType + "] with preset Content-Type '" + contentType + "'");
    }
    throw new HttpMediaTypeNotAcceptableException(this.allSupportedMediaTypes);
  }
}


this.messageConverters如下所示:


下面这个判断很有意思,如果genericConverter 不为null,则判断其是否能够进行写操作,否则判断类型转换前的converter是否能够进行写操作。如果都为false,则直接进行下个循环。默认情况下,这里我们会找到MappingJackson2HttpMessageConverter进行处理。

if (genericConverter != null ?
          ((GenericHttpMessageConverter) converter).canWrite(targetType, valueType, selectedMediaType) :
          converter.canWrite(valueType, selectedMediaType))

producibleTypes与排序后的mediaTypesToUse:

GenericHttpMessageConverter 的子类有如下所示:



如下图所示,当writeWithMessageConverters方法中下面代码执行完,客户端就会拿到响应结果。此时整个请求流程对springmvc来说还未结束!!!

if (genericConverter != null) {
  genericConverter.write(body, targetType, selectedMediaType, outputMessage);
}
else {
  ((HttpMessageConverter) converter).write(body, selectedMediaType, outputMessage);
}


【12ModelAndViewResolverMethodReturnValueHandler


这个处理器比较有意思,其supportsReturnType仅仅只是返回true。也就意味着,这是最后的保留手段。所以该处理器是配置在所有处理器的后面。


返回值可以使用ModelAndViewResolver处理。如果是非简单类型,则可以将其视为模型属性(model attribute使用modelAttributeProcessor处理)。如果这两种方法都不成功(本质上是简单类型,而不是字符串),则会引发UnsupportdOperationException。


需要注意的是,ModelAndViewResolver只是一个接口,目前并没有实现类。所以你可以实现ModelAndViewResolver将其作为一个HandlerMethodReturnValueHandler如下所示:其拥有两个属性mavResolversmodelAttributeProcessor

@Nullable
private final List<ModelAndViewResolver> mavResolvers;
private final ModelAttributeMethodProcessor modelAttributeProcessor =
    new ServletModelAttributeMethodProcessor(true);
@Override
public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
    ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
  if (this.mavResolvers != null) {
    for (ModelAndViewResolver mavResolver : this.mavResolvers) {
      Class<?> handlerType = returnType.getContainingClass();
      Method method = returnType.getMethod();
      Assert.state(method != null, "No handler method");
      ExtendedModelMap model = (ExtendedModelMap) mavContainer.getModel();
      // 解析得到MAV
      ModelAndView mav = mavResolver.resolveModelAndView(method, handlerType, returnValue, model, webRequest);
      if (mav != ModelAndViewResolver.UNRESOLVED) {
       //将属性放入mavContainer
        mavContainer.addAllAttributes(mav.getModel());
        //为mavContainer设置viewName
        mavContainer.setViewName(mav.getViewName());
        if (!mav.isReference()) {
          //设置view
          mavContainer.setView(mav.getView());
        }
        return;
      }
    }
  }
  // No suitable ModelAndViewResolver...
  if (this.modelAttributeProcessor.supportsReturnType(returnType)) {
    this.modelAttributeProcessor.handleReturnValue(returnValue, returnType, mavContainer, webRequest);
  }
  else {
    throw new UnsupportedOperationException("Unexpected return type: " +
        returnType.getParameterType().getName() + " in method: " + returnType.getMethod());
  }
}


方法如上所示,先尝试使用ModelAndViewResolver 进行处理,然后使用modelAttributeProcessor进行处理。默认ModelAndViewResolver没有实现类,所以通常这里是将动作委派给具体的modelAttributeProcessor进行处理。


如果你想实现一个ModelAndViewResolver,那么考虑本文中【3】、【4】、【5】、【6】、【7】、【8】的实现。

目录
相关文章
|
27天前
|
前端开发 JavaScript
React 步骤条组件 Stepper 深入解析与常见问题
步骤条组件是构建多步骤表单或流程时的有力工具,帮助用户了解进度并导航。本文介绍了在React中实现简单步骤条的方法,包括基本结构、状态管理、样式处理及常见问题解决策略,如状态管理库的使用、自定义Hook的提取和CSS Modules的应用,以确保组件的健壮性和可维护性。
63 17
|
2月前
|
监控 Java 应用服务中间件
高级java面试---spring.factories文件的解析源码API机制
【11月更文挑战第20天】Spring Boot是一个用于快速构建基于Spring框架的应用程序的开源框架。它通过自动配置、起步依赖和内嵌服务器等特性,极大地简化了Spring应用的开发和部署过程。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,特别是spring.factories文件的解析源码API机制。
92 2
|
14天前
|
设计模式 XML Java
【23种设计模式·全精解析 | 自定义Spring框架篇】Spring核心源码分析+自定义Spring的IOC功能,依赖注入功能
本文详细介绍了Spring框架的核心功能,并通过手写自定义Spring框架的方式,深入理解了Spring的IOC(控制反转)和DI(依赖注入)功能,并且学会实际运用设计模式到真实开发中。
【23种设计模式·全精解析 | 自定义Spring框架篇】Spring核心源码分析+自定义Spring的IOC功能,依赖注入功能
|
2月前
|
XML Java 数据库连接
Spring高手之路25——深入解析事务管理的切面本质
本篇文章将带你深入解析Spring事务管理的切面本质,通过AOP手动实现 @Transactional 基本功能,并探讨PlatformTransactionManager的设计和事务拦截器TransactionInterceptor的工作原理,结合时序图详细展示事务管理流程,最后引导分析 @Transactional 的代理机制源码,帮助你全面掌握Spring事务管理。
40 2
Spring高手之路25——深入解析事务管理的切面本质
|
1月前
|
前端开发 UED
React 文本区域组件 Textarea:深入解析与优化
本文介绍了 React 中 Textarea 组件的基础用法、常见问题及优化方法,包括状态绑定、初始值设置、样式自定义、性能优化和跨浏览器兼容性处理,并提供了代码案例。
58 8
|
2月前
|
Java 开发者 Spring
深入解析:Spring AOP的底层实现机制
在现代软件开发中,Spring框架的AOP(面向切面编程)功能因其能够有效分离横切关注点(如日志记录、事务管理等)而备受青睐。本文将深入探讨Spring AOP的底层原理,揭示其如何通过动态代理技术实现方法的增强。
80 8
|
2月前
|
前端开发 Java 开发者
Spring MVC中的请求映射:@RequestMapping注解深度解析
在Spring MVC框架中,`@RequestMapping`注解是实现请求映射的关键,它将HTTP请求映射到相应的处理器方法上。本文将深入探讨`@RequestMapping`注解的工作原理、使用方法以及最佳实践,为开发者提供一份详尽的技术干货。
151 2
|
2月前
|
前端开发 Java Spring
探索Spring MVC:@Controller注解的全面解析
在Spring MVC框架中,`@Controller`注解是构建Web应用程序的基石之一。它不仅简化了控制器的定义,还提供了一种优雅的方式来处理HTTP请求。本文将全面解析`@Controller`注解,包括其定义、用法、以及在Spring MVC中的作用。
61 2
|
2月前
|
前端开发 Java Maven
深入解析:如何用 Spring Boot 实现分页和排序
深入解析:如何用 Spring Boot 实现分页和排序
66 2
|
2月前
|
Java 开发者 Spring
Spring AOP深度解析:探秘动态代理与增强逻辑
Spring框架中的AOP(Aspect-Oriented Programming,面向切面编程)功能为开发者提供了一种强大的工具,用以将横切关注点(如日志、事务管理等)与业务逻辑分离。本文将深入探讨Spring AOP的底层原理,包括动态代理机制和增强逻辑的实现。
51 4

推荐镜像

更多