Lombok

Lombok 和 Jackson 矛盾错误

  • @getter,@setter在遇到形如aName的时候,jakson无法将其正确反序列化
  • 当 RESTful 接口接收到客户端发送的 JSON 请求体 时,Spring Boot 使用 Jackson 将 JSON 数据反序列化为 Java 对象。
  • 得出结论Lombok在转换的时候将其转换为AName,符合直觉,但是jakson无法识别。
  • 示范如下:
    • 用Lombok实现getter,setter等配置的自动注入:
@Data
public class User {
    private String name;
    private String aName;
    private String aaName;
}

发现get,set方法正确实现,符合直觉

    @Generated
    public void setName(final String name) {
        this.name = name;
    }

    @Generated
    public void setAName(final String aName) {
        this.aName = aName;
    }

    @Generated
    public void setAaName(final String aaName) {
        this.aaName = aaName;
    }

json请求字段

{ 
    "name" : "name",
    "aname" : "aname",
    "aaName" : "aaName",
    "aName" : "aName"
}

发现传过来的只有aname: aname, 却被识别为aName的字段,但其实是aname的字段。 这个显然是jakson传入时候的问题

User(name=name, aName=aname, aaName=aaName)

alt text alt text

改成这个就好了

@Data
public class User {
    private String name;
    @JsonProperty("aName") // 明确指定 JSON 键名
    private String aName;
    private String aaName;
}

debug流程图展示

这里要跟的是 @RequestBody User user 的参数解析过程。进入 Controller 时,JSON 已经转换成 User,所以断点要打在 Controller 之前。

下面按 Spring MVC 6.1.8、Jackson 2.17.1 源码列关键调用点,省略部分辅助调用;实际调试以项目依赖版本为准。

DispatcherServlet.doDispatch
  -> getHandler:找到 /user 对应的 HandlerMethod
  -> getHandlerAdapter:取得 RequestMappingHandlerAdapter
  -> RequestMappingHandlerAdapter.handleInternal
  -> invokeHandlerMethod
  -> ServletInvocableHandlerMethod.invokeAndHandle
  -> InvocableHandlerMethod.invokeForRequest
  -> getMethodArgumentValues:准备 Controller 的参数
  -> HandlerMethodArgumentResolverComposite.resolveArgument
  -> RequestResponseBodyMethodProcessor.resolveArgument:处理 @RequestBody
  -> readWithMessageConverters:根据类型和 Content-Type 选择转换器
  -> MappingJackson2HttpMessageConverter
     (read、readJavaType 实现在父类 AbstractJackson2HttpMessageConverter 中)
  -> ObjectReader.readValue:把请求体交给 Jackson

这条链路可从 RequestMappingHandlerAdapter、RequestResponseBodyMethodProcessor 和 AbstractJackson2HttpMessageConverter 接着往下跟。

对于这里普通 JavaBean 的 setter 赋值路径,继续跟:

ObjectReader.readValue(InputStream / Reader)
  -> _bindAndClose
  -> DefaultDeserializationContext.readRootValue
  -> BeanDeserializer.deserialize
  -> vanillaDeserialize:普通 Bean 的快速路径
  -> _beanProperties.find(propName):用 JSON 键名查找属性
  -> MethodProperty.deserializeAndSet:解析属性值
  -> _setter.invoke(instance, value)
  -> User.setAName(value)
  -> 参数解析返回 User 对象
  -> InvocableHandlerMethod.doInvoke
  -> Controller 的 user 方法

vanillaDeserialize 不是所有对象都会经过;字段直接赋值、构造器绑定、自定义反序列化器会走不同分支。看 BeanDeserializer 和 MethodProperty 时,要先确认当前实际进入的分支。