You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Quarkus Jackson无反射优化下带JsonView类的序列化行为疑问

Quarkus Jackson无反射序列化器差异问题解析

问题背景

我在项目中启用了配置quarkus.rest.jackson.optimization.enable-reflection-free-serializers=true,开启Quarkus的Jackson无反射优化功能。现有两个实体类:CustomerWithoutJsonView未使用JsonView注解,CustomerWithJsonView的部分属性标注了@JsonView。二者生成的字节码序列化器存在明显差异——CustomerWithJsonView的序列化器里完全没有属性序列化逻辑,但实际运行时接口输出却和预期一致。想搞明白:

  • 为什么生成的序列化器不同,但序列化行为却一致?
  • 这类场景下接口的预期表现是什么?

相关代码

无JsonView的实体类

// Without json view enabled
package org.acme.model;

import java.util.*;

public final class CustomerWithoutJsonView {
  int id;
  String name;
  List<Address> addresses;

  public CustomerWithoutJsonView(int id, String name) {
    this.id = id;
    this.name = name;
  }

  public int getId() {
    return id;
  }

  public String getName() {
    return name;
  }
}

带JsonView的实体类

// With Json view enabled
package org.acme.model;

import com.fasterxml.jackson.annotation.*;

import java.util.*;

public final class CustomerWithJsonView {

  int id;
  String name;
  List<Address> addresses;

  public CustomerWithJsonView(int id, String name, List<Address> addresses) {
    this.id = id;
    this.name = name;
    this.addresses = addresses;
  }

  @JsonView({Views.Search.class})
  public int getId() {
    return id;
  }

  public String getName() {
    return name;
  }

  @JsonView({Views.Read.class})
  public List<Address> getAddresses() {
    return addresses;
  }
}

Address实体类

package org.acme.model;

public final class Address {
  String streetName;

  public Address(String streetName) {
    this.streetName = streetName;
  }

  public String getStreetName() {
    return streetName;
  }
}

接口资源类

// And the following resource class
package org.acme;

import com.fasterxml.jackson.annotation.*;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;
import org.acme.model.*;

import java.util.*;

@Path("")
public class GreetingResource {

    // All the attributes annotated with search are only included
    @GET
    @Path("with-json-view-search")
    @Produces(MediaType.APPLICATION_JSON)
    @JsonView({Views.Search.class})
    public CustomerWithJsonView withJsonViewSearch() {
        return new CustomerWithJsonView(1, "John Doe",
                List.of(new Address("123 Main St"), new Address("456 Elm St")));
    }

    // All the attributes annotated with either search or view are included
    @GET
    @Path("with-json-view-read")
    @Produces(MediaType.APPLICATION_JSON)
    @JsonView({Views.Read.class})
    public CustomerWithJsonView withJsonViewRead() {
        return new CustomerWithJsonView(1, "John Doe",
            List.of(new Address("123 Main St"), new Address("456 Elm St")));
    }

    // no json view is applicable so all attributes are provided
    @GET
    @Path("without-json-view")
    @Produces(MediaType.APPLICATION_JSON)
    public CustomerWithoutJsonView withoutJsonView() {
        return new CustomerWithoutJsonView(2, "John Doe");
    }
}

生成的序列化器字节码

package org.acme.model;

import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.SerializableString;
import com.fasterxml.jackson.core.io.SerializedString;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.ser.std.StdSerializer;
import io.quarkus.resteasy.reactive.jackson.runtime.mappers.JacksonMapperUtil.SerializationInclude;
import java.io.IOException;

// $VF: synthetic class
public class CustomerWithoutJsonView$quarkusjacksonserializer extends StdSerializer {
   public CustomerWithoutJsonView$quarkusjacksonserializer() {
      super(CustomerWithoutJsonView.class);
   }

   public void serialize(Object var1, JsonGenerator var2, SerializerProvider var3) throws IOException {
      CustomerWithoutJsonView var4 = (CustomerWithoutJsonView)var1;
      SerializationInclude var5 = SerializationInclude.decode(var1, var3);
      var2.writeStartObject();
      int var6 = var4.getId();
      if (var5.shouldSerialize(var6)) {
         SerializedString var7 = SerializedStrings$quarkusjacksonserializer.id;
         var2.writeFieldName((SerializableString)var7);
         var2.writeNumber(var6);
      }

      String var8 = var4.getName();
      if (var5.shouldSerialize(var8)) {
         SerializedString var9 = SerializedStrings$quarkusjacksonserializer.name;
         var2.writeFieldName((SerializableString)var9);
         var2.writeString(var8);
      }

      var2.writeEndObject();
   }
}
package org.acme.model;

import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.ser.std.StdSerializer;
import io.quarkus.resteasy.reactive.jackson.runtime.mappers.JacksonMapperUtil.SerializationInclude;
import java.io.IOException;

// $VF: synthetic class
public class CustomerWithJsonView$quarkusjacksonserializer extends StdSerializer {
   public CustomerWithJsonView$quarkusjacksonserializer() {
      super(CustomerWithJsonView.class);
   }

   public void serialize(Object var1, JsonGenerator var2, SerializerProvider var3) throws IOException {
      SerializationInclude.decode(var1, var3);
      var2.writeStartObject();
      var2.writeEndObject();
   }
}

问题解析

1. 序列化器差异的原因

Quarkus的Jackson无反射优化是编译期生成序列化器,但@JsonView属于动态场景:运行时激活的View由接口方法上的注解决定(比如你两个接口分别激活了Search和Read),编译期无法提前预知所有可能的View组合,也就没办法生成适配所有场景的硬编码序列化逻辑。

这种情况下,Quarkus会生成一个空实现的序列化器作为占位符,实际运行时会自动降级到Jackson的动态序列化逻辑(基于提前解析的注解元数据,而非完全的反射)。

2. 运行时行为一致的逻辑

虽然CustomerWithJsonView的编译期序列化器是空的,但Jackson在运行时会检测到这个情况,转而使用自身的BeanSerializer处理:

  • 第一步:根据当前接口方法的@JsonView注解,确定激活的View类型
  • 第二步:遍历实体类的所有属性,检查属性的@JsonView是否与激活View匹配
  • 第三步:序列化所有匹配的属性,以及没有标注@JsonView的属性(这类属性默认在任何View下都序列化)

而CustomerWithoutJsonView没有@JsonView,编译期可以确定所有属性都需要序列化,所以生成了完整的硬编码序列化逻辑,直接在运行时使用,不需要降级。

3. 场景预期表现

完全符合你代码中的注释逻辑:

  • /with-json-view-search:仅返回id属性(标注了Search View)
  • /with-json-view-read:返回id(Search View)、addresses(Read View)和name(无View注解,默认序列化)
  • /without-json-view:返回CustomerWithoutJsonView的id和name所有属性

内容的提问来源于stack exchange,提问作者Gautam Lihala

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.12 14:17:02