Java8中如何通过⽅法引⽤获取属性名详解
前⾔
在我们开发过程中常常有⼀个需求,就是要知道实体类中Getter⽅法对应的属性名称(Field Name),例如实体类属性到数据库字段的映射,我们常常是硬编码指定属性名,这种硬编码有两个缺点。
1、编码效率低:因为要硬编码写属性名,很可能写错,需要⾮常⼩⼼,时间浪费在了不必要的检查上。
2、容易让开发⼈员踩坑:例如有⼀天发现实体类中Field Name定义的不够明确,希望换⼀个Field Name,那么代码所有硬编码的Field Name都要跟着变更,对于未并更的地⽅,是⽆法在编译期发现的。只要有未变更的地⽅都可能导致bug的出现。
⽽使⽤了⽅法引⽤后,如果Field Name变更及其对应的Getter/Setter⽅法变更,编译器便可以实时的帮助我们检查变更的代码,在编译器给出错误信息。
那么如何通过⽅法引⽤获取Getter⽅法对应的Field Name呢?
Java8中给我们提供了实现⽅式,⾸先要做的就是定义⼀个可序列化的函数式接⼝(实现Serializable),实现如下:
/**
* Created by bruce on 2020/4/10 14:16
*/
@FunctionalInterface
public interface SerializableFunction<T, R> extends Function<T, R>, Serializable {
}
⽽在使⽤时,我们需要传递Getter⽅法引⽤
//⽅法引⽤
SerializableFunction<People, String> getName1 = People::getName;
Field field = Field(getName1);
下⾯看具体怎么解析这个SerializableFunction,完整实现如下ReflectionUtil
public class ReflectionUtil {
private static Map<SerializableFunction<?, ?>, Field> cache = new ConcurrentHashMap<>();
public static <T, R> String getFieldName(SerializableFunction<T, R> function) {
Field field = Field(function);
Name();
}
public static Field getField(SerializableFunction<?, ?> function) {
return cacheputeIfAbsent(function, ReflectionUtil::findField);
}
public static Field findField(SerializableFunction<?, ?> function) {
Field field = null;
String fieldName = null;
try {
// 第1步获取SerializedLambda
Method method = Class().getDeclaredMethod("writeReplace");
method.setAccessible(Boolean.TRUE);
SerializedLambda serializedLambda = (SerializedLambda) method.invoke(function);
// 第2步 implMethodName 即为Field对应的Getter⽅法名
String implMethodName = ImplMethodName();
if (implMethodName.startsWith("get") && implMethodName.length() > 3) {
fieldName = Introspector.decapitalize(implMethodName.substring(3));
} else if (implMethodName.startsWith("is") && implMethodName.length() > 2) {
fieldName = Introspector.decapitalize(implMethodName.substring(2));
jdk怎么使用} else if (implMethodName.startsWith("lambda$")) {
throw new IllegalArgumentException("SerializableFunction不能传递lambda表达式,只能使⽤⽅法引⽤");
} else {
throw new IllegalArgumentException(implMethodName + "不是Getter⽅法引⽤");
}
// 第3步获取的Class是字符串,并且包名是“/”分割,需要替换成“.”,才能获取到对应的Class对象
String declaredClass = ImplClass().replace("/", ".");
Class<?> aClass = Class.forName(declaredClass, false, DefaultClassLoader());
// 第4步 Spring 中的反射⼯具类获取Class中定义的Field
field = ReflectionUtils.findField(aClass, fieldName);
} catch (Exception e) {
e.printStackTrace();
}
// 第5步如果没有到对应的字段应该抛出异常
if (field != null) {
return field;
}
throw new NoSuchFieldError(fieldName);
}
}
该类中主要有如下三个⽅法
String getFieldName(SerializableFunction<T, R> function)获取Field的字符串name
Field getField(SerializableFunction<?, ?> function)从缓存中查询⽅法引⽤对应的Field,如果没有则通过findField(SerializableFunction<?, ?> function)⽅法反射获取Field findField(SerializableFunction<?, ?> function)反射获取⽅法应⽤对应的Field
实现原理
1、⾸先我们看最后⼀个⽅法Field findField(SerializableFunction<?, ?> function),该⽅法中第⼀步是通过SerializableFunction对象获取Class,即传递的⽅法引⽤,然后反射获取writeReplace()⽅法,并调⽤该⽅法获取导SerializedLambda对象。
2、SerializedLambda是Java8中提供,主要就是⽤于封装⽅法引⽤所对应的信息,主要的就是⽅法名、定义⽅法的类名、创建⽅法引⽤所在类。
3、拿到这些信息后,便可以通过反射获取对应的Field。
4、⽽在⽅法Field getField(SerializableFunction<?, ?> function)中对获取到的Field进⾏缓存,避免每次都反射获取,造成资源浪费。
除此之外似乎还有⼀些值得思考的问题
writeReplace()⽅法是哪来的呢?
⾸先简单了解⼀下java.io.Serializable接⼝,该接⼝很常见,我们在持久化⼀个对象或者在RPC框架之间通信使⽤JDK序列化时都会让传输的实体类实现该接⼝,该接⼝是⼀个标记接⼝没有定义任何⽅法,但是该接⼝⽂档中有这么⼀段描述:
Serializable classes that need to designate an alternative object to be used when writing an object to the stream should implement this special method with the exact signature:
ANY-ACCESS-MODIFIER Object writeReplace() throws ObjectStreamException;
This writeReplace method is invoked by serialization if the method exists and it would be accessible from a method defined within the class of the object being serialized. Thus, the method can have private, protected and package-private access. Subclass access to this method follows java accessibility rules.
概要意思就是说,如果想在序列化时改变序列化的对象,可以通过在实体类中定义任意访问权限的Object writeReplace()来改变默认序列化的对象。
那么我们的定义的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,代码如下:
public static Field getField(SerializableFunction<?, ?> function) {
//使⽤SerializableFunction的Class作为缓存key,导致每次都调⽤Class()
return Class(), ReflectionUtil::findField);
}
但是个⼈建议采⽤SerializableFunction对象,因为⽆论⽅法被调⽤多少次,⽅法代码块内的⽅法引⽤对象始终是同⼀个,如果采⽤其Class做为缓存key,每次查询缓存时都需要调⽤native⽅法Class()获取其Class,也是⼀种资源损耗。
总结:Java如何通过⽅法引⽤获取属性名实现及思考⾄此结束。直接使⽤ReflectionUtil即可
到此这篇关于Java8中如何通过⽅法引⽤获取属性名的⽂章就介绍到这了,更多相关Java8通过⽅法引⽤获取属性名内容请搜索以前的⽂章或继续浏览下⾯的相关⽂章希望⼤家以后多多⽀持!
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论