zoukankan      html  css  js  c++  java
  • SpringMVC之json是怎么传回前端的 @ResponseBody解析

    一 序章 

     

      http的请求里有一个属性叫accept,它规定了返回值类型,本篇要讲的返回值正是跟这个属性关系紧密

      

    二 源码分析

      SpringMVC为@RequestBody和@ResponseBody两个注解实现了统一处理类RequestResponseBodyMethodProcessor,实现了HandlerMethodArgumentResolver和HandlerMethodReturnValueHandler两个接口。

    ServletInvocableHandlerMethod
    public
    void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception { Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs); setResponseStatus(webRequest); if (returnValue == null) { if (isRequestNotModified(webRequest) || getResponseStatus() != null || mavContainer.isRequestHandled()) { mavContainer.setRequestHandled(true); return; } } else if (StringUtils.hasText(getResponseStatusReason())) { mavContainer.setRequestHandled(true); return; } mavContainer.setRequestHandled(false); try { 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; } }

      要执行就离不开  returnValueHandlers 。先讲讲这些  returnValueHandlers 怎么初始化的

      我们先看  RequestMappingHandlerAdapter.invokeHandlerMethod 

    protected ModelAndView invokeHandlerMethod(HttpServletRequest request,
                HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
    
            ServletWebRequest webRequest = new ServletWebRequest(request, response);
            try {
                WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod);
                ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory);
    
                ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);
                invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
                invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);//设置返回值Handler
                invocableMethod.setDataBinderFactory(binderFactory);
                invocableMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer);

      注意这里,每次处理请求的时候,把原始HandlerMethod包装成一个 ServletInvocableHandlerMethod,这里用的是每次都new一个出来

    protected ServletInvocableHandlerMethod createInvocableHandlerMethod(HandlerMethod handlerMethod) {
            return new ServletInvocableHandlerMethod(handlerMethod);
        }

      所以就来看看 RequestMappingHandlerAdapter里的 returnValueHandlers 是怎么来的

      现在已经知道了 RequestMappingHandlerAdapter 会被当成一个bean来进行初始化,而且它还实现了 InitializingBean

    @Override
        public void afterPropertiesSet() {
            // Do this first, it may add ResponseBody advice beans
            initControllerAdviceCache();
    
            if (this.argumentResolvers == null) {
                List<HandlerMethodArgumentResolver> resolvers = getDefaultArgumentResolvers();
                this.argumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);
            }
            if (this.initBinderArgumentResolvers == null) {
                List<HandlerMethodArgumentResolver> resolvers = getDefaultInitBinderArgumentResolvers();
                this.initBinderArgumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);
            }
            if (this.returnValueHandlers == null) {
                List<HandlerMethodReturnValueHandler> handlers = getDefaultReturnValueHandlers();
                this.returnValueHandlers = new HandlerMethodReturnValueHandlerComposite().addHandlers(handlers);
            }
        }

      这个getDefaultReturnValueHandlers很长,这里就不贴了,只需要记住一条  RequestResponseBodyMethodProcessor 也是 这个handlers中的一员。而它恰恰是处理@ResponseBody的关键

      

      现在回头继续看  his.returnValueHandlers.handleReturnValue 

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

      这里就是选择合适的handler,因为我们的返回值是有@ResponseBody注解的,所以就选出来了 RequestResponseBodyMethodProcessor

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

      最终会调用  AbstractMessageConverterMethodProcessor.writeWithMessageConverters 

      这个方法很长,这里只看一部分。如果返回值是一个引用类型而不是基础类型,那就要有jackson相关的类,在这里  messageConverter 就是  MappingJackson2HttpMessageConverter , 在这里调用write方法就对对象做了序列化,在通过HttpResponse的outputStream写出去

    if (selectedMediaType != null) {
                selectedMediaType = selectedMediaType.removeQualityValue();
                for (HttpMessageConverter<?> messageConverter : this.messageConverters) {
                    if (messageConverter instanceof GenericHttpMessageConverter) {
                        if (((GenericHttpMessageConverter) messageConverter).canWrite(
                                declaredType, valueType, selectedMediaType)) {
                            outputValue = (T) getAdvice().beforeBodyWrite(outputValue, returnType, selectedMediaType,
                                    (Class<? extends HttpMessageConverter<?>>) messageConverter.getClass(),
                                    inputMessage, outputMessage);
                            if (outputValue != null) {
                                addContentDispositionHeader(inputMessage, outputMessage);
                                ((GenericHttpMessageConverter) messageConverter).write(
                                        outputValue, declaredType, selectedMediaType, outputMessage);
                                if (logger.isDebugEnabled()) {
                                    logger.debug("Written [" + outputValue + "] as "" + selectedMediaType +
                                            "" using [" + messageConverter + "]");
                                }
                            }
                            return;
                        }
                    }
  • 相关阅读:
    java 整合redis缓存 SSM 后台框架 rest接口 shiro druid maven bootstrap html5
    《将博客搬至CSDN》
    前后端分离-定义响应格式化数据
    微服务-Springboot+Redis缓存管理接口代码实现
    java语法
    java后台树形结构展示---懒加载
    后端处理前端传过来的日期的两种方式
    汉字转拼音工具类
    Mybatis的小技巧
    调用高德API,通过输入的地址,如省份、市、区获取经纬度 ,通过输入的经纬度,获取区域详情
  • 原文地址:https://www.cnblogs.com/juniorMa/p/14110532.html
Copyright © 2011-2022 走看看