普通对象的序列化
- 实现
Serializable
接口 - 可选实现
writeReplace
与readResolve
方法,实现写时替换
与读时替换
。写即序列化,读即反序列化。这两个方法有些特殊,不需要实现哪些接口,只要类中存在符合格式的方法,就会被调用。具体可以查看ObjectOutputStream
与ObjectInputStream
的源码,内部会根据硬编码反射获取这两个方法的 Method 实例进行调用。
代码:
public class Person implements Serializable {
private String name;
public void setName(String name) {
this.name = name;
}
public String getName() {
return this.name;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
'}';
}
// 写时替换
public Object writeReplace() {
System.out.println("Write replace...");
Person person = new Person();
person.setName("CQQ-Replaced");
return person;
}
// 读时替换
public Object readResolve() {
System.out.println("Read resolve...");
Person person = new Person();
person.setName("CQQ-ReadResolved");
return person;
}
}
public class ObjectSerializeTest {
public static void main(String[] args) throws Exception {
Person person = new Person();
person.setName("CQQ");
Object process = process(person);
System.out.println(process);
}
// 序列化 & 反序列化
public static Object process(Object obj) throws Exception {
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
objectOutputStream.writeObject(obj);
objectOutputStream.flush();
byte[] objectBytes = byteArrayOutputStream.toByteArray();
ObjectInputStream objectInputStream = new ObjectInputStream(new ByteArrayInputStream(objectBytes));
return objectInputStream.readObject();
}
}
输出:
Write replace...
Read resolve...
Person{name='CQQ-ReadResolved'}
Lambda 实例的序列化
修改 main 方法:
Consumer<String> c = str -> {};
Object process2 = process(c);
System.out.println(process2);
运行:
Exception in thread "main" java.io.NotSerializableException:
com.bright.moon.play.whatslambda.serialize.ObjectSerializeTest$$Lambda$1/864237698
因为原生的 Consumer 接口并没有实现 Serializable 接口,所以需要手动魔改一下,新增 SConsumer 函数式接口:
@FunctionalInterface
public interface SConsumer<T> extends Consumer<T>, Serializable {
}
修改 main 方法,实例化 SConsumer 后再次运行:
com.bright.moon.play.whatslambda.serialize.ObjectSerializeTest$$Lambda$6/1971489295@3abfe836
序列化与反序列化成功。那么底层是如何实现的呢?我们 dump 一下 Lambda 实例的实现类源码:
/* loaded from: ObjectSerializeTest$$Lambda$6.class */
final /* synthetic */ class ObjectSerializeTest$$Lambda$6 implements SConsumer {
private ObjectSerializeTest$$Lambda$6() {
}
@LambdaForm.Hidden
public void accept(Object obj) {
ObjectSerializeTest.lambda$main$b87162fe$1((String) obj);
}
private final Object writeReplace() {
return new SerializedLambda(
ObjectSerializeTest.class,
"SConsumer",
"accept",
"(Ljava/lang/Object;)V",
6,
"ObjectSerializeTest",
"lambda$main$b87162fe$1",
"(Ljava/lang/String;)V",
"(Ljava/lang/String;)V",
new Object[0]
);
}
}
是的,假若源函数式接口实现了 Serializable 接口,那么在运行时生成的 Lambda 实现类时,也会为其生成一个 writeReplace 方法,该方法会返回一个 SerializedLambda 对象实例,而非 SConsumer
。当反序列化 SerializedLambda 对象时,才会根据 SerializedLambda 对象中维护的数据在创建 SConsumer 实例。也就是说,SerializedLambda 中一定也存在一个 readResolve 方法
:
public final class SerializedLambda implements Serializable {
private Object readResolve() throws ReflectiveOperationException {
try {
Method deserialize = AccessController.doPrivileged(new PrivilegedExceptionAction<Method>() {
@Override
public Method run() throws Exception {
Method m = capturingClass.getDeclaredMethod("$deserializeLambda$", SerializedLambda.class);
m.setAccessible(true);
return m;
}
});
return deserialize.invoke(null, this);
}
catch (PrivilegedActionException e) {
...
}
}
}
Mybatis Plus 中的应用
说了这么多,知道了 lambda 实例的序列化方式有什么用呢?我们在编写一段代码:
public static com.bright.moon.play.whatslambda.serialize.SerializedLambda getMethodNameCalled(Object obj) throws Exception {
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
objectOutputStream.writeObject(obj);
objectOutputStream.flush();
byte[] objectBytes = byteArrayOutputStream.toByteArray();
ObjectInputStream objectInputStream = new ObjectInputStream(new ByteArrayInputStream(objectBytes)) {
@Override
protected Class<?> resolveClass(ObjectStreamClass desc) throws IOException, ClassNotFoundException {
Class<?> resolveClass = super.resolveClass(desc);
if (resolveClass == SerializedLambda.class) {
return com.bright.moon.play.whatslambda.serialize.SerializedLambda.class;
}
return resolveClass;
}
};
return (com.bright.moon.play.whatslambda.serialize.SerializedLambda) objectInputStream.readObject();
}
此方法的含义:通过对 Lambda 对象的序列化与反序列化,获取到 Lambda 实例的详细信息。
代码中出现的 SerializedLambda 魔改版,与原生唯一的区别在于删除了 readResolve 方法。
对比前面提到的 Lambda 对象的序列化与反序列化,我们将 将原生的 SerializedLambda 偷换为我们自己魔改的 SerializedLambda
。目的就是在于屏蔽掉 readResolve
方法,使得 objectInputStream.readObject()
进行反序列化时不再执行 readResolve 方法后返回 Lambda 实例,而是直接返回封装了实例信息的 SerializedLambda 。
现在,我们可以拿到 Lambda 实例的信息了,其中有一个很关键的数据:若 Lambda 实例是被方法引用构成的,那么该方法的名称就会被封装在 SerializedLambda 中,测试一下。
public class ObjectSerializeTest {
public static void main(String[] args) throws Exception {
SFunction<Person, String> sf = Person::getName;
System.out.println(getMethodNameCalled(sf).getImplClass());
System.out.println(getMethodNameCalled(sf).getImplMethodName());
SFunction<Person, String> sf2 = p -> null;
System.out.println(getMethodNameCalled(sf2).getImplClass());
System.out.println(getMethodNameCalled(sf2).getImplMethodName());
}
}
输出结果:
com/bright/moon/play/whatslambda/serialize/Person
getName
com/bright/moon/play/whatslambda/serialize/ObjectSerializeTest
lambda$main$ed2a2f00$1
成功获取到了 getName(如果不知道 lambda$main$ed2a2f00$1
这个方法是什么,可以看下 Lambda 实例的实现类源码)。也就是说我们实现了获取 Lambda 体中方法引用的方法名称
这样一个功能。Mybatis plus 就是使用这个方式,根据传递 的 Lambda 获取到所对应的实体字段,继而获取到所对应的表字段。比如:
new LambdaQueryWrapper<HospitalOrderInfo>()
.eq(HospitalOrderInfo::getOrderCode, "S123")
.in(HospitalOrderInfo::getOrderState, 1, 2, 3)
...
基于反射的实现方式
这一种方式要简单的很多,直接获取 Lambda 实例的 writeReplace 方法,获取其返回值即可。
public static String getMethodNameCalledByReflect(Object obj) throws Exception {
Method writeReplace = obj.getClass().getDeclaredMethod("writeReplace");
writeReplace.setAccessible(true);
SerializedLambda serializedLambda = (SerializedLambda) writeReplace.invoke(obj);
return serializedLambda.getImplMethodName();
}