最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

SpringMVC @RequestBody 為null問題的排查及解決

 更新時間:2021年10月22日 09:41:08   作者:瘋狂哈丘  
這篇文章主要介紹了SpringMVC @RequestBody 為null問題的排查及解決,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教

SpringMVC @RequestBody為null

今天寫一個springmvc接口,希望入?yún)閖son,然后自動轉成自己定義的封裝對象,于是有了下面的代碼

@PostMapping("/update")
@ApiOperation("更新用戶信息")
public CumResponseBody update(@RequestBody UserInfoParam param) {
     int userId = getUserId();
     userService.updateUserInfo(userId, param);
     return ResponseFactory.createSuccessResponse("ok");
}
//UserInfoParam.java
public class UserInfoParam {
    private String tel;
    private String email;
    public String getTel() {
        return tel;
    }
    public void setTel(String tel) {
        this.tel = tel;
    }
    public String getEmail() {
        return email;
    }
    public void setEmail(String email) {
        this.email = email;
    }
}

程序正常啟動后,使用swaggerUI發(fā)起測試

curl -X POST --header 'Content-Type: application/json' --header 'Accept: application/json' -d '{ \ 
   "email": "12%40mail.com", \ 
   "tel": "13677682911" \ 
 }' 'http://127.0.0.1:9998/api/user/update'

最后程序報錯

org.springframework.http.converter.HttpMessageNotReadableException: Required request body is missing: public com.pingguiyuan.shop.common.response.CumResponseBody com.pingguiyuan.shop.weixinapi.controller.UserController.update(com.pingguiyuan.shop.common.param.weixin.UserInfoParam)

對了幾遍,接口的編寫和請求內容都確定沒有問題,但是請求的json就是沒注入進來轉成param對象。查了一圈資料也沒找到滿意的答案,就只能給springMVC的源碼打斷點跟一遍,看一下具體是哪里出了問題。

由于本篇不是介紹springMVC實現(xiàn)原理的,就不具體介紹springMVC的源碼。

最后斷點發(fā)現(xiàn)springMVC從request的inputstream沒取出內容來(inputstream.read()出來的直接是-1)。由于有在一個攔截器輸出請求的參數(shù)內容—>【當請求時get時,通過request.getParameterMap();獲取參數(shù),當請求時post時,則是直接輸出request的inpustream里面的內容】。所以請求的body里面是肯定有內容的,也就是說request.getInputstream()的流是有內容的,那為什么到springMVC這read出來的就是-1呢。

稍微理了下思路,發(fā)現(xiàn)是自己給自己挖了個坑。答案是:request的inputstream只能讀一次,博主在攔截器中把inputstream的內容都輸出來了,到springMVC這,就沒有內容可以讀了。

關于inputsteam的一些理解

servlet request的inpustream是面向流的,這意味著讀取該inputstream時是一個字節(jié)一個字節(jié)讀的,直到整個流的字節(jié)全部讀回來,這期間沒有對這些數(shù)據(jù)做任何緩存。因此,整個流一旦被讀完,是無法再繼續(xù)讀的。

這和nio的處理方式就完全不同,如果是nio的話,數(shù)據(jù)是先被讀取到一塊緩存中,然后程序去讀取這塊緩存的內容,這時候就允許程序重復讀取緩存的內容,比如mark()然后reset()或者直接clear()重新讀。

特意去看了下InputStream的源碼,發(fā)現(xiàn)其實是有mark()和reset()方法的,但是默認的實現(xiàn)表示這是不能用的,源碼如下

public boolean markSupported() {
    return false;
}
public synchronized void reset() throws IOException {
        throw new IOException("mark/reset not supported");
}
public synchronized void mark(int readlimit) {}

其中mark是一個空函數(shù),reset函數(shù)直接拋出異常。同時,inputstream還提供了markSupported()方法,默認是返回false,表示不支持mark,也就是標記(用于重新讀)。

但是并不是所有的Inputstream實現(xiàn)都不允許重復讀,比如BufferedInputStream就是允許重復讀的,從類名來看,就知道這個類其實就是將讀出來的數(shù)據(jù)進行緩存,來達到可以重復讀的效果。下面是BufferedInputStream重寫的3個方法

    public synchronized void mark(int readlimit) {
        marklimit = readlimit;
        markpos = pos;
    }
    public synchronized void reset() throws IOException {
        getBufIfOpen(); // Cause exception if closed
        if (markpos < 0)
            throw new IOException("Resetting to invalid mark");
        pos = markpos;
    }
    public boolean markSupported() {
        return true;
    }

可以看到BufferedInputStream的markSupported()方法返回的是true,說明它應該是支持重復讀的。我們可以通過mark()和reset()來實現(xiàn)重復讀的效果。

@RequestBody 自動映射原理的簡單介紹

springMVC在處理請求時,先找到對應controller處理該請求的方法,然后遍歷整個方法的所有參數(shù),進行封裝。在處理參數(shù)的過程中,會調用AbstractMessageConverterMethodArgumentResolver#readWithMessageConverters()類的方法進行進行一些轉換操作,源碼如下

protected <T> Object readWithMessageConverters(HttpInputMessage inputMessage, MethodParameter parameter,
            Type targetType) throws IOException, HttpMediaTypeNotSupportedException, HttpMessageNotReadableException {
        MediaType contentType;
        boolean noContentType = false;
        try {
            contentType = inputMessage.getHeaders().getContentType();
        }
        catch (InvalidMediaTypeException ex) {
            throw new HttpMediaTypeNotSupportedException(ex.getMessage());
        }
        if (contentType == null) {
            noContentType = true;
            contentType = MediaType.APPLICATION_OCTET_STREAM;
        }
        Class<?> contextClass = parameter.getContainingClass();
        Class<T> targetClass = (targetType instanceof Class ? (Class<T>) targetType : null);
        if (targetClass == null) {
            ResolvableType resolvableType = ResolvableType.forMethodParameter(parameter);
            targetClass = (Class<T>) resolvableType.resolve();
        }
        HttpMethod httpMethod = (inputMessage instanceof HttpRequest ? ((HttpRequest) inputMessage).getMethod() : null);
        Object body = NO_VALUE;
        EmptyBodyCheckingHttpInputMessage message;
        try {
            message = new EmptyBodyCheckingHttpInputMessage(inputMessage);
            for (HttpMessageConverter<?> converter : this.messageConverters) {
                Class<HttpMessageConverter<?>> converterType = (Class<HttpMessageConverter<?>>) converter.getClass();
                GenericHttpMessageConverter<?> genericConverter =
                        (converter instanceof GenericHttpMessageConverter ? (GenericHttpMessageConverter<?>) converter : null);
                if (genericConverter != null ? genericConverter.canRead(targetType, contextClass, contentType) :
                        (targetClass != null && converter.canRead(targetClass, contentType))) {
                    if (logger.isDebugEnabled()) {
                        logger.debug("Read [" + targetType + "] as \"" + contentType + "\" with [" + converter + "]");
                    }
                    if (message.hasBody()) {
                        HttpInputMessage msgToUse =
                                getAdvice().beforeBodyRead(message, parameter, targetType, converterType);
                        body = (genericConverter != null ? genericConverter.read(targetType, contextClass, msgToUse) :
                                ((HttpMessageConverter<T>) converter).read(targetClass, msgToUse));
                        body = getAdvice().afterBodyRead(body, msgToUse, parameter, targetType, converterType);
                    }
                    else {
                        body = getAdvice().handleEmptyBody(null, message, parameter, targetType, converterType);
                    }
                    break;
                }
            }
        }
        catch (IOException ex) {
            throw new HttpMessageNotReadableException("I/O error while reading input message", ex);
        }
        if (body == NO_VALUE) {
            if (httpMethod == null || !SUPPORTED_METHODS.contains(httpMethod) ||
                    (noContentType && !message.hasBody())) {
                return null;
            }
            throw new HttpMediaTypeNotSupportedException(contentType, this.allSupportedMediaTypes);
        }
        return body;
    }

上面這段代碼主要做的事情大概就是獲取請求的contentType,然后遍歷配置的HttpMessageConverter—>this.messageConverters,如果該HttpMessageConverter可以用于解析這種contentType(genericConverter.canRead方法),就用這種HttpMessageConverter解析請求的請求體內容,最后返回具體的對象。

在spring5.0.7版本中,messageConverters默認似乎配置了8種convert。分別是

  • ByteArrayMessageConverter
  • StringHttpMessageConverter
  • ResourceHttpMessageConverter
  • ResourceRegionHttpMessageConverter
  • SourceHttpMessageConverter
  • AllEncompassingFormHttpMessageConverter
  • MappingJackson2HttpMessageConverter
  • Jaxb2RootElementHttpMessageConverter

具體的convert是哪些contentType并怎么解析的,這里不多做介紹,感興趣的朋友可以自行查看源碼。

比如我們請求的header中的contentType是application/json,那么在遍歷messageConverters的時候,其他genericConverter.canRead()都會返回false,說明沒有適配上。

然后遍歷到MappingJackson2HttpMessageConverter時genericConverter.canRead()返回true,接著就去獲取請求的請求體,并通過json解析成我們@RequestBody定義的對象。

因此,如果我們的請求的contentType和數(shù)據(jù)協(xié)議都是自定義的,我們完全可以自己實現(xiàn)一個HttpMessageConverter,然后解析特定的contentType。

最后記得將這個實現(xiàn)放入messageConverters中,這樣springMVC就會自動幫我們把請求內容解析成對象了。

關于@requestBody的一些說明

1、@requestBody注解

常用來處理content-type不是默認的application/x-www-form-urlcoded編碼的內容,比如說:application/json或者是application/xml等。一般情況下來說常用其來處理application/json類型。

2、通過@requestBody

可以將請求體中的JSON字符串綁定到相應的bean上,當然也可以將其分別綁定到對應的字符串上。

例如說以下情況:

$.ajax({       
         url:"/login",       
         type:"POST",        
          data:'{"userName":"admin","pwd","admin123"}',      
        content-type:"application/json charset=utf-8",        
          success:function(data)
             {          
                alert("request success ! ");       
            }    
});
@requestMapping("/login")    
  public void login(@requestBody String userName,@requestBody String pwd){      
  System.out.println(userName+" :"+pwd);    
}

這種情況是將JSON字符串中的兩個變量的值分別賦予了兩個字符串,但是呢假如我有一個User類,擁有如下字段:

String userName;
String pwd;

那么上述參數(shù)可以改為以下形式:@requestBody User user 這種形式會將JSON字符串中的值賦予user中對應的屬性上

需要注意的是,JSON字符串中的key必須對應user中的屬性名,否則是請求不過去的。

3、在一些特殊情況

@requestBody也可以用來處理content-type類型為application/x-www-form-urlcoded的內容,只不過這種方式不是很常用,在處理這類請求的時候,@requestBody會將處理結果放到一個MultiValueMap<String,String>中,這種情況一般在特殊情況下才會使用,例如jQuery easyUI的datagrid請求數(shù)據(jù)的時候需要使用到這種方式、小型項目只創(chuàng)建一個POJO類的話也可以使用這種接受方式。

以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。

相關文章

  • Java中的WeakHashMap簡析

    Java中的WeakHashMap簡析

    這篇文章主要介紹了Java中的WeakHashMap簡析,Map 的子類常見的有 HashMap、Hashtable、ConcurrentHashMap、LinkedHashMap 等,WeakHashMap,直譯就是,虛弱的 HashMap,從名字可得知其和 HashMap 有關,需要的朋友可以參考下
    2023-09-09
  • SpringCloud-Hystrix組件使用方法

    SpringCloud-Hystrix組件使用方法

    這篇文章主要介紹了SpringCloud-Hystrix組件使用方法,本文給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2020-12-12
  • Spring Boot 中的自動配置autoconfigure詳解

    Spring Boot 中的自動配置autoconfigure詳解

    這篇文章主要介紹了Spring Boot 中的自動配置autoconfigure詳解,本文通過實例代碼給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友參考下吧
    2024-01-01
  • 如何從官網(wǎng)下載Hibernate jar包的方法示例

    如何從官網(wǎng)下載Hibernate jar包的方法示例

    這篇文章主要介紹了如何從官網(wǎng)下載Hibernate jar包的方法示例,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2019-04-04
  • JavaEE中用response向客戶端輸出中文數(shù)據(jù)亂碼問題分析

    JavaEE中用response向客戶端輸出中文數(shù)據(jù)亂碼問題分析

    這篇文章主要介紹了JavaEE中用response向客戶端輸出中文數(shù)據(jù)亂碼問題分析,需要的朋友可以參考下
    2014-10-10
  • Java 后端開發(fā)中Tomcat服務器運行不了的五種解決方案

    Java 后端開發(fā)中Tomcat服務器運行不了的五種解決方案

    tomcat是在使用Java編程語言開發(fā)服務端技術使用最廣泛的服務器之一,但經(jīng)常在開發(fā)項目的時候會出現(xiàn)運行不了的情況,這里總結出幾種能解決的辦法
    2021-10-10
  • java 獲取服務器真實IP的實例

    java 獲取服務器真實IP的實例

    這篇文章主要介紹了java 獲取服務器真實IP的實例的相關資料,這里提供實現(xiàn)方法幫助大家學習理解這部分內容,需要的朋友可以參考下
    2017-08-08
  • SpringBoot中的統(tǒng)一異常處理詳細解析

    SpringBoot中的統(tǒng)一異常處理詳細解析

    這篇文章主要介紹了SpringBoot中的統(tǒng)一異常處理詳細解析,該注解可以把異常處理器應用到所有控制器,而不是單個控制器,借助該注解,我們可以實現(xiàn):在獨立的某個地方,比如單獨一個類,定義一套對各種異常的處理機制,需要的朋友可以參考下
    2024-01-01
  • Java超詳細教你寫一個學籍管理系統(tǒng)案例

    Java超詳細教你寫一個學籍管理系統(tǒng)案例

    這篇文章主要介紹了怎么用Java來寫一個學籍管理系統(tǒng),學籍管理主要涉及到學生信息的增刪查改,本篇將詳細的實現(xiàn),感興趣的朋友跟隨文章往下看看吧
    2022-03-03
  • Java Scanner輸入兩個數(shù)組的方法

    Java Scanner輸入兩個數(shù)組的方法

    今天小編就為大家分享一篇Java Scanner輸入兩個數(shù)組的方法,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2018-07-07

最新評論

济南市| 敦煌市| 定西市| 工布江达县| 焉耆| 明溪县| 湖北省| 榕江县| 西昌市| 修水县| 肥城市| 丰台区| 定南县| 泰州市| 贵定县| 同江市| 定远县| 从化市| 敦煌市| 廊坊市| 青浦区| 通江县| 达尔| 泰来县| 河池市| 榆林市| 清水河县| 泉州市| 伊金霍洛旗| 梅河口市| 甘孜| 抚宁县| 临海市| 札达县| 揭东县| 内江市| 麻栗坡县| 木里| 若尔盖县| 东乡族自治县| 即墨市|