躺着看云飘 發表於 2025-12-3 10:57:00

Spring Boot中HTTP请求参数转换和请求体JSON反序列化的区别

<h1 id="spring-boot中http请求参数转换和请求体json反序列化的区别">Spring Boot中HTTP请求参数转换和请求体JSON反序列化的区别</h1>
<h2 id="问题">问题</h2>
<p>假设如下方法和对象</p>
<pre><code class="language-java">@Operation(summary = "新增或修改标签信息")
@PostMapping("saveOrUpdate")
public Result saveOrUpdateLabel(@RequestBody LabelInfo labelInfo) {
    service.saveOrUpdate(labelInfo);
    return Result.ok();
}

@Schema(description = "标签信息表")
@TableName(value = "label_info")
@Data
public class LabelInfo extends BaseEntity {

    private static final long serialVersionUID = 1L;

    @Schema(description = "类型")
    @TableField(value = "type")
    private ItemType type;

    @Schema(description = "标签名称")
    @TableField(value = "name")
    private String name;
}

@Getter
@AllArgsConstructor
public enum ItemType implements BaseEnum {
    APARTMENT(1, "公寓"),
    ROOM(2, "房间");

    @EnumValue
    @JsonValue
    private Integer code;

    private String name;
}
</code></pre>
<p>在<code>saveOrUpdateLabel</code>方法中,如果不对<code>ItemType</code>对象的<code>code</code>变量添加<code>@JsonValue</code>注解,若前端发送<code>{ "type": "2","name": "两室一厅" }</code>会报错</p>
<pre><code class="language-java">Resolved ]
</code></pre>
<p>此时ItemType的反序列化流程是什么样的?以及为什么是通过Jackson的<code>@JsonValue</code>实现反序列化而不是<code>WebDataBinder</code>的<code>Converter</code>?</p>
<h2 id="1-两种不同的转换场景">1. 两种不同的转换场景</h2>
<p>首先,我们需要区分两种不同的转换场景:</p>
<ol>
<li><strong>HTTP请求参数转换</strong>:
<ul>
<li>场景:URL查询参数或表单数据,如<code>?type=2</code></li>
<li>处理器:Spring MVC的<code>WebDataBinder</code>和<code>Converter</code>接口</li>
<li>转换方向:String → Java对象</li>
</ul>
</li>
<li><strong>JSON反序列化</strong>:
<ul>
<li>场景:请求体中的JSON数据,如<code>{"type": "2", "name": "两室一厅"}</code></li>
<li>处理器:Jackson库的<code>ObjectMapper</code></li>
<li>转换方向:JSON字符串 → Java对象</li>
</ul>
</li>
</ol>
<h2 id="2-json反序列化流程详解">2. JSON反序列化流程详解</h2>
<p>当Spring Boot接收到包含JSON的请求体时,会发生以下过程:</p>
<ol>
<li>
<p><strong>HTTP请求到达</strong>:</p>
<pre><code class="language-http">POST /admin/label/saveOrUpdate
Content-Type: application/json

{
"type": "2",
"name": "两室一厅"
}
</code></pre>
</li>
<li>
<p><strong>Jackson处理JSON</strong>:</p>
<ul>
<li>Spring Boot使用Jackson的<code>ObjectMapper</code>来解析JSON</li>
<li>当遇到<code>"type": "2"</code>时,Jackson需要将其转换为<code>ItemType</code>枚举</li>
</ul>
</li>
<li>
<p><strong>默认枚举反序列化</strong>:</p>
<ul>
<li>Jackson默认使用枚举的名称进行反序列化</li>
<li>它会尝试匹配"2"与枚举常量名称:</li>
<li>由于没有名为"2"的枚举常量,所以抛出异常</li>
</ul>
</li>
</ol>
<h2 id="3-jsonvalue注解的作用">3. @JsonValue注解的作用</h2>
<p><code>@JsonValue</code>注解告诉Jackson在进行序列化和反序列化时,应该使用哪个字段作为枚举的值:</p>
<pre><code class="language-java">public enum ItemType implements BaseEnum {
    APARTMENT(1, "公寓"),
    ROOM(2, "房间");

    @EnumValue// MyBatis-Plus使用此注解确定存储到数据库的值
    @JsonValue// Jackson使用此注解确定序列化/反序列化的值
    private Integer code;
   
    // ...
}
</code></pre>
<p>当添加了<code>@JsonValue</code>注解后:</p>
<ol>
<li><strong>序列化</strong>:将<code>ItemType.ROOM</code>转换为JSON时,输出<code>2</code>而不是<code>"ROOM"</code></li>
<li><strong>反序列化</strong>:将JSON中的字符串<code>"2"</code>或者数字<code>2</code>转换为<code>ItemType.ROOM</code>对象</li>
</ol>
<h2 id="4-为什么converter不适用于json反序列化">4. 为什么Converter不适用于JSON反序列化</h2>
<p><code>Converter</code>接口(如<code>StringToItemTypeConverter</code>)是为Spring MVC的<code>WebDataBinder</code>设计的,专门用于处理HTTP请求参数的转换,而不是JSON数据的反序列化。</p>
<p>两者的工作层面不同:</p>
<ol>
<li><strong>Converter</strong>:
<ul>
<li>工作在Spring MVC层面</li>
<li>处理HTTP请求参数(查询参数、表单数据)</li>
<li>在控制器方法参数绑定之前执行</li>
</ul>
</li>
<li><strong>Jackson的反序列化</strong>:
<ul>
<li>工作在JSON处理层面</li>
<li>处理请求体中的JSON数据</li>
<li>在控制器方法参数绑定之前执行,但独立于Spring MVC的转换机制</li>
</ul>
</li>
</ol>
<h2 id="5-验证这个区别">5. 验证这个区别</h2>
<p>可以通过以下方式验证这个区别:</p>
<ol>
<li>
<p><strong>测试请求参数</strong>(使用Converter):</p>
<pre><code class="language-http">GET /admin/label/list?type=2
</code></pre>
<p>这个请求会使用<code>StringToItemTypeConverter</code>进行转换</p>
</li>
<li>
<p><strong>测试JSON请求体</strong>(使用@JsonValue):</p>
<pre><code class="language-http">POST /admin/label/saveOrUpdate
Content-Type: application/json

{
"type": "2",
"name": "两室一厅"
}
</code></pre>
<p>这个请求会使用Jackson的反序列化机制,依赖于<code>@JsonValue</code>注解</p>
</li>
</ol>
<h2 id="6-替代方案jsoncreator注解">6. 替代方案:@JsonCreator注解</h2>
<p>除了使用<code>@JsonValue</code>,您还可以使用<code>@JsonCreator</code>注解提供自定义的反序列化方法:</p>
<pre><code class="language-java">// @JsonCreator注解指定自定义反序列化方法,静态工厂函数,根据code属性值返回对应的枚举对象实例
// 参数类型需与Json数据中code属性的数据类型一致,或者声明为Object类型,否则无法将json数据与参数绑定
// 参数名可直接使用Json数据中的属性名,否则需使用@JsonProperty注解指定属性名。
@JsonCreator
public static ItemType forValue(@JsonProperty("code") String c) {
    for (ItemType type : ItemType.values()) {
      if (type.getCode().equals(Integer.valueOf(c))) {
            return type;
      }
    }
    throw new IllegalArgumentException("code: " + c + "非法");
}
</code></pre>
<p><strong>优点</strong>:灵活,可进行额外的处理流程</p>
<p><strong>缺点</strong>:</p>
<ol>
<li>繁琐,需要为每个枚举类单独实现</li>
<li>需要注意参数的变量类型和变量名与JSON数据是否对应,否则无法触发;而<code>@JsonValue</code>注解时,JSON数据既可以是字符串也可以是数字</li>
</ol>
<h2 id="总结">总结</h2>
<ul>
<li><strong>HTTP请求参数转换</strong>:使用<code>Converter</code>接口,由Spring MVC的<code>WebDataBinder</code>处理</li>
<li><strong>JSON反序列化</strong>:使用Jackson的机制,依赖于<code>@JsonValue</code>或<code>@JsonCreator</code>注解,不仅是请求体中的JSON数据反序列化,任何时候涉及到JSON数据反序列化,只要使用的是Spring Boot默认的<code>ObejctMapper</code>或配置了相同Jackson设置的<code>ObejctMapper</code>,相关注解都会生效。</li>
<li>两者是独立的机制,解决不同场景下的类型转换问题</li>
<li>为了完整支持两种场景,需要同时提供<code>Converter</code>和适当的Jackson注解</li>
</ul>


</div>
<div id="MySignature" role="contentinfo">
    <p>本文来自博客园,作者:青衫扶夕,转载请注明原文链接:https://www.cnblogs.com/qsswxm/p/19301027</p><br><br>
来源:https://www.cnblogs.com/qsswxm/p/19301027
頁: [1]
查看完整版本: Spring Boot中HTTP请求参数转换和请求体JSON反序列化的区别