Lambda 实现类的序列化与在MybatisPlus中的应用

普通对象的序列化

  1. 实现 Serializable 接口
  2. 可选实现 writeReplacereadResolve 方法,实现写时替换读时替换。写即序列化,读即反序列化。这两个方法有些特殊,不需要实现哪些接口,只要类中存在符合格式的方法,就会被调用。具体可以查看 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();
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值