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

定制型和模板型网站百度客服怎么转人工

定制型和模板型网站,百度客服怎么转人工,织梦网站迁移,全球域名最贵的100个域名前提 笔者在下班空余时间想以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://www.yidumall.com/news/62656.html

相关文章:

  • 爱站官网百度指数查询移民
  • 宝坻做网站国家职业技能培训官网
  • 网站内容包括百度一下百度
  • 个人网站做电商网站推广常用的方法
  • 山东省住房和城乡建设厅二建查询广州seo优化
  • 超酷的网站设计网页制作
  • 广州建委网站全国互联网营销大赛官网
  • 专业建设网站制作深圳搜狗seo
  • 附近网站建设公司营销策划案
  • wordpress会员制网站英文站友情链接去哪里查
  • 承接设计网站建设班级优化大师使用指南
  • 泉州厦门网站建设公司西安网站快速排名提升
  • 网站建设的课程设计智慧软文发稿平台官网
  • 教人做甜点的网站外包公司是正规公司吗
  • 北京网站建设网站开发福建seo外包
  • 长宁专业网站制作公司百度广告投放技巧
  • 华为云服务器怎么做网站qianhu微建站
  • 深圳送花网站哪个好b站推广在哪里
  • 怎样申请建网站淘宝运营培训班去哪里学
  • 全自动建站系统中国企业培训网
  • 网站主体负责人和网站负责人软文写作是什么意思
  • 公众平台申请福州seo网站排名
  • gis做图网站品牌推广与传播
  • 银川网站建设公司网站模板之家
  • 利用建站系统wordpress建设网站如何查询百度收录
  • 电子商务网站模板 下载微信小程序开发多少钱
  • 公司网站建设计划书响应式网站 乐云seo品牌
  • 做国际贸易哪个网站比较好seo搜索优化公司排名
  • php网站开发 在本地修改 服务器源文件同步网络推广营销
  • 贵阳网站建设推广公司百度推广优化怎么做的