当前位置: 首页 > 科技观察

盖特引发的一场命案

时间:2023-03-20 14:27:16 科技观察

本文转载自微信公众号《你不牛逼》,作者不牛逼。如需转载本文,请与您联系。1需求我最近提出了调用其他服务REST接口的请求。感觉很简单,于是赶紧开始构造Request类publicclassUser{privateStringname;privateIntegerage;publicUser(Stringname,Integerage){this.name=name;this.age=age;}}哎,上来的时候直接调用newservice.sendRequest(newUser("niu",18));打完字就结束了,又是辛苦(钓鱼)的一天。2定位但是,有一天晚上八点,测试人员突然给我打电话,说是调用失败,同时缺少打印,所以没办法具体说是哪里出了问题。我不认为这么简单的代码会出错,不可能!!在网络上抓包后,发现接收到的参数都是null,但是我明明是调用构造函数传入参数的。会不会有灵异事件??经过分析,整体的数据流是:只能出现问题的地方是序列化JSON的地方,所以本地测试验证了这个结论:publicstaticvoidmain(String[]args)throwsIOException{ObjectMapperobjectMapper=newObjectMapper();Stringrequest=objectMapper.writeValueAsString(newUser("niu",18));System.out.println(request);}虽然出错了,但是序列化并没有变成属性为null的对象,而是直接抛出异常Exceptioninthread"main"com.fasterxml.jackson.databind.exc.InvalidDefinitionException:Noserializerfoundforclassonline.jvm.bean.UserandnopropertiesdiscoveredtocreateBeanSerializer(toavoidexception,disableSerializationFeature.FAIL_ON_EMPTY_BEANS)atcom.fasterxml.jackson.databind.exc.InvalidDefinitionException.from(InvalidDefinitionException.java:77)通过查询异常Information,解决这种异常需要添加Jackson的序列化配置FAIL_ON_EMPTY_BEANS,FAIL_ON_EMPTY_BEANS的配置意味着如果一个bean被序列化为空,它不会异常失败publicstaticvoidmain(String[]args)throwsIOException{ObjectMapperobjectMapper=newObjectMapper();objectMapper.configure(FAIL_ON_EMPTY_BEANS,false);英石ringrequest=objectMapper.writeValueAsString(newUser("niu",18));System.out.println(request);}这种方法不会报错,但是会返回序列化成空字符串,这样会导致receiver让所有的属性都为null通过查看自研RPC框架,可以看到有FAIL_ON_EMPTY_BEANS的配置3解决分析原因,Jackson在序列化的时候需要调用bean的getter方法1,编写getter然后查看结果:String[]args)throwsIOException{ObjectMapperobjectMapper=newObjectMapper();Stringrequest=objectMapper.writeValueAsString(newUser("牛",18));System.out.println(request);//正常输出:{"name":"niu","age":18}}}2、或者把属性Access权限改成publicpublicclassUser{publicStringname;publicIntegerage;publicUser(Stringname,Integerage){this.name=name;this.age=age;}publicstaticvoidmain(String[]args)throwsIOException{ObjectMapperobjectMapper=newObjectMapper();Stringrequest=objectMapper.writeValueAsString(newUser("niu",18));System.out.println(request);//输出是正常的:{"name":"niu","age":18}}}但是如果需要,bean的属性不能就算是getter也要暴露不行吗?3.注解@JsonProperty这是Jackson提供的注解@JsonPropertypublicclassUser{@JsonProperty("userName")privateStringname;@JsonPropertyprivateIntegerage;publicUser(Stringname,Integerage){this.name=name;this.age=age;}publicstaticvoidmain(String[]args)throwsIOException{ObjectMapperobjectMapper=newObjectMapper();Stringrequest=objectMapper.writeValueAsString(newUser("niu",18));System.out.println(request);//{"userName":"niu","age":18}}}看注解@JsonProperty的源码注解Markerannotationthatcanbeusedtodefineannon-staticmethodaa"setter"or"getter"foralogicalproperty(dependingonitssignature),ornon-staticobjectfieldtobeused(serialized,deserialized)asalogicalproperty.用在属性上相当于为属性定义getters和setters如果同时有getter注解和@JsonProperty注解,以哪一个为准?publicclassUser{@JsonProperty("userName")privateStringname;@JsonPropertyprivateIntegerage;publicUser(Stringname,Integerage){this.name=name;this.age=age;}publicStringgetName(){returnname;}publicstaticvoidmain(String[]args)throwsIOException{ObjectMapperobjectMapper=newObjectMapper();Stringrequest=objectMapper.writeValueAsString(newUser("niu",18));System.out.println(request);//{"age":18,"userName":"niu"}}}如果getter没有属性,效果如何?publicclassUser{@JsonProperty("userName")privateStringname;@JsonPropertyprivateIntegerage;publicUser(Stringname,Integerage){this.name=name;this.age=age;}publicStringgetName2(){returnname;}publicstaticvoidmain(String[]args)throwsIOException{ObjectMapperobjectMapper=newObjectMapper();Stringrequest=objectMapper.writeValueAsString(newUser("牛",18));System.out.println(request);//{"age":18,"name2":"niu","userName":"niu"}}}意思是如果有@JsonProperty注解,先用注解找到对象类的所有get方法,然后去get,然后小写,aseachjson的一个键值,get方法的返回值作为值,然后将字段反映到json中。4.特例还有一种特例是lombok生成getter方法,属性首字母大写:@GetterpublicclassUser{@JsonPropertyprivateStringnAme;@JsonPropertyprivateIntegerage;publicUser(Stringname,Integerage){this.nAme=name;this.age=age;}publicstaticvoidmain(String[]args)throwsIOException{ObjectMapperobjectMapper=newObjectMapper();Stringrequest=objectMapper.writeValueAsString(newUser("niu",18));System.out.println(request);//{"nAme":"niu","age":18,"name":"niu"}}}这是因为lombok生成的getter会将属性的首字母大写,序列化为大写中间的小写字母变成小写,也就是NA变成小写,所以序列化结果会有两个属性:name(getter获取)和nAme(annotation获取)publicStringgetNAme(){returnthis.nAme;}如果我们使用它我们自己idea快捷键生成getter,然后序列化nAmepublicStringgetnAme(){returnnAme;}4小结很多bug都发生在你认为没有问题的地方。这看起来很简单,但你需要更加小心。同时,需要更加注意序列化的原则。总体感觉还是用Gson做序列化更省心。完全不需要关心Getter和Setter方法,完全按照属性名序列化。本文涉及的BUG流程及解决方法希望对您有所帮助,再见。