java騷操作之通過lambda表達(dá)式獲取get方法引用的屬性

介紹

  • 在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);
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容