首页 > 编程语言 > 详细

SpringMVC-@RequestBody注解的传参及原理解析

时间:2020-12-26 18:15:09      阅读:342      评论:0      收藏:0      [点我收藏+]

引子

在涉及前后端交互的 Java 应用中,SpringMVC 可以说是很流行的一种框架。那么在 SpringMVC 中,如何将较复杂的嵌套对象从前端传给后端呢?可以使用注解 @RequestBody 。

传参

后端接收:

@RequestMapping(value = "/save")
@ResponseBody
public BaseResult save(@RequestBody BookInfo bookInfo) {
    Assert.notNull(bookInfo, "商品对象不能为空");
    Assert.notNull(bookInfo.getGoods().getGoodsId(), "商品ID不能为空");

    complete(bookInfo);
    boolean isSaved = goodsSnapshotService.save(bookInfo);
    BookSaveResponse bookSaveResponse = new BookSaveResponse(bookInfo.getOrder().getOrderNo(), bookInfo.getGoods().getGoodsId());
    return isSaved ? BaseResult.succ(bookSaveResponse): BaseResult.failed(Errors.BookError);
}

public class BookInfo {

    /** 下单的商品信息 */
    private GoodsInfo goods;

    /** 下单的订单信息 */
    private Order order;
}

public class GoodsInfo {

    /** 商品ID */
    private Long goodsId;

    /** 店铺ID */
    private Long shopId;

    /** 商品标题 */
    private String title;

    /** 商品描述 */
    private String desc;

    /** 商品服务 keys */
    private String serviceKeys;

    /** 商品规格选择 */
    private String choice;

    /** 商品关联的订单号 */
    private String orderNo;
}

public class Order {

    /** 订单号 */
    private String orderNo;

    /** 店铺ID */
    private Long shopId;

    /** 下单时间 */
    private Long bookTime;

    /** 下单人ID */
    private Long userId;

    /** 是否货到付款 */
    private Boolean isCodPay;

    /** 是否担保交易 */
    private Boolean isSecuredOrder;

    /** 是否有线下门店 */
    private Boolean hasRetailShop;

    /** 配送方式 0 快递 1 自提 2 同城送 */
    private DeliveryType deliveryType;

    /** 订单金额, 分为单位 */
    private Long price;

    /** 快递配送金额 */
    private Long expressFee;

    /** 同城配送起送金额 */
    private Long localDeliveryBasePrice;

    /** 同城配送金额 */
    private Long localDeliveryPrice;

    /** 订单的服务 keys */
    private List<String> keys;
}

前端传参:


    var bookInfo = {
       ‘goods‘: {
            ‘shopId‘: shopId,
            ‘goodsId‘: goodsId,
            ‘price‘: priceNum,
            ‘title‘: title,
            ‘desc‘: desc,
            ‘serviceKeys‘ : serviceKeys,
            ‘choice‘: choice
        },
        ‘order‘: {
            ‘shopId‘: shopId,
            ‘userId‘: userId,
            ‘deliveryType‘: deliveryType,
            ‘price‘: priceNum,
            ‘isCodPay‘ : isCodPay,
        }
    };

    var jqXHR = jQuery.ajax({
        dataType: "json",
        contentType: "application/json; charset=utf-8",
        url: ‘http://localhost:8080/api/goodsnapshot/save‘,
        data: JSON.stringify(bookInfo),
        timeout: 90000,
        type: ‘POST‘
    });


原理

@RequestBody 的作用就是做参数校验和参数转换,将前端的 JSON 字符串转换成后端给定的 Java 对象。那么,它的参数转换功能是如何实现的呢? 要回答这个问题,就要找到这个注解的实现类。

找到入口方法

可以在 IDEA 搜索类 RequestBody,得到与之相关的几个类如下图所示:

技术分享图片

在可能实现类的 public 方法入口打上断点,然后单步调试。可以找到入口方法是 RequestResponseBodyMethodProcessor.resolveArgument 。这个方法进一步调用了方法 AbstractMessageConverterMethodArgumentResolver.readWithMessageConverters , 该方法会拿到若干个 HttpMessageConverter 的实现类(如下图所示),判断其中是否有可以处理请求对象 InputMessage 的类。只要任一能处理即可。

技术分享图片

技术分享图片

找到参数转换类

如何判断 HttpMessageConverter 是否能够处理 InputMessage 呢?要满足两个基本条件:

  • 能够处理请求参数类,比如,这里是 BookInfo ;
  • 要能处理给定的 Content-type ,比如,这里是 JSON , UTF-8。

见:AbstractHttpMessageConverter.java


    public boolean canRead(Class<?> clazz, MediaType mediaType) {
        return this.supports(clazz) && this.canRead(mediaType);
    }

    protected boolean canRead(MediaType mediaType) {
        if (mediaType == null) {
            return true;
        } else {
            Iterator var2 = this.getSupportedMediaTypes().iterator();

            MediaType supportedMediaType;
            do {
                if (!var2.hasNext()) {
                    return false;
                }

                supportedMediaType = (MediaType)var2.next();
            } while(!supportedMediaType.includes(mediaType));

            return true;
        }
    }

可以逐一查看上述截图中的 HttpMessageConverter 实现类(支持的 Media Type 如截图所示):

  • ByteArrayHttpMessageConverter: 支持字节数组的处理;
  • StringHttpMessageConverter: 支持字符串的处理;
  • ResourceHttpMessageConverter: 支持 Resource 类型的处理;
  • SourceHttpMessageConverter: 支持 DOMSource, SAXSource, StAXSource, StreamSource, Source 类型;
  • AllEncompassingFormHttpMessageConverter: 支持表单的处理;
  • MappingJackson2HttpMessageConverter: 支持 JSON 字符串的处理;
  • Jaxb2RootElementHttpMessageConverter:参数类带有 XmlRootElement 或 XmlType 。

最终发现,能够处理 JSON 字符串及 JSON media 的是类 MappingJackson2HttpMessageConverter 。 注意到,在这个类处理 InputMessage 的时候,还有一个切面。这个切面的实现类是 RequestResponseBodyAdviceChain ,里面应用了责任链设计模式来处理请求 InputMessage。对应于 @RequestBody 的切面类是 JsonViewRequestBodyAdvice, 仅在参数上有 @JsonView 注解时才处理。


AbstractMessageConverterMethodArgumentResolver.java

        Object inputMessage;
        try {
            inputMessage = new AbstractMessageConverterMethodArgumentResolver.EmptyBodyCheckingHttpInputMessage(inputMessage);
            Iterator var10 = this.messageConverters.iterator();

            while(var10.hasNext()) {
                HttpMessageConverter<?> converter = (HttpMessageConverter)var10.next();
                Class<HttpMessageConverter<?>> converterType = converter.getClass();
                if (converter instanceof GenericHttpMessageConverter) {
                    GenericHttpMessageConverter<?> genericConverter = (GenericHttpMessageConverter)converter;
                    if (genericConverter.canRead(targetType, contextClass, contentType)) {
                        if (this.logger.isDebugEnabled()) {
                            this.logger.debug("Read [" + targetType + "] as \"" + contentType + "\" with [" + converter + "]");
                        }

                        if (((HttpInputMessage)inputMessage).getBody() != null) {
                            inputMessage = this.getAdvice().beforeBodyRead((HttpInputMessage)inputMessage, parameter, targetType, converterType);
                            body = genericConverter.read(targetType, contextClass, (HttpInputMessage)inputMessage);
                            body = this.getAdvice().afterBodyRead(body, (HttpInputMessage)inputMessage, parameter, targetType, converterType);
                        } else {
                            body = this.getAdvice().handleEmptyBody((Object)null, (HttpInputMessage)inputMessage, parameter, targetType, converterType);
                        }
                        break;
                    }
                } else if (targetClass != null && converter.canRead(targetClass, contentType)) {
                    if (this.logger.isDebugEnabled()) {
                        this.logger.debug("Read [" + targetType + "] as \"" + contentType + "\" with [" + converter + "]");
                    }

                    if (((HttpInputMessage)inputMessage).getBody() != null) {
                        inputMessage = this.getAdvice().beforeBodyRead((HttpInputMessage)inputMessage, parameter, targetType, converterType);
                        body = converter.read(targetClass, (HttpInputMessage)inputMessage);
                        body = this.getAdvice().afterBodyRead(body, (HttpInputMessage)inputMessage, parameter, targetType, converterType);
                    } else {
                        body = this.getAdvice().handleEmptyBody((Object)null, (HttpInputMessage)inputMessage, parameter, targetType, converterType);
                    }
                    break;
                }
            }

SpringMVC-@RequestBody注解的传参及原理解析

原文:https://www.cnblogs.com/lovesqcc/p/14192871.html

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!