Java8中如何通过方法引用获取属性名详解
前言
在我们开发过程中常常有一个需求,就是要知道实体类中Getter方法对应的属性名称(FieldName),例如实体类属性到数据库字段的映射,我们常常是硬编码指定属性名,这种硬编码有两个缺点。
1、编码效率低:因为要硬编码写属性名,很可能写错,需要非常小心,时间浪费在了不必要的检查上。
2、容易让开发人员踩坑:例如有一天发现实体类中FieldName定义的不够明确,希望换一个FieldName,那么代码所有硬编码的FieldName都要跟着变更,对于未并更的地方,是无法在编译期发现的。只要有未变更的地方都可能导致bug的出现。
而使用了方法引用后,如果FieldName变更及其对应的Getter/Setter方法变更,编译器便可以实时的帮助我们检查变更的代码,在编译器给出错误信息。
那么如何通过方法引用获取Getter方法对应的FieldName呢?
Java8中给我们提供了实现方式,首先要做的就是定义一个可序列化的函数式接口(实现Serializable),实现如下:
/** *Createdbybruceon2020/4/1014:16 */ @FunctionalInterface publicinterfaceSerializableFunctionextendsFunction ,Serializable{ }
而在使用时,我们需要传递Getter方法引用
//方法引用 SerializableFunctiongetName1=People::getName; Fieldfield=ReflectionUtil.getField(getName1);
下面看具体怎么解析这个SerializableFunction,完整实现如下ReflectionUtil
publicclassReflectionUtil{ privatestaticMap,Field>cache=newConcurrentHashMap<>(); publicstatic StringgetFieldName(SerializableFunction function){ Fieldfield=ReflectionUtil.getField(function); returnfield.getName(); } publicstaticFieldgetField(SerializableFunction,?>function){ returncache.computeIfAbsent(function,ReflectionUtil::findField); } publicstaticFieldfindField(SerializableFunction,?>function){ Fieldfield=null; StringfieldName=null; try{ //第1步获取SerializedLambda Methodmethod=function.getClass().getDeclaredMethod("writeReplace"); method.setAccessible(Boolean.TRUE); SerializedLambdaserializedLambda=(SerializedLambda)method.invoke(function); //第2步implMethodName即为Field对应的Getter方法名 StringimplMethodName=serializedLambda.getImplMethodName(); if(implMethodName.startsWith("get")&&implMethodName.length()>3){ fieldName=Introspector.decapitalize(implMethodName.substring(3)); }elseif(implMethodName.startsWith("is")&&implMethodName.length()>2){ fieldName=Introspector.decapitalize(implMethodName.substring(2)); }elseif(implMethodName.startsWith("lambda$")){ thrownewIllegalArgumentException("SerializableFunction不能传递lambda表达式,只能使用方法引用"); }else{ thrownewIllegalArgumentException(implMethodName+"不是Getter方法引用"); } //第3步获取的Class是字符串,并且包名是“/”分割,需要替换成“.”,才能获取到对应的Class对象 StringdeclaredClass=serializedLambda.getImplClass().replace("/","."); Class>aClass=Class.forName(declaredClass,false,ClassUtils.getDefaultClassLoader()); //第4步Spring中的反射工具类获取Class中定义的Field field=ReflectionUtils.findField(aClass,fieldName); }catch(Exceptione){ e.printStackTrace(); } //第5步如果没有找到对应的字段应该抛出异常 if(field!=null){ returnfield; } thrownewNoSuchFieldError(fieldName); } }
该类中主要有如下三个方法
- StringgetFieldName(SerializableFunction
function) 获取Field的字符串name - FieldgetField(SerializableFunction,?>function)从缓存中查询方法引用对应的Field,如果没有则通过findField(SerializableFunction,?>function)方法反射获取
- FieldfindField(SerializableFunction,?>function) 反射获取方法应用对应的Field
实现原理
1、首先我们看最后一个方法FieldfindField(SerializableFunction,?>function),该方法中第一步是通过SerializableFunction对象获取Class,即传递的方法引用,然后反射获取writeReplace()方法,并调用该方法获取导SerializedLambda对象。
2、SerializedLambda是Java8中提供,主要就是用于封装方法引用所对应的信息,主要的就是方法名、定义方法的类名、创建方法引用所在类。
3、拿到这些信息后,便可以通过反射获取对应的Field。
4、而在方法FieldgetField(SerializableFunction,?>function)中对获取到的Field进行缓存,避免每次都反射获取,造成资源浪费。
除此之外似乎还有一些值得思考的问题
writeReplace()方法是哪来的呢?
首先简单了解一下java.io.Serializable接口,该接口很常见,我们在持久化一个对象或者在RPC框架之间通信使用JDK序列化时都会让传输的实体类实现该接口,该接口是一个标记接口没有定义任何方法,但是该接口文档中有这么一段描述:
Serializableclassesthatneedtodesignateanalternativeobjecttobeusedwhenwritinganobjecttothestreamshouldimplementthisspecialmethodwiththeexactsignature:
ANY-ACCESS-MODIFIERObjectwriteReplace()throwsObjectStreamException;
ThiswriteReplacemethodisinvokedbyserializationifthemethodexistsanditwouldbeaccessiblefromamethoddefinedwithintheclassoftheobjectbeingserialized.Thus,themethodcanhaveprivate,protectedandpackage-privateaccess.Subclassaccesstothismethodfollowsjavaaccessibilityrules.
概要意思就是说,如果想在序列化时改变序列化的对象,可以通过在实体类中定义任意访问权限的ObjectwriteReplace()来改变默认序列化的对象。
那么我们的定义的SerializableFunction中并没有定义writeReplace()方法,这个方法是哪来的呢?
代码中SerializableFunction,Function只是一个接口,但是其在最后必定也是一个实现类的实例对象,而方法引用其实是在运行时动态创建的,当代码执行到方法引用时,如People::getName,最后会经过
java.lang.invoke.LambdaMetafactory
java.lang.invoke.InnerClassLambdaMetafactory
去动态的创建实现类。而在动态创建实现类时则会判断函数式接口是否实现了Serializable,如果实现了,则添加writeReplace()
值得注意的是,代码中多次编写的同一个方法引用,他们创建的是不同Function实现类,即他们的Function实例对象也并不是同一个
我们可以通过如下属性配置将动态生成的Class保存到磁盘上
java8中可以通过硬编码
System.setProperty("jdk.internal.lambda.dumpProxyClasses",".");
jdk11中只能使用jvm参数指定,硬编码无效,原因是模块化导致的
-Djdk.internal.lambda.dumpProxyClasses=.
示例代码如下:
动态生成的Class如下:
一个方法引用创建一个实现类,他们是不同的对象,那么ReflectionUtil中将SerializableFunction最为缓存key还有意义吗?
答案是肯定有意义的!!!因为同一方法中的定义的Function只会动态的创建一次实现类并只实例化一次,当该方法被多次调用时即可走缓存中查询该方法引用对应的Field
这里的缓存Key应该选用SerializableFunction#Class还是SerializableFunction实例对象好呢?
看到有些实现使用SerializableFunction的Class作为缓存key,代码如下:
publicstaticFieldgetField(SerializableFunction,?>function){ //使用SerializableFunction的Class作为缓存key,导致每次都调用function.getClass() returncache.computeIfAbsent(function.getClass(),ReflectionUtil::findField); }
但是个人建议采用SerializableFunction对象,因为无论方法被调用多少次,方法代码块内的方法引用对象始终是同一个,如果采用其Class做为缓存key,每次查询缓存时都需要调用native方法function.getClass()获取其Class,也是一种资源损耗。
总结:Java如何通过方法引用获取属性名实现及思考至此结束。直接使用ReflectionUtil即可
到此这篇关于Java8中如何通过方法引用获取属性名的文章就介绍到这了,更多相关Java8通过方法引用获取属性名内容请搜索毛票票以前的文章或继续浏览下面的相关文章希望大家以后多多支持毛票票!
声明:本文内容来源于网络,版权归原作者所有,内容由互联网用户自发贡献自行上传,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任。如果您发现有涉嫌版权的内容,欢迎发送邮件至:czq8825#qq.com(发邮件时,请将#更换为@)进行举报,并提供相关证据,一经查实,本站将立刻删除涉嫌侵权内容。