SpringFramework之HandlerMethodReturnValueHandler

Easter79
• 阅读 478

    SpringFramework版本是5.1.9

    List-1

@Controller
public class UserController {

    @RequestMapping("/user")
    @ResponseBody
    public User user(){
        System.out.println("收到请求");
        User alen = new User("Alen", 25);
        return alen;
    }
}

...

public class User {
    private String name;
    private int age;
...

    如上所示List-1,springboot中这样使用后,会返还json数据,那么spring是怎么将User序列化后写入response的呢?

    DispatcherServlet的doDispatch方法中,调用HandlerAdapter.handle方法,

    AbstractHandlerMethodAdapter#handle->RequestMappingHandlerAdapter#handleInternal->invokeHandlerMethod

        ->ServletInvocableHandlerMethod#invokeAndHandle

    List-2

public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer,
        Object... providedArgs) throws Exception {
    //调用controller的方法得到User
    Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
    setResponseStatus(webRequest);

    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;
    }

    mavContainer.setRequestHandled(false);
    Assert.state(this.returnValueHandlers != null, "No return value handlers");
    try {
        //调用ReturnValueHandler
        this.returnValueHandlers.handleReturnValue(
                returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
    }
    catch (Exception ex) {
        if (logger.isTraceEnabled()) {
            logger.trace(formatErrorForReturnValue(returnValue), ex);
        }
        throw ex;
    }
}

    如List-2所示

  •     invokeForRequest方法使用反射调用UserController.user方法,得到User对象
  •     之后调用returnValueHandlers.handleReturnValue方法,这个方法里面就是将User对象写入response流中

    List-3

private HandlerMethodReturnValueHandlerComposite returnValueHandlers;

    如List-3所示,HandlerMethodReturnValueHandlerComposite,看到这个类名称,就知道使用了组合模式,不用说这个类的内部一定有一个集合来存储HandlerMethodReturnValueHandler。

    来看HandlerMethodReturnValueHandlerComposite的handleReturnValue方法

    List-4

public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
    HandlerMethodReturnValueHandler handler = this.selectHandler(returnValue, returnType);
    if (handler == null) {
        throw new IllegalArgumentException("Unknown return value type: " + returnType.getParameterType().getName());
    } else {
        handler.handleReturnValue(returnValue, returnType, mavContainer, webRequest);
    }
}

@Nullable
private HandlerMethodReturnValueHandler selectHandler(@Nullable Object value, MethodParameter returnType) {
    ...
    Iterator var4 = this.returnValueHandlers.iterator();

    HandlerMethodReturnValueHandler handler;
    do {
        ...
    } while(!handler.supportsReturnType(returnType));

    return handler;
}

    如上List-4

  • returnValueHandlers的类型是List
  • 循环遍历returnValueHandlers,看哪个handler支持处理这种返回类型,即handler.supportsReturnType()
  • 调用HandlerMethodReturnValueHandler.handleReturnValue来处理UserController的返还结果

    HandlerMethodReturnValueHandler的实现类有15种,List-1中的情况使用哪个HandlerMethodReturnValueHandler呢? 

    是RequestResponseBodyMethodProcessor,我们来看它的supportsReturnType方法和handleReturnValue方法

    List-5

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

...

@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);
}

...

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

    如上List-5

  • supportsReturnType方法中,AnnotatedElementUtils.hasAnnotation(returnType.getContainingClass(), ResponseBody.class)判断UserController类上有没有ResponseBody注解,@RestController注解就是@Controller和@ResponseBody注解的组合,所以只要Controller类上有@RestController注解,就会由这行类来处理
  • supportsReturnType方法中,returnType.hasMethodAnnotation(ResponseBody.class)判断user()方法上是否有@ResponseBody注解,由于UserController.user方法上有ResponseBody注解,所以这个方法返还true

    handleReturnValue方法中,

  • 调用createOutputMessage方法,获取ServletServerHttpResponse
  • 之后在writeWithMessageConverters中将User对象序列化后输出到response流中

    writeWithMessageConverters方法中,使用到HttpMessageConverter,用HttpMessageConverter来将User对象序列化,具体实现类是MappingJackson2HttpMessageConverter。writeWithMessageConverters方法内部还是和HandlerMethodReturnValueHandlerComposite一样使用到了组合,然后使用Template设计模式,所以有的人使用Fastjson来做序列化时就是自定义了HttpMessageConverter的实现,然后注册到spring容器中

    一些思考,spring中有ViewResolver用来将结果渲染为页面,但是rest接口是不由ViewResolver来处理的,所以在ViewResolver那打断点,程序还没执行到那,但是结果已经返还到客户端了。

点赞
收藏
评论区
推荐文章
blmius blmius
2年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
Wesley13 Wesley13
2年前
java将前端的json数组字符串转换为列表
记录下在前端通过ajax提交了一个json数组的字符串,在后端如何转换为列表。前端数据转化与请求varcontracts{id:'1',name:'yanggb合同1'},{id:'2',name:'yanggb合同2'},{id:'3',name:'yang
Easter79 Easter79
2年前
SpringFramework之RequestBodyAdvice的使用
  Spring版本5.1.4.release.  前一篇(https://my.oschina.net/u/2518341/blog/4329778)讲了RequestBodyAdvice的实现  有人用RequestBodyAdvice来做参数的解密(前端传过来的是加密的),或者使用RequestBodyAdvice进行全局统一返
Easter79 Easter79
2年前
TypeScript 4.0正式发布!现在是开始使用它的最佳时机
!(https://oscimg.oschina.net/oscnet/9d5dde21c00d49b6b148bfabfc93db57.jpg)作者|DanielRosenwasser译者|王强策划|李俊辰稿源|前端之巅微软宣布TypeScript4.0正式版上线了!这一新版本深入改进了表现力、生产力
Easter79 Easter79
2年前
SpringFramework之ContentNegotiation内容协商
  Spring版本5.1.4.release.  内容协商是用在Springmvc返回Controller方法结果序列化时使用,而不是解析mvc参数时使用。    Springmvc支持4种内容协商,拓展名、固定值、Http的头部Accept、请求参数format,那Springmvc中怎么实现的呢,怎么使用已经有很多人分析了,这里来分析下
Wesley13 Wesley13
2年前
Java爬虫之JSoup使用教程
title:Java爬虫之JSoup使用教程date:201812248:00:000800update:201812248:00:000800author:mecover:https://imgblog.csdnimg.cn/20181224144920712(https://www.oschin
Stella981 Stella981
2年前
SpringBoot HTTP接口 GET请求
1\.普通传参@RequestMapping(path"/{city_id}/{user_id}",methodRequestMethod.GET)publicObjectfindUser(@PathVariable("city_id")StringcityId,
Wesley13 Wesley13
2年前
mysql设置时区
mysql设置时区mysql\_query("SETtime\_zone'8:00'")ordie('时区设置失败,请联系管理员!');中国在东8区所以加8方法二:selectcount(user\_id)asdevice,CONVERT\_TZ(FROM\_UNIXTIME(reg\_time),'08:00','0
Easter79 Easter79
2年前
SpringFramework之ViewResolver优化
  Springboot版本是2.x  Springboot中,对mvc进行自动化配置时在WebMvcAutoConfiguration中会自动注入InternalResourceViewResolver(渲染jsp的)、BeanNameViewResolver、ContentNegotiatingViewResolver。  但是我们一
Wesley13 Wesley13
2年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
Easter79
Easter79
Lv1
今生可爱与温柔,每一样都不能少。
文章
2.8k
粉丝
5
获赞
1.2k