介紹
- 在java中,利用反射的時(shí)候,有時(shí)候會(huì)傳過去屬性的名稱字符串,然后根據(jù)字符串來獲取字段Field對(duì)象,但是這種不是很優(yōu)雅,需要傳字符串過去。比如在mybatis-plus 2的版本中,構(gòu)建查詢wapper時(shí),查詢某個(gè)字段,需要將字段對(duì)應(yīng)的列名稱的字符串,傳遞過去,就看著十分不優(yōu)雅。
- 但是在mybatis-plus 3中,就可以用lambda將字段的get方法傳過去,它會(huì)自動(dòng)知道查詢的是哪個(gè)字段,這就有點(diǎn)神奇了。最終查詢肯定是需要知道列的名稱,而列名稱是在屬性上通過@TableField注解來定義的,它是怎么通過方法的lambda引用,來讀取字段屬性Field的了?
- 經(jīng)過搜索,發(fā)現(xiàn)了 SerializedLambda 這個(gè)類,原來是基于這個(gè)類來實(shí)現(xiàn),通過方法引用,來獲取引用的方法的一些信息,包括實(shí)現(xiàn)方法的名稱,和包含實(shí)現(xiàn)方法的類的名稱等,下面來看看。
SerializedLambda
在java 8中,如果函數(shù)式接口實(shí)現(xiàn)了Serializable,那這個(gè)函數(shù)式接口用lambda表達(dá)式實(shí)現(xiàn)時(shí),運(yùn)行時(shí)會(huì)生成一個(gè)這個(gè)函數(shù)式接口的實(shí)現(xiàn)類,會(huì)有一個(gè)私有的“writeReplace”方法,返回 SerializedLambda 對(duì)象。而 SerializedLambda 這個(gè)類,就是來描述lambda表達(dá)式的,包含了函數(shù)式接口和實(shí)現(xiàn)方法等的信息。
注: Lambda表達(dá)式的實(shí)現(xiàn)原理,jvm中是利用invokedynamic指令(jdk7新增的),來實(shí)現(xiàn)調(diào)用動(dòng)態(tài)方法,在運(yùn)行期間通過生成字節(jié)碼技術(shù),生成一個(gè)內(nèi)部類來實(shí)現(xiàn)接口,再來調(diào)用待執(zhí)行的一個(gè)內(nèi)部靜態(tài)方法(lambda表達(dá)式中真正待執(zhí)行的代碼),從而執(zhí)行真正的代碼邏輯。Lambda的實(shí)現(xiàn)原理,本文不再詳細(xì)說明。
SerializedLambda 在本文中用到的方法:
- serializedLambda.getImplMethodName(),獲取實(shí)現(xiàn)方法的名稱。如果是直接傳遞的方法引用,則獲取的就是引用的方法的名稱。
- serializedLambda.getImplClass(),獲取實(shí)現(xiàn)方法所在的類的全限定名,用過“/”連接的字符串。如果是直接傳遞的方法引用,就能獲取引用的方法所在類的名稱。
代碼實(shí)現(xiàn)
1、函數(shù)式接口
首先自定義一個(gè)函數(shù)式接口,實(shí)現(xiàn)Serializable,因?yàn)閖dk中自帶幾個(gè)函數(shù)式接口,沒有實(shí)現(xiàn)Serializable。
import java.io.Serializable;
import java.util.function.Function;
/**
* SFunction代替Function,獲取序列化能力
*/
@FunctionalInterface
public interface SFunction<T, R> extends Function<T, R>, Serializable {
}
2、獲取屬性工具類方法
首先利用反射,獲取SFunction接口的實(shí)現(xiàn)類中的 writeReplace 方法,然后用反射執(zhí)行該方法,得到 SerializedLambda 對(duì)象,再通過 SerializedLambda 對(duì)象,獲取引用的方法的名稱,根據(jù) java bean get 方法規(guī)范,獲取 get 方法對(duì)應(yīng)的屬性名稱。再獲取引用的方法所在的類,然后再通過反射,獲取該類中該屬性的Field對(duì)象。
import java.lang.invoke.SerializedLambda;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
/**
* bean屬性獲取工具類
*/
public class FieldUtil {
/**
* 將bean的屬性的get方法,作為lambda表達(dá)式傳入時(shí),獲取get方法對(duì)應(yīng)的屬性Field
*
* @param fn lambda表達(dá)式,bean的屬性的get方法
* @param <T> 泛型
* @return 屬性對(duì)象
*/
public static <T> Field getField(SFunction<T, ?> fn) {
// 從function取出序列化方法
Method writeReplaceMethod;
try {
writeReplaceMethod = fn.getClass().getDeclaredMethod("writeReplace");
} catch (NoSuchMethodException e) {
throw new RuntimeException(e);
}
// 從序列化方法取出序列化的lambda信息
boolean isAccessible = writeReplaceMethod.isAccessible();
writeReplaceMethod.setAccessible(true);
SerializedLambda serializedLambda;
try {
serializedLambda = (SerializedLambda) writeReplaceMethod.invoke(fn);
} catch (IllegalAccessException | InvocationTargetException e) {
throw new RuntimeException(e);
}
writeReplaceMethod.setAccessible(isAccessible);
// 從lambda信息取出method、field、class等
String implMethodName = serializedLambda.getImplMethodName();
// 確保方法是符合規(guī)范的get方法,boolean類型是is開頭
if (!implMethodName.startsWith("is") && !implMethodName.startsWith("get")) {
throw new RuntimeException("get方法名稱: " + implMethodName + ", 不符合java bean規(guī)范");
}
// get方法開頭為 is 或者 get,將方法名 去除is或者get,然后首字母小寫,就是屬性名
int prefixLen = implMethodName.startsWith("is") ? 2 : 3;
String fieldName = implMethodName.substring(prefixLen);
String firstChar = fieldName.substring(0, 1);
fieldName = fieldName.replaceFirst(firstChar, firstChar.toLowerCase());
Field field;
try {
field = Class.forName(serializedLambda.getImplClass().replace("/", ".")).getDeclaredField(fieldName);
} catch (ClassNotFoundException | NoSuchFieldException e) {
throw new RuntimeException(e);
}
return field;
}
}
3、測試
先對(duì)定義一個(gè)bean對(duì)象,Uesr,有個(gè)屬性name。
public class User {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
然后通過 User::getName,方法的引用,來獲取該字段屬性對(duì)象,從而就能獲取屬性的名稱、類型、擁有的注解等信息了,就能做很多的操作了,想象空間很大哦。
public static void main(String[] args) {
Field field = getField(User::getName);
System.out.println(field);
// 字段名稱
String fieldName = field.getName();
System.out.println(fieldName);
// 字段上面的注解
Annotation[] annotations = field.getAnnotations();
for (Annotation annotation : annotations) {
System.out.println(annotation);
}
// 字段的類型
Class<?> type = field.getType();
System.out.println(type);
}