Stories

Detail Return Return

這問題巧了,SpringMVC 不同參數處理機制引發的思考 | 京東雲技術團隊 - Stories Detail

這個問題非常有趣,不是SpringMVC 的問題,是實際開發中混合使用了兩種請求方式暴露出來的。

問題場景

功能模塊中,提供兩個 Http 服務。一個是列表查詢(application/json 請求),一個是列表導出(表單請求)。運行環境發現個問題:MVC model 新添加的屬性,類似的 Http 請求,一個有值,一個沒有

代碼如下:

/**
 * application/json 請求。 這種情況 param.field2 有值 ✔
 * @param param RequestResponseBodyMethodProcessr 處理 HttpServletRequest 參數
 */
@PostMapping(value = "query")
public ResponseResult<Page<SomeData>> queryByCondition(@RequestBody SomeParam param){
    // 業務邏輯...
}

/**
 * application/x-www-form-urlencoded 請求 這種情況 param.field2 沒有有賦值 ❌
 * @param param ServletModelAttributeMethodProcessor 處理 HttpServletRequest 參數
 */
@PostMapping(value = "export")
public void exportExcel(SomeParam param) {
    // 業務邏輯...
}


public class SomeParam {

    // 這個是原有的,有 get set 方法
    private String field1;

    // 這個是新增的,沒有get set 方法 (這是一個巧合、意外)。 問題就出在這裏。
    private String field2;

}

❓ 根據代碼分析,那應該是 SpringMVC 針對這兩種參數處理的機制不同。
針對上述的參數處理,可以參考:
RequestResponseBodyMethodProcessor、 ServletModelAttributeMethodProcessor

Insight RequestResponseBodyMethodProcessor

處理 Http Body 的數據。解析註解 RequestBody 的參數。

針對 MimeType 為 application/json 的請求,按照json 格式進行反序列化。

默認參數處理器
MappingJackson2HttpMessageConverter

string 反序列化為對象,使用的是
com.fasterxml.jackson.databind.ObjectMapper。

上述工程中,對 ObjectMapper 開啓 private 屬性檢測。新增的屬性可以正常反序列化。

ObjectMapper mapper = new ObjectMapper();
// 這又是一個巧合、意外  咋還有這個用法 
mapper.setVisibility(PropertyAccessor.FIELD, Visibility.ANY);

Visibility 具體的用法示例參考: Jackson - Decide What Fields Get (De)Serialized | Baeldung

原理: 如果沒有 setter 方法,jackson 會操作 field 來完成賦值。

/**
 * This concrete sub-class implements property that is set directly assigning to a Field.
 */
public final static class FieldProperty extends SettableBeanProperty {

    @Override
    public final void set(Object instance, Object value) throws IOException {
        try {
            _field.set(instance, value);
        } catch (Exception e) {
            _throwAsIOE(e, value);
        }
    }
}

Insight ServletModelAttributeMethodProcessor

自定義 Class 參數解析

通過解析 request parameters, 用來構造和初始化對應的方法入參。

主要通過
ServletRequestDataBinder.bind(request) 來完成。

/**
 * Apply given property values to the target object.
 * By default, unknown fields will be ignored.
 * 
 * @see org.springframework.validation.DataBinder#applyPropertyValues
 */
protected void applyPropertyValues(MutablePropertyValues mpvs) {
    try {
        // Bind request parameters onto target object.
        // 默認使用 BeanWrapperImpl.setPropertyValue()
        getPropertyAccessor().setPropertyValues(mpvs, isIgnoreUnknownFields(), isIgnoreInvalidFields());
    }
    catch (PropertyBatchUpdateException ex) {
        // Use bind error processor to create FieldErrors.
    }
}
public void setPropertyValues(PropertyValues pvs, boolean ignoreUnknown, boolean ignoreInvalid) throws BeansException {
    // 通過遍歷 request parameters 來嘗試對 target 進行賦值
    List<PropertyValue> propertyValues = (pvs instanceof MutablePropertyValues ?             ((MutablePropertyValues) pvs).getPropertyValueList() : Arrays.asList(pvs.getPropertyValues()));
    for (PropertyValue pv : propertyValues) {
        try {
            // etPropertyValue 使用 JDK 的 Introspector 來進行序列化操作。
            // 沒有setter 方法,自然沒法賦值。
            setPropertyValue(pv);
        }
    }
}

總結

  • 一件事情出錯,不是一處問題造成的。
  • 工程開發要規範,用最常規、最穩定的辦法來實現。遇到稀奇古怪的問題就是冷門用法帶來的。
  • 對於常用的框架和工具庫熟悉其底層原理,遇到問題可以很快定位。

作者:京東物流 楊攀

來源:京東雲開發者社區

user avatar dawanzi_6278b06ec111c Avatar Rocokingdom2024 Avatar u_15214399 Avatar yizhidanshendetielian Avatar wuliaodechaye Avatar yunpan-plus Avatar datadowell Avatar guangmingleiluodebaomihua Avatar songboy Avatar wqjiao Avatar project_management_solutions Avatar shiwangdehuangdou_bpfcez Avatar
Favorites 15 users favorite the story!
Favorites

Add a new Comments

Some HTML is okay.