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

Java SpringBoot在RequestBody中高效的使用枚舉參數(shù)原理案例詳解

 更新時(shí)間:2021年09月03日 11:00:31   作者:看山  
這篇文章主要介紹了Java SpringBoot在RequestBody中高效的使用枚舉參數(shù)原理案例詳解,本篇文章通過簡(jiǎn)要的案例,講解了該項(xiàng)技術(shù)的了解與使用,以下就是詳細(xì)內(nèi)容,需要的朋友可以參考下

優(yōu)雅的使用枚舉參數(shù)(原理篇)中我們聊過,Spring對(duì)于不同的參數(shù)形式,會(huì)采用不同的處理類處理參數(shù),這種形式,有些類似于策略模式。將針對(duì)不同參數(shù)形式的處理邏輯,拆分到不同處理類中,減少耦合和各種if-else邏輯。本文就來扒一扒,RequestBody參數(shù)中使用枚舉參數(shù)的原理。

找入口

對(duì) Spring 有一定基礎(chǔ)的同學(xué)一定知道,請(qǐng)求入口是DispatcherServlet,所有的請(qǐng)求最終都會(huì)落到doDispatch方法中的ha.handle(processedRequest, response, mappedHandler.getHandler())邏輯。我們從這里出發(fā),一層一層向里扒。

跟著代碼深入,我們會(huì)找到org.springframework.web.method.support.InvocableHandlerMethod#invokeForRequest的邏輯:

public Object invokeForRequest(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
        Object... providedArgs) throws Exception {

    Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
    if (logger.isTraceEnabled()) {
        logger.trace("Arguments: " + Arrays.toString(args));
    }
    return doInvoke(args);
}

可以看出,這里面通過getMethodArgumentValues方法處理參數(shù),然后調(diào)用doInvoke方法獲取返回值。getMethodArgumentValues方法內(nèi)部又是通過HandlerMethodArgumentResolverComposite實(shí)例處理參數(shù)。這個(gè)類內(nèi)部是一個(gè)HandlerMethodArgumentResolver實(shí)例列表,列表中是Spring處理參數(shù)邏輯的集合,跟隨代碼Debug,可以看到有27個(gè)元素。這些類也是可以定制擴(kuò)展,實(shí)現(xiàn)自己的參數(shù)解析邏輯,這部分內(nèi)容后續(xù)再做介紹。

選擇Resolver

這個(gè)Resolver列表中,包含我們常用的幾個(gè)處理類。Get請(qǐng)求的普通參數(shù)是通過RequestParamMethodArgumentResolver處理參數(shù),包裝類通過ModelAttributeMethodProcessor處理參數(shù),RequestBody形式的參數(shù),則是通過RequestResponseBodyMethodProcessor處理參數(shù)。這段就是Spring中策略模式的使用,通過實(shí)現(xiàn)org.springframework.web.method.support.HandlerMethodArgumentResolver#supportsParameter方法,判斷輸入?yún)?shù)是否可以解析。下面貼上RequestResponseBodyMethodProcessor的實(shí)現(xiàn):

public boolean supportsParameter(MethodParameter parameter) {
    return parameter.hasParameterAnnotation(RequestBody.class);
}

可以看到,RequestResponseBodyMethodProcessor是通過判斷參數(shù)是否帶有RequestBody注解來判斷,當(dāng)前參數(shù)是否可以解析。

解析參數(shù)

RequestResponseBodyMethodProcessor繼承自AbstractMessageConverterMethodArgumentResolver,真正解析RequestBody參數(shù)的邏輯在org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodArgumentResolver#readWithMessageConverters方法中。我們看下源碼(因?yàn)樵创a比較長(zhǎng),文中僅留下核心邏輯。):

protected <T> Object readWithMessageConverters(HttpInputMessage inputMessage, MethodParameter parameter,
        Type targetType) throws IOException, HttpMediaTypeNotSupportedException, HttpMessageNotReadableException {
    MediaType contentType = inputMessage.getHeaders().getContentType();// 1
    Class<?> contextClass = parameter.getContainingClass();// 2
    Class<T> targetClass = (targetType instanceof Class ? (Class<T>) targetType : null);// 3

    Object body = NO_VALUE;

    EmptyBodyCheckingHttpInputMessage message = new EmptyBodyCheckingHttpInputMessage(inputMessage);// 4
    for (HttpMessageConverter<?> converter : this.messageConverters) {// 5
        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 (message.hasBody()) {
                HttpInputMessage msgToUse =
                        getAdvice().beforeBodyRead(message, parameter, targetType, converterType);
                body = (genericConverter != null ? genericConverter.read(targetType, contextClass, msgToUse) :
                        ((HttpMessageConverter<T>) converter).read(targetClass, msgToUse));// 6
                body = getAdvice().afterBodyRead(body, msgToUse, parameter, targetType, converterType);
            }
            else {
                body = getAdvice().handleEmptyBody(null, message, parameter, targetType, converterType);
            }
            break;
        }
    }
    return body;
}

跟著代碼說明一下各部分用途:

  1. 獲取請(qǐng)求content-type
  2. 獲取參數(shù)容器類
  3. 獲取目標(biāo)參數(shù)類型
  4. 將請(qǐng)求參數(shù)轉(zhuǎn)換為EmptyBodyCheckingHttpInputMessage類型
  5. 循環(huán)各種RequestBody參數(shù)解析器,這些解析器都是HttpMessageConverter接口的實(shí)現(xiàn)類。Spring對(duì)各種情況做了全量覆蓋,總有一款適合的。文末給出HttpMessageConverter各個(gè)擴(kuò)展類的類圖。
  6. for循環(huán)體中就是選擇一款適合的,進(jìn)行解析
    • 首先調(diào)用canRead方法判斷是否可用
    • 判斷請(qǐng)求請(qǐng)求參數(shù)是否為空,為空則通過AOP的advice處理一下空請(qǐng)求體,然后返回
    • 不為空,先通過AOP的advice做前置處理,然后調(diào)用read方法轉(zhuǎn)換對(duì)象,在通過advice做后置處理

Spring的AOP不在本文范圍內(nèi),所以一筆帶過。后續(xù)有專題說明。

本例中,HttpMessageConverter使用的是MappingJackson2HttpMessageConverter,該類繼承自AbstractJackson2HttpMessageConverter。看名稱就知道,這個(gè)類是使用Jackson處理請(qǐng)求參數(shù)。其中read方法之后,會(huì)調(diào)用內(nèi)部私有方法readJavaType,下面給出該方法的核心邏輯:

private Object readJavaType(JavaType javaType, HttpInputMessage inputMessage) throws IOException {
    MediaType contentType = inputMessage.getHeaders().getContentType();// 1
    Charset charset = getCharset(contentType);

    ObjectMapper objectMapper = selectObjectMapper(javaType.getRawClass(), contentType);// 2
    Assert.state(objectMapper != null, "No ObjectMapper for " + javaType);

    boolean isUnicode = ENCODINGS.containsKey(charset.name()) ||
            "UTF-16".equals(charset.name()) ||
            "UTF-32".equals(charset.name());// 3
    try {
        if (isUnicode) {
            return objectMapper.readValue(inputMessage.getBody(), javaType);// 4
        } else {
            Reader reader = new InputStreamReader(inputMessage.getBody(), charset);
            return objectMapper.readValue(reader, javaType);
        }
    }
    catch (InvalidDefinitionException ex) {
        throw new HttpMessageConversionException("Type definition error: " + ex.getType(), ex);
    }
    catch (JsonProcessingException ex) {
        throw new HttpMessageNotReadableException("JSON parse error: " + ex.getOriginalMessage(), ex, inputMessage);
    }
}

跟著代碼說明一下各部分用途:

  1. 獲取請(qǐng)求的content-type,這個(gè)是Spring實(shí)現(xiàn)的擴(kuò)展邏輯,根據(jù)不同的content-type可以選擇不同的ObjectMapper實(shí)例。也就是第2步的邏輯
  2. 根據(jù)content-type和目標(biāo)類型,選擇ObjectMapper實(shí)例。本例中直接返回的是默認(rèn)的,也就是通過Jackson2ObjectMapperBuilder.cbor().build()方法創(chuàng)建的。
  3. 檢查請(qǐng)求是否是unicode字符,目前來說,大家用的都是UTF-8的
  4. 通過ObjectMapper將請(qǐng)求json轉(zhuǎn)換為對(duì)象。其實(shí)這部分還有一段判斷inputMessage是否是MappingJacksonInputMessage實(shí)例的,考慮到大家使用的版本,這部分就不說了。

至此,Spring的邏輯全部結(jié)束,似乎還是沒有找到我們使用的JsonCreator注解或者JsonDeserialize的邏輯。不過也能想到,這兩個(gè)都是Jackson的類,那必然應(yīng)該是Jackson的邏輯。接下來,就扒一扒Jackson的轉(zhuǎn)換邏輯了。

深入Jackson的ObjectMapper邏輯

牽扯Jackson的邏輯主要分布在AbstractMessageConverterMethodArgumentResolver#readWithMessageConverters和ObjectMapper#readValue這兩個(gè)方法中。先說一下ObjectMapper#readValue方法的邏輯,這里面會(huì)調(diào)用GenderIdCodeEnum#create方法,完成類型轉(zhuǎn)換。

ObjectMapper#readValue方法直接調(diào)用了當(dāng)前類中的_readMapAndClose方法,這個(gè)方法里面比較關(guān)鍵的是ctxt.readRootValue(p, valueType, _findRootDeserializer(ctxt, valueType), null),這個(gè)方法就是將輸入json轉(zhuǎn)換為對(duì)象。咱們?cè)倮^續(xù)深入,可以找到Jackson內(nèi)部是通過BeanDeserializer這個(gè)類轉(zhuǎn)換對(duì)象的,比較重要的是deserializeFromObject方法,源碼如下(刪除一下不太重要的代碼):

public Object deserializeFromObject(JsonParser p, DeserializationContext ctxt) throws IOException
{
    // 這里根據(jù)上下文中目標(biāo)類型,創(chuàng)建實(shí)例對(duì)象,其中 _valueInstantiator 是 StdValueInstantiator 實(shí)例。
    final Object bean = _valueInstantiator.createUsingDefault(ctxt);
    // [databind#631]: Assign current value, to be accessible by custom deserializers
    p.setCurrentValue(bean);

    if (p.hasTokenId(JsonTokenId.ID_FIELD_NAME)) {
        String propName = p.currentName();
        do {
            p.nextToken();

            // 根據(jù)字段名找到 屬性對(duì)象,對(duì)于gender字段,類型是 MethodProperty。
            SettableBeanProperty prop = _beanProperties.find(propName);
            if (prop != null) { // normal case
                try {
                    // 開始進(jìn)行解碼操作,并將解碼結(jié)果寫入到對(duì)象中
                    prop.deserializeAndSet(p, ctxt, bean);
                } catch (Exception e) {
                    wrapAndThrow(e, bean, propName, ctxt);
                }
                continue;
            }
            handleUnknownVanilla(p, ctxt, bean, propName);
        } while ((propName = p.nextFieldName()) != null);
    }
    return bean;
}

咱們看一下MethodProperty#deserializeAndSet的邏輯(只保留關(guān)鍵代碼):

public void deserializeAndSet(JsonParser p, DeserializationContext ctxt,
        Object instance) throws IOException
{
    Object value;
    // 調(diào)用 FactoryBasedEnumDeserializer 實(shí)例的解碼方法
    value = _valueDeserializer.deserialize(p, ctxt);
    // 通過反射將值寫入對(duì)象中
    _setter.invoke(instance, value);
}

其中_valueDeserializer是FactoryBasedEnumDeserializer實(shí)例,快要接近目標(biāo)了,看下這段邏輯:

public Object deserialize(JsonParser p, DeserializationContext ctxt) throws IOException
{
    // 獲取json中的值
    Object value = _deser.deserialize(p, ctxt);
    // 調(diào)用 GenderIdCodeEnum#create 方法
    return _factory.callOnWith(_valueClass, value);
}

_factory是AnnotatedMethod實(shí)例,主要是對(duì)JsonCreator注解定義的方法的包裝,然后callOnWith中調(diào)用java.lang.reflect.Method#invoke反射方法,執(zhí)行GenderIdCodeEnum#create。

至此,我們終于串起來所有邏輯。

文末總結(jié)

本文通過一個(gè)示例串起來@JsonCreator注解起作用的邏輯,JsonDeserializer接口的邏輯與之類型,可以耐心debug一番。下面給出主要類的類圖:

SpringBoot參數(shù)解析類圖

HttpMessageConverter

ObjectMapper

推薦閱讀

到此這篇關(guān)于Java SpringBoot在RequestBody中高效的使用枚舉參數(shù)原理案例詳解的文章就介紹到這了,更多相關(guān)Java SpringBoot在RequestBody中高效的使用枚舉參數(shù)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • springcloud之FeignClient使用詳解

    springcloud之FeignClient使用詳解

    Feign是一種聲明式、模板化的HTTP客戶端,可以簡(jiǎn)化微服務(wù)之間的遠(yuǎn)程過程調(diào)用,通過Feign,開發(fā)者可以像調(diào)用本地方法一樣調(diào)用遠(yuǎn)程服務(wù),而無需感知這是一個(gè)HTTP請(qǐng)求,引入Feign后,微服務(wù)之間的調(diào)用流程更加簡(jiǎn)化,結(jié)合Ribbon實(shí)現(xiàn)了路由負(fù)載、超時(shí)熔斷等功能
    2024-12-12
  • java基于swing實(shí)現(xiàn)的連連看代碼

    java基于swing實(shí)現(xiàn)的連連看代碼

    這篇文章主要介紹了java基于swing實(shí)現(xiàn)的連連看代碼,包含了游戲中涉及的事件處理與邏輯功能,需要的朋友可以參考下
    2014-11-11
  • Java實(shí)現(xiàn)文件上傳與文件下載的示例代碼

    Java實(shí)現(xiàn)文件上傳與文件下載的示例代碼

    在開發(fā)中項(xiàng)目難免會(huì)遇到文件上傳和下載的情況,這篇文章主要為大家詳細(xì)介紹了Java中實(shí)現(xiàn)文件上傳與文件下載的示例代碼,希望對(duì)大家有所幫助
    2023-07-07
  • Java找不到或無法加載主類及編碼錯(cuò)誤問題的解決方案

    Java找不到或無法加載主類及編碼錯(cuò)誤問題的解決方案

    今天小編就為大家分享一篇關(guān)于Java找不到或無法加載主類及編碼錯(cuò)誤問題的解決方案,小編覺得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來看看吧
    2019-02-02
  • Java協(xié)程編程之Loom

    Java協(xié)程編程之Loom

    這篇文章主要介紹了Java協(xié)程編程Loom的方法,需要的朋友請(qǐng)看下文
    2021-08-08
  • yaml文件中${}語法取值方式

    yaml文件中${}語法取值方式

    在Spring Boot中,配置文件中的${test.aa}等占位符的值可以通過系統(tǒng)屬性或依賴的其他模塊來獲取,這意味著,可以通過JVM參數(shù)或者系統(tǒng)屬性來指定這些值,例如,通過在啟動(dòng)命令中添加-Dtest.aa=your_value或在代碼中通過
    2024-10-10
  • Java Math類、Random類、System類及BigDecimal類用法示例

    Java Math類、Random類、System類及BigDecimal類用法示例

    這篇文章主要介紹了Java Math類、Random類、System類及BigDecimal類用法,結(jié)合實(shí)例形式分析了java數(shù)值運(yùn)算相關(guān)的Math類、Random類、System類及BigDecimal類基本功能與使用技巧,需要的朋友可以參考下
    2019-03-03
  • Spring中的@ComponentScan注解使用詳解

    Spring中的@ComponentScan注解使用詳解

    這篇文章主要介紹了Spring中的@ComponentScan注解使用詳解,@ComponentScan 注解的作用就是根據(jù)指定的掃描路徑,把路徑中符合掃描規(guī)則的類裝配到 Spring 容器中,需要的朋友可以參考下
    2024-01-01
  • Java多線程實(shí)現(xiàn)Runnable方式

    Java多線程實(shí)現(xiàn)Runnable方式

    這篇文章主要為大家詳細(xì)介紹了Java多線程如何實(shí)現(xiàn)Runnable方式,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2018-03-03
  • 為什么SpringBoot的jar可以直接運(yùn)行

    為什么SpringBoot的jar可以直接運(yùn)行

    這篇文章主要介紹了為什么SpringBoot的jar可以直接運(yùn)行,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2020-05-05

最新評(píng)論

禹城市| 佛冈县| 曲周县| 武胜县| 闽清县| 木里| 昌图县| 瓮安县| 伊春市| 蕲春县| 宝兴县| 灵川县| 房产| 黄骅市| 鄱阳县| 宁海县| 浦江县| 宁蒗| 德令哈市| 长治市| 杭锦旗| 仙游县| 灌南县| 全椒县| 白山市| 永靖县| 呈贡县| 通化市| 德格县| 合肥市| 浪卡子县| 镇坪县| 桐柏县| 灵石县| 四川省| 闸北区| 什邡市| 保德县| 新龙县| 阿尔山市| 天全县|