前言
这一阵子一直在忙项目,现在终于可以静下心来总结这一段时间的开发心得,新年第一篇打算讲下我犯的一次低级失误,或者可以说是疏忽,希望能给大家一些帮助 ~
最近,笔者也有一些事不顺心,人生不如意事十之八九,努力过了,该熬的夜熬过了,不后悔就够了。希望大家在新年顺顺利利 ~
问题描述
- 场景:业务下沉,将RPC调用方的业务逻辑下沉到RPC服务方,提供通用接口。
代码对比
// 调用RPC服务获取Mission对象
ResponseDTO<Mission> res = missionService.getMissionById(missionId);
// 下沉后直接通过缓存获取对象(RPC服务原有的方法)
Mission mission = missionCache.getMissionById(missionId);
复制代码
问题代码
// set mission对象属性 (其实set很不合理,应该是一个BO)
mission.setA(A);
mission.setB(B);
// 根据mission对象属性触发业务逻辑
// ...
复制代码
根据上述改动,会发现我只是把RPC调用的逻辑下沉到了服务方,调用了服务方的方法获取了mission
对象,但是出现了问题,很显然,导致的直接结果是显示错乱。
问题分析
想必,大家都会知道原因,如下:
1、RPC获取的对象经过反序列化,不是从数据库取出的对象,set对象属性不会影响缓存中的值。 2、缓存里取出来的是数据库取出的对象(缓存的key为missionId)是从数据库取出的对象,set对象属性会影响到其他用户的访问结果。
反序列化得到的对象为啥是新对象?
- 将对象转换成二进制流的过程叫做序列化,将二进制流转换成对象的过程叫做反序列化。
- 下面以示例代码为例,看下经过序列化、反序列化后的对象是否与原对象一致。
package serialization;// Java code for serialization and deserialization
// of a Java object
import java.io.*;
class Demo implements java.io.Serializable
{
public int a;
public String b;
// Default constructor
public Demo(int a, String b)
{
this.a = a;
this.b = b;
}
}
public class TestObject
{
public static void main(String[] args)
{
Demo object = new Demo(1, "geeksforgeeks");
String filename = "file.ser";
// Serialization
try
{
//Saving of object in a file
FileOutputStream file = new FileOutputStream(filename);
ObjectOutputStream out = new ObjectOutputStream(file);
// Method for serialization of object
out.writeObject(object);
out.close();
file.close();
System.out.println("Object has been serialized");
System.out.println(object.toString());
}
catch(IOException ex)
{
System.out.println("IOException is caught");
}
Demo object1 = null;
// Deserialization
try
{
// Reading the object from a file
FileInputStream file = new FileInputStream(filename);
ObjectInputStream in = new ObjectInputStream(file);
// Method for deserialization of object
object1 = (Demo)in.readObject();
in.close();
file.close();
System.out.println("Object has been deserialized ");
System.out.println(object1.toString());
}
catch(IOException ex)
{
System.out.println("IOException is caught");
}
catch(ClassNotFoundException ex)
{
System.out.println("ClassNotFoundException is caught");
}
}
}
复制代码
运行结果
- 可以看到,经过反序列化后,生成的对象是不一样了
Object has been serialized
serialization.Demo@23fc625e
Object has been deserialized
serialization.Demo@1f17ae12
复制代码
- 针对上述遇到的问题,就要使用对象拷贝的方法,生成新对象进行相应业务逻辑处理。
对象拷贝
- 对象拷贝分为深拷贝、浅拷贝,其区别如下:
在Java语言中,数据类型分为值类型(基本数据类型)和引用类型,值类型包括int、double、byte、boolean、char等简单数据类型,引用类型包括类、接口、数组等复杂类型。浅克隆和深克隆的主要区别在于是否支持引用类型的成员变量的复制,下面将对两者进行详细介绍。
最粗暴的get/set
方法
实现clonable
接口
class Demo implements Cloneable {
public int a;
public String b;
// Default constructor
public Demo(int a, String b) {
this.a = a;
this.b = b;
}
@Override
protected Object clone() {
Demo demo = null;
try {
demo = (Demo) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return demo;
}
public static void main(String[] args) {
Demo demo1 = new Demo(1, "1");
Demo demo2 = (Demo) demo1.clone();
System.out.println(demo1 == demo2);
}
}
复制代码
使用apache
或者spring
的工具类,BeanUtils
1、spring (org.springframework.beans.BeanUtils) 2、apache commons-beanutils(org.apache.commons.beanutils.BeanUtils)
// org.springframework.beans.BeanUtils,
// a拷贝到b
// org.apache.commons.beanutils.BeanUtils,
// b拷贝到a
BeanUtils.copyProperties(a, b);
复制代码
- 这个小失误,笔者最近犯了2次,想必对大家来说都是不会犯的错误,特别是在RPC业务逻辑下沉的时候,需要注意这些,因为RPC调用方拿到反序列化后的对象set一些属性的操作(set属性不合理),如果不加思索,就会产生一些问题。
结语
- RPC经过反序列化获取到的是新对象。
- 需要set对象的属性时,要分析上下文,是否影响其他请求结果。
最后,希望我经过一些失败之后,能够从哪儿跌倒,从哪儿爬起来。