一个Getter引发的"血案"

简介: 一个Getter引发的"血案"

需求


最近做一了个需求,调用其他服务的REST接口,感觉很简单,于是迅速就搞起来了

构造Request类


public class User {
    private String name;
    private Integer age;
    public User(String name, Integer age) {
        this.name = name;
        this.age = age;
    }
}


啪,我上来就一new


service.sendRequest(new User("niu", 18));


打完,收工,又是努力工作(摸鱼)的一天。


定位


但是,某天晚上8点,测试人员突然给我打电话,说调用失败,同时本身又缺少打印,没有办法具体哪出问题了。


我是不会认为这么简单的代码自己会出错的,不可能!!


经过网络抓包后发现,收到的参数都是null,但是我这边明明调用构造器传入参数了

image.png

难道出现灵异事件了?


经过分析,整体数据流为:

image.png

能出现问题的地方只能是序列化JSON地方,于是本地测试验证了这一结论:


public static void main(String[] args) throws IOException {
    ObjectMapper objectMapper = new ObjectMapper();
    String request = objectMapper.writeValueAsString(new User("niu", 18));
    System.out.println(request);
}


虽然是出问题了,但是序列化并没有转为属性为null的对象,而是直接抛出异常


Exception in thread "main" com.fasterxml.jackson.databind.exc.InvalidDefinitionException: No serializer found for class online.jvm.bean.User and no properties discovered to create BeanSerializer (to avoid exception, disable SerializationFeature.FAIL_ON_EMPTY_BEANS)
  at com.fasterxml.jackson.databind.exc.InvalidDefinitionException.from(InvalidDefinitionException.java:77)

通过查询异常资料,解决掉这种异常需要在增加Jackson的序列化配置FAIL_ON_EMPTY_BEANS,FAIL_ON_EMPTY_BEANS这个配置表示如果某个bean序列化为空时不会异常失败

public static void main(String[] args) throws IOException {
    ObjectMapper objectMapper = new ObjectMapper();
    objectMapper.configure(FAIL_ON_EMPTY_BEANS, false);
    String request = objectMapper.writeValueAsString(new User("niu", 18));
    System.out.println(request);
}

这种就不会报错,而是返回序列化成空串,也就导致接受方为属性都为null

通过看自研RPC框架看到是有该FAIL_ON_EMPTY_BEANS的配置


解决


再来分析一下原因,Jackson序列化时需要调用bean的getter方法


1、写上getter后再看下结果:


public class User {
    private String name;
    private Integer age;
    public User(String name, Integer age) {
        this.name = name;
        this.age = age;
    }
    public String getName() {
        return name;
    }
    public Integer getAge() {
        return age;
    }
    public static void main(String[] args) throws IOException {
        ObjectMapper objectMapper = new ObjectMapper();
        String request = objectMapper.writeValueAsString(new User("niu", 18));
        System.out.println(request);
        // 输出正常 : {"name":"niu","age":18}
    }
}


2、或者把属性访问权限改为public


public class User {
    public String name;
    public Integer age;
    public User(String name, Integer age) {
        this.name = name;
        this.age = age;
    }
    public static void main(String[] args) throws IOException {
        ObjectMapper objectMapper = new ObjectMapper();
        String request = objectMapper.writeValueAsString(new User("niu", 18));
        System.out.println(request);
        // 输出正常 : {"name":"niu","age":18}
    }
}

但是如果要求不能暴露bean的属性即使是getter也不行呢?


3、注解 @JsonProperty


这是就需要使用Jackson提供的注解 @JsonProperty

public class User {
    @JsonProperty("userName")
    private String name;
    @JsonProperty
    private Integer age;
    public User(String name, Integer age) {
        this.name = name;
        this.age = age;
    }
    public static void main(String[] args) throws IOException {
        ObjectMapper objectMapper = new ObjectMapper();
        String request = objectMapper.writeValueAsString(new User("niu", 18));
        System.out.println(request);
        //   {"userName":"niu","age":18}
    }
}

来看下注解@JsonProperty的源码注释

Marker annotation that can be used to define a non-static method as a "setter" or "getter" for a logical property (depending on its signature), or non-static object field to be used (serialized, deserialized) as a logical property.

大体意思是注解如果用在属性上相当于为该属性定义getter和setter。

那如果既有getter又有@JsonProperty注解,以哪个为准呢?

public class User {
    @JsonProperty("userName")
    private String name;
    @JsonProperty
    private Integer age;
    public User(String name, Integer age) {
        this.name = name;
        this.age = age;
    }
    public String getName() {
        return name;
    }
    public static void main(String[] args) throws IOException {
        ObjectMapper objectMapper = new ObjectMapper();
        String request = objectMapper.writeValueAsString(new User("niu", 18));
        System.out.println(request);
        // {"age":18,"userName":"niu"}
    }
}

如果getter一个没有的属性,效果如何呢?

public class User {
    @JsonProperty("userName")
    private String name;
    @JsonProperty
    private Integer age;
    public User(String name, Integer age) {
        this.name = name;
        this.age = age;
    }
    public String getName2() {
        return name;
    }
    public static void main(String[] args) throws IOException {
        ObjectMapper objectMapper = new ObjectMapper();
        String request = objectMapper.writeValueAsString(new User("niu", 18));
        System.out.println(request);
        // {"age":18,"name2":"niu","userName":"niu"}
    }
}

这说明如果有@JsonProperty注解,先以注解为准


然后利用反射找到对象类的所有get方法,接下来去get,然后小写化,作为json的每个key值,而get方法的返回值作为value。接下来再反射field,添加到json中。


4、特殊情况


还有一种比较特殊的情况, getter方法由lombok生成,且属性的次首字母是大写:

@Getter
public class User {
    @JsonProperty
    private String nAme;
    @JsonProperty
    private Integer age;
    public User(String name, Integer age) {
        this.nAme = name;
        this.age = age;
    }
    public static void main(String[] args) throws IOException {
        ObjectMapper objectMapper = new ObjectMapper();
        String request = objectMapper.writeValueAsString(new User("niu", 18));
        System.out.println(request);
        // {"nAme":"niu","age":18,"name":"niu"}
    }
}

这是因为lombok生成的getter会把属性的第一个字母变成大写,

序列化时会把get后与小写字母中间的大写变成小写,也就是会把NA变成小写

所以序列化结果会有name(getter获取)和nAme(注解获取)两个属性

public String getNAme() {
    retrn this.nAme;
}

如果我们自己用idea快捷键生成getter,

此时之后序列化nAme

public String getnAme() {
    return nAme;
}


小结


许多bug都是在自以为没有问题的地方产生,看似简单,更需要小心,同时也需要多注意序列化原理,整体感觉序列化还是用Gson更省心,完全不用关心Getter和Setter方法,会完全按照属性名来序列化。


本文的涉及的bug过程和解决方式希望对你也有所帮助,再见。


目录
相关文章
|
Java Maven
maven依赖的小坑
maven依赖的小坑
1410 0
|
SQL 缓存 NoSQL
接口的幂等性设计和防重保证,详细分析幂等性的几种实现方法
本篇文章详细说明了幂等性,解释了什么是幂等性,幂等性的使用场景,讨论了幂等和防重的概念。分析了幂等性的情况以及如何设计幂等性服务。阐述了幂等性实现防重的几种策略,包括乐关锁,防重表,分布式锁,token令牌以及支付缓冲区。
9919 0
接口的幂等性设计和防重保证,详细分析幂等性的几种实现方法
|
Java Spring 容器
在Feign接口中返回泛型类型——自定义Decoder
前几天对接了一套第三方接口,所有接口的请求地址一样,请求参数和响应结果中有很多共同的字段,所以就想把这些字段都抽出来,Feign定义的接口直接返回泛型类型。
在Feign接口中返回泛型类型——自定义Decoder
|
3月前
|
JSON 人工智能 自然语言处理
基于顶级 Agent(Claude Code)的 Harness 工程搭建式业务 Agent 评测方案
用一个强 Agent 构建评测 Harness,系统性评测一群业务 Agent(文章内容基于作者个人技术实践与独立思考,旨在分享经验,仅代表个人观点。)
555 0
|
9月前
|
SQL 前端开发 NoSQL
大厂如何解决订单幂等问题
本文介绍如何保证分布式系统中订单服务的幂等性,避免重复下单与ABA问题。通过预生成唯一订单号并利用数据库主键约束,确保创建幂等;通过版本号机制校验与自增,实现更新幂等。方案适用于各类数据库存储场景。
|
Java 开发者 Sentinel
网关修改响应码,拯救业务不规范设计
项目中的后端接口普遍使用200响应码,无论是否出错,导致OpenFeign和第三方应用处理困难。问题在于后端开发者对HTTP基础知识理解不足,未统一处理异常时的响应码。客户端依赖响应体的`code`字段而非HTTP状态码判断请求结果。为解决这个问题,网关可扮演关键角色:
438 0
|
前端开发
[巨详细]使用HBuilder-X新建uniapp项目教程
【6月更文挑战第6天】安装HBuilder-X 详细步骤可看上文》》 启动uniapp项目 先打开HBuilder-X
1376 5
|
SQL Oracle 架构师
支持全量&增量迁移!YashanDB增量迁移实现原理解读
本文基于YashanDB高可用架构师马志宏在“2024年国产数据库创新生态大会”的演讲,深入阐述了YashanDB的数据迁移流程及增量迁移组件的技术原理。崖山迁移平台YMP提供异构RDBMS与YashanDB间的迁移评估、数据迁移和校验功能。最新版本V23.3新增增量迁移组件,支持在线全量和增量迁移的无缝衔接,确保业务无感知迁移,保障数据一致性和业务连续性。迁移组件由source、transform、sink三个模块组成,具备一键式迁移、支持多种数据类型和DDL操作、无侵入式部署等关键能力,确保高效、可靠的迁移体验。未来将优化兼容性、支持双向复制和提升数据质量管理。
|
设计模式 数据中心 网络架构
|
Prometheus 监控 Cloud Native
Spring Boot 性能护航!Prometheus、Grafana、ELK 组合拳,点燃数字化时代应用稳定之火
【8月更文挑战第29天】在现代软件开发中,保证应用性能与稳定至关重要。Spring Boot 作为流行的 Java 框架,结合 Prometheus、Grafana 和 ELK 可显著提升监控与分析能力。Prometheus 负责收集时间序列数据,Grafana 将数据可视化,而 ELK (Elasticsearch、Logstash、Kibana)则管理并分析应用日志。通过具体实例演示了如何在 Spring Boot 应用中集成这些工具:配置 Prometheus 获取度量信息、Grafana 显示结果及 ELK 分析日志,从而帮助开发者快速定位问题,确保应用稳定高效运行。
1180 1

热门文章

最新文章