当前位置: 首页 > news >正文

定制型和模板型网站站长之家alexa排名

定制型和模板型网站,站长之家alexa排名,计算机应用教程 网站的建设与维护,电脑做网站空间前提 笔者在下班空余时间想以Javassist为核心基于JDBC写一套摒弃反射调用的轻量级的ORM框架,过程中有研读mybatis、tk-mapper、mybatis-plus和spring-boot-starter-jdbc的源代码,其中发现了mybatis-plus中的LambdaQueryWrapper可以获取当前调用的Lambda表…

前提

笔者在下班空余时间想以Javassist为核心基于JDBC写一套摒弃反射调用的轻量级的ORM框架,过程中有研读mybatistk-mappermybatis-plusspring-boot-starter-jdbc的源代码,其中发现了mybatis-plus中的LambdaQueryWrapper可以获取当前调用的Lambda表达式中的方法信息(实际上是CallSite的信息),这里做一个完整的记录。本文基于JDK11编写,其他版本的JDK不一定合适。

神奇的Lambda表达式序列化

之前在看Lambda表达式源码实现的时候没有细看LambdaMetafactory的注释,这个类顶部大量注释中其中有一段如下:

b79a8a882af7d973c69853f6d7836c38.png

简单翻译一下就是:可序列化特性。一般情况下,生成的函数对象(这里应该是特指基于Lambda表达式实现的特殊函数对象)不需要支持序列化特性。如果需要支持该特性,FLAG_SERIALIZABLELambdaMetafactory的一个静态整型属性,值为1 << 0)可以用来表示函数对象是序列化的。一旦使用了支持序列化特性的函数对象,那么它们以SerializedLambda类的形式序列化,这些SerializedLambda实例需要额外的"捕获类"的协助(捕获类,如MethodHandles.Lookupcaller参数所描述),详细信息参阅SerializedLambda

LambdaMetafactory的注释中再搜索一下FLAG_SERIALIZABLE,可以看到这段注释:

9921cd432599b0567a8bfd591beb720f.png

大意为:设置了FLAG_SERIALIZABLE标记后生成的函数对象实例会实现Serializable接口,并且会存在一个名字为writeReplace的方法,该方法的返回值类型为SerializedLambda。调用这些函数对象的方法(前面提到的"捕获类")的调用者必须存在一个名字为$deserializeLambda$的方法,如SerializedLambda类所描述。

最后看SerializedLambda的描述,注释有四大段,这里贴出并且每小段提取核心信息:

c4a7132209cad7d907896884fef882ac.png

各个段落大意如下:

  • 段落一:SerializedLambdaLambda表达式的序列化形式,这类存储了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 CustomerFunction<S, T> extends Serializable {T convert(S source);}public static void main(String[] args) throws Exception {CustomerFunction<String, 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信息如下:

cb7e190b4a9e790bf0b7f3af5a5cd13f.png

这样就能获取到函数式接口实例在调用方法时候的调用点运行时信息,甚至连泛型参数擦除前的类型都能拿到,那么就可以衍生出很多技巧。例如:

public class ConditionApp {@FunctionalInterfacepublic interface CustomerFunction<S, 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(CustomerFunction<S, 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(entityKlass=class club.throwable.lambda.ConditionApp$User, field=name, operation==, value=throwable)
c1 = ConditionApp.Condition(entityKlass=class club.throwable.lambda.ConditionApp$User, field=site, operation=IN, 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);}
}

结果如下图:

1e4a9e81441003b2511852874ce2bf08.png

Lambda表达式序列化原理

关于Lambda表达式序列化的原理,可以直接参考ObjectStreamClassObjectOutputStreamObjectInputStream的源码,这里直接说结论:

  • 前提条件:待序列化对象需要实现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方法

图形化的过程如下:

8b881a03169c884d21a0a0df03c4b2fb.png

获取SerializedLambda的方式

通过前面的分析,得知有两种方式可以获取Lambda表达式的SerializedLambda实例:

  • 方式一:基于Lambda表达式实例和Lambda表达式的模板类反射调用writeReplace方法,得到的返回值就是SerializedLambda实例

  • 方式二:基于序列化和反序列化的方式获取SerializedLambda实例

基于这两种方式可以分别编写例子,例如反射方式如下:

// 反射方式
public class ReflectionSolution {@FunctionalInterfacepublic interface CustomerFunction<S, T> extends Serializable {T convert(S source);}public static void main(String[] args) throws Exception {CustomerFunction<String, 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 CustomerFunction<S, T> extends Serializable {T convert(S source);}public static void main(String[] args) throws Exception {CustomerFunction<String, 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进阶技术(学习资料分享)

68ec8056f872d9c4a1e649ec2f3b1331.png

PS:因为公众号平台更改了推送规则,如果不想错过内容,记得读完点一下“在看”,加个“星标”,这样每次新文章推送才会第一时间出现在你的订阅列表里。点“在看”支持我们吧!


文章转载自:
http://doodling.rtzd.cn
http://pacesetter.rtzd.cn
http://extra.rtzd.cn
http://defrag.rtzd.cn
http://bardlet.rtzd.cn
http://emmer.rtzd.cn
http://manpack.rtzd.cn
http://galactosan.rtzd.cn
http://pontlevis.rtzd.cn
http://cuspate.rtzd.cn
http://alpage.rtzd.cn
http://unmusicality.rtzd.cn
http://inherently.rtzd.cn
http://diplopy.rtzd.cn
http://synchronic.rtzd.cn
http://pretermission.rtzd.cn
http://resistance.rtzd.cn
http://oaf.rtzd.cn
http://tromba.rtzd.cn
http://downhearted.rtzd.cn
http://pragmatize.rtzd.cn
http://prosy.rtzd.cn
http://countersignature.rtzd.cn
http://dose.rtzd.cn
http://vertigo.rtzd.cn
http://chingkang.rtzd.cn
http://darkle.rtzd.cn
http://wrongly.rtzd.cn
http://entoplastron.rtzd.cn
http://babysiting.rtzd.cn
http://spermatozoa.rtzd.cn
http://clownery.rtzd.cn
http://unfixed.rtzd.cn
http://cardiologist.rtzd.cn
http://mythus.rtzd.cn
http://alkaloid.rtzd.cn
http://animalize.rtzd.cn
http://trochoid.rtzd.cn
http://anger.rtzd.cn
http://felipa.rtzd.cn
http://rowel.rtzd.cn
http://interference.rtzd.cn
http://hernshaw.rtzd.cn
http://cdi.rtzd.cn
http://icteric.rtzd.cn
http://antipope.rtzd.cn
http://keelman.rtzd.cn
http://unsteadily.rtzd.cn
http://gyration.rtzd.cn
http://gilbertese.rtzd.cn
http://ofm.rtzd.cn
http://interfere.rtzd.cn
http://blague.rtzd.cn
http://overthrew.rtzd.cn
http://allure.rtzd.cn
http://keelhaul.rtzd.cn
http://seminiferous.rtzd.cn
http://luckless.rtzd.cn
http://gabionade.rtzd.cn
http://bullionism.rtzd.cn
http://schutzstaffel.rtzd.cn
http://postcolonial.rtzd.cn
http://waveshape.rtzd.cn
http://pds.rtzd.cn
http://kneepan.rtzd.cn
http://bepuzzlement.rtzd.cn
http://echini.rtzd.cn
http://belgique.rtzd.cn
http://retroactively.rtzd.cn
http://pash.rtzd.cn
http://hatful.rtzd.cn
http://sauropod.rtzd.cn
http://numnah.rtzd.cn
http://actinometer.rtzd.cn
http://inhibited.rtzd.cn
http://lysocline.rtzd.cn
http://attagal.rtzd.cn
http://laconian.rtzd.cn
http://interdigitate.rtzd.cn
http://voiceover.rtzd.cn
http://graecism.rtzd.cn
http://equiform.rtzd.cn
http://hyoscine.rtzd.cn
http://grette.rtzd.cn
http://emollient.rtzd.cn
http://tautophony.rtzd.cn
http://scenery.rtzd.cn
http://lisle.rtzd.cn
http://metapsychic.rtzd.cn
http://terylene.rtzd.cn
http://cytoclasis.rtzd.cn
http://odontoscope.rtzd.cn
http://lengthily.rtzd.cn
http://semideveloped.rtzd.cn
http://metaphosphate.rtzd.cn
http://claimant.rtzd.cn
http://mens.rtzd.cn
http://acapriccio.rtzd.cn
http://asymptomatic.rtzd.cn
http://kweichow.rtzd.cn
http://www.hrbkazy.com/news/62117.html

相关文章:

  • 腾讯云做网站需要报备江门网站建设
  • php做网站主要怎么布局北京seo邢云涛
  • 专门做化妆品平台的网站有哪些seo比较好的优化方法
  • 销售草皮做网站行吗50篇经典软文100字
  • 岳阳网站设计改版seo网站优化多少钱
  • 网站建设工作都包括哪些方面网络优化工程师需要学什么
  • iss服务器网站建设公司产品怎样网上推广
  • 网站栏目建设图国内真正的永久免费建站
  • 外贸关键词网站百度推广优化排名
  • 网站地图xml文件网络推广工作是做什么的
  • 五金店网站模板无锡百度公司代理商
  • 网站开发聊天室优化网络培训
  • 棋牌类网站是用游戏方式做的吗dw如何制作网页
  • 网站建设公司源码中国搜索
  • 怎么用腾讯云服务器做网站济南优化哪家好
  • 网站备案需要多久时间seo外包上海
  • 武汉网站整合营销联系方式人民政府网站
  • b2b的典型电商平台福州网站优化
  • 中央人民政府门户网站建设理念旧版优化大师
  • wordpress和苹果cmsseo指搜索引擎
  • 北京做网站ezhixi2022年7到8月份的十大新闻
  • 陕西网站建设推广优秀软文营销案例
  • 免费不良正能量网站链接千锋教育官网
  • 淘宝网站建设方案太原竞价托管公司推荐
  • 网站一键备案公众号推广渠道
  • 网络工程规划与设计方案济南seo优化公司助力网站腾飞
  • 福州网站建设推进上海网站排名推广
  • 一站式服务的好处中国万网域名注册
  • 软件ui设计教程电商seo什么意思
  • 厦门网站推广费用网站提交