模板式自助建站,芜湖市住房和城乡建设厅网站,百度收录最快的网站,牡丹江47号公告前提 笔者在下班空余时间想以Javassist为核心基于JDBC写一套摒弃反射调用的轻量级的ORM框架#xff0c;过程中有研读mybatis、tk-mapper、mybatis-plus和spring-boot-starter-jdbc的源代码#xff0c;其中发现了mybatis-plus中的LambdaQueryWrapper可以获取当前调用的Lambda表… 前提 笔者在下班空余时间想以Javassist为核心基于JDBC写一套摒弃反射调用的轻量级的ORM框架过程中有研读mybatis、tk-mapper、mybatis-plus和spring-boot-starter-jdbc的源代码其中发现了mybatis-plus中的LambdaQueryWrapper可以获取当前调用的Lambda表达式中的方法信息实际上是CallSite的信息这里做一个完整的记录。本文基于JDK11编写其他版本的JDK不一定合适。 神奇的Lambda表达式序列化 之前在看Lambda表达式源码实现的时候没有细看LambdaMetafactory的注释这个类顶部大量注释中其中有一段如下 简单翻译一下就是可序列化特性。一般情况下生成的函数对象这里应该是特指基于Lambda表达式实现的特殊函数对象不需要支持序列化特性。如果需要支持该特性FLAG_SERIALIZABLELambdaMetafactory的一个静态整型属性值为1 0可以用来表示函数对象是序列化的。一旦使用了支持序列化特性的函数对象那么它们以SerializedLambda类的形式序列化这些SerializedLambda实例需要额外的捕获类的协助捕获类如MethodHandles.Lookup的caller参数所描述详细信息参阅SerializedLambda。 在LambdaMetafactory的注释中再搜索一下FLAG_SERIALIZABLE可以看到这段注释 大意为设置了FLAG_SERIALIZABLE标记后生成的函数对象实例会实现Serializable接口并且会存在一个名字为writeReplace的方法该方法的返回值类型为SerializedLambda。调用这些函数对象的方法前面提到的捕获类的调用者必须存在一个名字为$deserializeLambda$的方法如SerializedLambda类所描述。 最后看SerializedLambda的描述注释有四大段这里贴出并且每小段提取核心信息 各个段落大意如下 段落一SerializedLambda是Lambda表达式的序列化形式这类存储了Lambda表达式的运行时信息段落二为了确保Lambda表达式的序列化实现正确性编译器或者语言类库可以选用的一种方式是确保writeReplace方法返回一个SerializedLambda实例段落三SerializedLambda提供一个readResolve方法其职能类似于调用捕获类中静态方法$deserializeLambda$(SerializedLambda)并且把自身实例作为入参该过程理解为反序列化过程段落四序列化和反序列化产生的函数对象的身份敏感操作的标识形式如System.identityHashCode()、对象锁定等等是不可预测的 最终的结论就是如果一个函数式接口实现了Serializable接口那么它的实例就会自动生成了一个返回SerializedLambda实例的writeReplace方法可以从SerializedLambda实例中获取到这个函数式接口的运行时信息。这些运行时信息就是SerializedLambda的属性 属性含义capturingClass捕获类当前的Lambda表达式出现的所在类functionalInterfaceClass名称并且以/分隔返回的Lambda对象的静态类型functionalInterfaceMethodName函数式接口方法名称functionalInterfaceMethodSignature函数式接口方法签名其实是参数类型和返回值类型如果使用了泛型则是擦除后的类型implClass名称并且以/分隔持有该函数式接口方法的实现方法的类型实现了函数式接口方法的实现类implMethodName函数式接口方法的实现方法名称implMethodSignature函数式接口方法的实现方法的方法签名实是参数类型和返回值类型instantiatedMethodType用实例类型变量替换后的函数式接口类型capturedArgsLambda捕获的动态参数implMethodKind实现方法的MethodHandle类型 举个实际的例子定义一个实现了Serializable的函数式接口并且调用它 public class App {FunctionalInterfacepublic interface CustomerFunctionS, T extends Serializable {T convert(S source);}public static void main(String[] args) throws Exception {CustomerFunctionString, Long function Long::parseLong;Long result function.convert(123);System.out.println(result);Method method function.getClass().getDeclaredMethod(writeReplace);method.setAccessible(true);SerializedLambda serializedLambda (SerializedLambda)method.invoke(function);System.out.println(serializedLambda.getCapturingClass());}
} 执行的DEBUG信息如下 这样就能获取到函数式接口实例在调用方法时候的调用点运行时信息甚至连泛型参数擦除前的类型都能拿到那么就可以衍生出很多技巧。例如 public class ConditionApp {FunctionalInterfacepublic interface CustomerFunctionS, T extends Serializable {T convert(S source);}Datapublic static class User {private String name;private String site;}public static void main(String[] args) throws Exception {Condition c1 addCondition(User::getName, , throwable);System.out.println(c1 c1);Condition c2 addCondition(User::getSite, IN, (throwx.cn,vlts.cn));System.out.println(c1 c2);}private static S Condition addCondition(CustomerFunctionS, String function,String operation,Object value) throws Exception {Condition condition new Condition();Method method function.getClass().getDeclaredMethod(writeReplace);method.setAccessible(true);SerializedLambda serializedLambda (SerializedLambda) method.invoke(function);String implMethodName serializedLambda.getImplMethodName();int idx;if ((idx implMethodName.lastIndexOf(get)) 0) {condition.setField(Character.toLowerCase(implMethodName.charAt(idx 3)) implMethodName.substring(idx 4));}condition.setEntityKlass(Class.forName(serializedLambda.getImplClass().replace(/, .)));condition.setOperation(operation);condition.setValue(value);return condition;}Dataprivate static class Condition {private Class? entityKlass;private String field;private String operation;private Object value;}
}// 执行结果
c1 ConditionApp.Condition(entityKlassclass club.throwable.lambda.ConditionApp$User, fieldname, operation, valuethrowable)
c1 ConditionApp.Condition(entityKlassclass club.throwable.lambda.ConditionApp$User, fieldsite, operationIN, value(throwx.cn,vlts.cn)) ❝ 很多人会担心反射调用的性能其实在高版本的JDK反射性能已经大幅度优化十分逼近直接调用的性能更何况有些场景是少量反射调用场景可以放心使用。 ❞ 前面花大量篇幅展示了SerializedLambda的功能和使用接着看Lambda表达式的序列化与反序列化 public class SerializedLambdaApp {FunctionalInterfacepublic interface CustomRunnable extends Serializable {void run();}public static void main(String[] args) throws Exception {invoke(() - {});}private static void invoke(CustomRunnable customRunnable) throws Exception {ByteArrayOutputStream baos new ByteArrayOutputStream();ObjectOutputStream oos new ObjectOutputStream(baos);oos.writeObject(customRunnable);oos.close();ObjectInputStream ois new ObjectInputStream(new ByteArrayInputStream(baos.toByteArray()));Object target ois.readObject();System.out.println(target);}
} 结果如下图 Lambda表达式序列化原理 关于Lambda表达式序列化的原理可以直接参考ObjectStreamClass、ObjectOutputStream和ObjectInputStream的源码这里直接说结论 前提条件待序列化对象需要实现Serializable接口待序列化对象中如果存在writeReplace方法则直接基于传入的实例反射调用此方法得到的返回值类型作为序列化的目标类型对于Lambda表达式就是SerializedLambda类型反序列化的过程刚好是逆转的过程调用的方法为readResolve刚好前面提到SerializedLambda也存在同名的私有方法Lambda表达式的实现类型是VM生成的模板类从结果上观察序列化前的实例和反序列化后得到的实例属于不同的模板类对于前一小节的例子某次运行的结果中序列化前的模板类为club.throwable.lambda.SerializedLambdaApp$$Lambda$14/0x0000000800065840反序列化后的模板类为club.throwable.lambda.SerializedLambdaApp$$Lambda$26/0x00000008000a4040 ❝ ObjectStreamClass是序列化和反序列化实现的类描述符关于对象序列化和反序列化的类描述信息可以从这个类里面的成员属性找到例如这里提到的writeReplace和readResolve方法 ❞ 图形化的过程如下 获取SerializedLambda的方式 通过前面的分析得知有两种方式可以获取Lambda表达式的SerializedLambda实例 方式一基于Lambda表达式实例和Lambda表达式的模板类反射调用writeReplace方法得到的返回值就是SerializedLambda实例方式二基于序列化和反序列化的方式获取SerializedLambda实例 基于这两种方式可以分别编写例子例如反射方式如下 // 反射方式
public class ReflectionSolution {FunctionalInterfacepublic interface CustomerFunctionS, T extends Serializable {T convert(S source);}public static void main(String[] args) throws Exception {CustomerFunctionString, Long function Long::parseLong;SerializedLambda serializedLambda getSerializedLambda(function);System.out.println(serializedLambda.getCapturingClass());}public static SerializedLambda getSerializedLambda(Serializable serializable) throws Exception {Method writeReplaceMethod serializable.getClass().getDeclaredMethod(writeReplace);writeReplaceMethod.setAccessible(true);return (SerializedLambda) writeReplaceMethod.invoke(serializable);}
} 序列化和反序列方式会稍微复杂因为ObjectInputStream.readObject()方法会最终回调SerializedLambda.readResolve()方法导致返回的结果是一个新模板类承载的Lambda表达式实例所以这里需要想办法中断这个调用提前返回结果方案是构造一个和SerializedLambda相似但是不存在readResolve()方法的「影子类型」 package cn.vlts;
import java.io.Serializable;/*** 这里注意一定要和java.lang.invoke.SerializedLambda同名,可以不同包名,这是为了欺骗ObjectStreamClass中有个神奇的类名称判断classNamesEqual()方法*/
SuppressWarnings(ALL)
public class SerializedLambda implements Serializable {private static final long serialVersionUID 8025925345765570181L;private Class? capturingClass;private String functionalInterfaceClass;private String functionalInterfaceMethodName;private String functionalInterfaceMethodSignature;private String implClass;private String implMethodName;private String implMethodSignature;private int implMethodKind;private String instantiatedMethodType;private Object[] capturedArgs;public String getCapturingClass() {return capturingClass.getName().replace(., /);}public String getFunctionalInterfaceClass() {return functionalInterfaceClass;}public String getFunctionalInterfaceMethodName() {return functionalInterfaceMethodName;}public String getFunctionalInterfaceMethodSignature() {return functionalInterfaceMethodSignature;}public String getImplClass() {return implClass;}public String getImplMethodName() {return implMethodName;}public String getImplMethodSignature() {return implMethodSignature;}public int getImplMethodKind() {return implMethodKind;}public final String getInstantiatedMethodType() {return instantiatedMethodType;}public int getCapturedArgCount() {return capturedArgs.length;}public Object getCapturedArg(int i) {return capturedArgs[i];}
}public class SerializationSolution {FunctionalInterfacepublic interface CustomerFunctionS, T extends Serializable {T convert(S source);}public static void main(String[] args) throws Exception {CustomerFunctionString, Long function Long::parseLong;cn.vlts.SerializedLambda serializedLambda getSerializedLambda(function);System.out.println(serializedLambda.getCapturingClass());}private static cn.vlts.SerializedLambda getSerializedLambda(Serializable serializable) throws Exception {try (ByteArrayOutputStream baos new ByteArrayOutputStream();ObjectOutputStream oos new ObjectOutputStream(baos)) {oos.writeObject(serializable);oos.flush();try (ObjectInputStream ois new ObjectInputStream(new ByteArrayInputStream(baos.toByteArray())) {Overrideprotected Class? resolveClass(ObjectStreamClass desc) throws IOException, ClassNotFoundException {Class? klass super.resolveClass(desc);return klass java.lang.invoke.SerializedLambda.class ? cn.vlts.SerializedLambda.class : klass;}}) {return (cn.vlts.SerializedLambda) ois.readObject();}}}
} 被遗忘的$deserializeLambda$方法 前文提到Lambda表达式实例反序列化的时候会调用java.lang.invoke.SerializedLambda.readResolve()方法神奇的是此方法源码如下 private Object readResolve() throws ReflectiveOperationException {try {Method deserialize AccessController.doPrivileged(new PrivilegedExceptionAction() {Overridepublic Method run() throws Exception {Method m capturingClass.getDeclaredMethod($deserializeLambda$, SerializedLambda.class);m.setAccessible(true);return m;}});return deserialize.invoke(null, this);}catch (PrivilegedActionException e) {Exception cause e.getException();if (cause instanceof ReflectiveOperationException)throw (ReflectiveOperationException) cause;else if (cause instanceof RuntimeException)throw (RuntimeException) cause;elsethrow new RuntimeException(Exception in SerializedLambda.readResolve, e);}
} 看起来就是捕获类中存在一个这样的静态方法 class CapturingClass {private static Object $deserializeLambda$(SerializedLambda serializedLambda){return [serializedLambda] Lambda表达式实例;}
} 可以尝试检索捕获类中的方法列表 public class CapturingClassApp {FunctionalInterfacepublic interface CustomRunnable extends Serializable {void run();}public static void main(String[] args) throws Exception {invoke(() - {});}private static void invoke(CustomRunnable customRunnable) throws Exception {Method writeReplaceMethod customRunnable.getClass().getDeclaredMethod(writeReplace);writeReplaceMethod.setAccessible(true);java.lang.invoke.SerializedLambda serializedLambda (java.lang.invoke.SerializedLambda)writeReplaceMethod.invoke(customRunnable);Class? capturingClass Class.forName(serializedLambda.getCapturingClass().replace(/, .));ReflectionUtils.doWithMethods(capturingClass, method - {System.out.printf(方法名:%s,修饰符:%s,方法参数列表:%s,方法返回值类型:%s\n, method.getName(),Modifier.toString(method.getModifiers()),Arrays.toString(method.getParameterTypes()),method.getReturnType().getName());},method - Objects.equals(method.getName(), $deserializeLambda$));}
}// 执行结果
方法名:$deserializeLambda$,修饰符:private static,方法参数列表:[class java.lang.invoke.SerializedLambda],方法返回值类型:java.lang.Object 果真是存在一个和之前提到的java.lang.invoke.SerializedLambda注释描述一致的捕获类的SerializedLambda实例转化为Lambda表达式实例的方法因为搜索多处地方都没发现此方法的踪迹猜测$deserializeLambda$是方法由VM生成并且只能通过反射的方法调用算是一个隐藏得比较深的技巧。 小结 JDK中的Lambda表达式功能已经发布很多年了想不到这么多年后的今天才弄清楚其序列化和反序列化方式虽然这不是一个复杂的问题但算是最近一段时间看到的比较有意思的一个知识点。 参考资料 JDK11源码Mybatis-Plus相关源码 推荐 主流Java进阶技术学习资料分享 PS因为公众号平台更改了推送规则如果不想错过内容记得读完点一下“在看”加个“星标”这样每次新文章推送才会第一时间出现在你的订阅列表里。点“在看”支持我们吧