<output id="qn6qe"></output>

    1. <output id="qn6qe"><tt id="qn6qe"></tt></output>
    2. <strike id="qn6qe"></strike>

      亚洲 日本 欧洲 欧美 视频,日韩中文字幕有码av,一本一道av中文字幕无码,国产线播放免费人成视频播放,人妻少妇偷人无码视频,日夜啪啪一区二区三区,国产尤物精品自在拍视频首页,久热这里只有精品12

      通過Lambda函數的方式獲取屬性名稱

      前言

      最近在使用mybatis-plus框架, 常常會使用lambda的方法引用獲取實體屬性, 避免出現大量的魔法值.

      public List<User> listBySex() {
        LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
        // lambda方法引用
        queryWrapper.eq(User::getSex, "男");
        return userServer.list(wrapper);
      }
      

      那么在我們平時的開發過程中, 常常需要用到java bean的屬性名, 直接寫死屬性名字符串的形式容易產生bug, 比如屬性名變化, 編譯時并不會報錯, 只有在運行時才會報錯該對象沒有指定的屬性名稱. 而lambda的方式不僅可以簡化代碼, 而且可以通過getter方法引用拿到屬性名, 避免潛在bug.

      期望的效果

      String userName = BeanUtils.getFieldName(User::getName);
      System.out.println(userName);
      // 輸出: name
      

      實現步驟

      1. 定義一個函數式接口, 用來接收lambda方法引用

        注意: 函數式接口必須繼承Serializable接口才能獲取方法信息

        @FunctionalInterface
        public interface SFunction<T> extends Serializable {
          Object apply(T t);
        }
        
      2. 定義一個工具類, 用來解析獲取屬性名稱

        import lombok.extern.slf4j.Slf4j;
        import org.springframework.util.ClassUtils;
        import org.springframework.util.ReflectionUtils;
        
        import java.beans.Introspector;
        import java.lang.invoke.SerializedLambda;
        import java.lang.reflect.Field;
        import java.lang.reflect.Method;
        import java.util.Map;
        import java.util.concurrent.ConcurrentHashMap;
        
        @Slf4j
        public class BeanUtils {
            private static final Map<SFunction<?>, Field> FUNCTION_CACHE = new ConcurrentHashMap<>();
         
            public static <T> String getFieldName(SFunction<T> function) {
                Field field = BeanUtils.getField(function);
                return field.getName();
            }
         
            public static <T> Field getField(SFunction<T> function) {
                return FUNCTION_CACHE.computeIfAbsent(function, BeanUtils::findField);
            }
         
            public static <T> Field findField(SFunction<T> function) {
                // 第1步 獲取SerializedLambda
                final SerializedLambda serializedLambda = getSerializedLambda(function);
                // 第2步 implMethodName 即為Field對應的Getter方法名
                final String implClass = serializedLambda.getImplClass();
                final String implMethodName = serializedLambda.getImplMethodName();
                final String fieldName = convertToFieldName(implMethodName);
                // 第3步  Spring 中的反射工具類獲取Class中定義的Field
                final Field field = getField(fieldName, serializedLambda);
        
                // 第4步 如果沒有找到對應的字段應該拋出異常
                if (field == null) {
                    throw new RuntimeException("No such class 「"+ implClass +"」 field 「" + fieldName + "」.");
                }
        
                return field;
            }
        
            static Field getField(String fieldName, SerializedLambda serializedLambda) {
                try {
                    // 獲取的Class是字符串,并且包名是“/”分割,需要替換成“.”,才能獲取到對應的Class對象
                    String declaredClass = serializedLambda.getImplClass().replace("/", ".");
                    Class<?>aClass = Class.forName(declaredClass, false, ClassUtils.getDefaultClassLoader());
                    return ReflectionUtils.findField(aClass, fieldName);
                }
                catch (ClassNotFoundException e) {
                    throw new RuntimeException("get class field exception.", e);
                }
            }
        
            static String convertToFieldName(String getterMethodName) {
                // 獲取方法名
                String prefix = null;
                if (getterMethodName.startsWith("get")) {
                    prefix = "get";
                }
                else if (getterMethodName.startsWith("is")) {
                    prefix = "is";
                }
        
                if (prefix == null) {
                    throw new IllegalArgumentException("invalid getter method: " + getterMethodName);
                }
        
                // 截取get/is之后的字符串并轉換首字母為小寫
                return Introspector.decapitalize(getterMethodName.replace(prefix, ""));
            }
        
            static <T> SerializedLambda getSerializedLambda(SFunction<T> function) {
                try {
                    Method method = function.getClass().getDeclaredMethod("writeReplace");
                    method.setAccessible(Boolean.TRUE);
                    return (SerializedLambda) method.invoke(function);
                }
                catch (Exception e) {
                    throw new RuntimeException("get SerializedLambda exception.", e);
                }
            }
        }
        

      測試

      public class Test {
          public static void main(String[] args) {
              SFunction<User> user = User::getName;
              final String fieldName = BeanUtils.getFieldName(user);
              System.out.println(fieldName);
          }
      
          @Data
          static class User {
              private String name;
      
              private int age;
          }
      }
      

      執行測試 輸出結果

      原理剖析

      為什么SFunction必須繼承Serializable

      首先簡單了解一下java.io.Serializable接口,該接口很常見,我們在持久化一個對象或者在RPC框架之間通信使用JDK序列化時都會讓傳輸的實體類實現該接口,該接口是一個標記接口沒有定義任何方法,但是該接口文檔中有這么一段描述:

      概要意思就是說,如果想在序列化時改變序列化的對象,可以通過在實體類中定義任意訪問權限的Object writeReplace()來改變默認序列化的對象。

      代碼中SFunction只是一個接口, 但是其在最后必定也是一個實現類的實例對象,而方法引用其實是在運行時動態創建的,當代碼執行到方法引用時,如User::getName,最后會經過

      java.lang.invoke.LambdaMetafactory
      java.lang.invoke.InnerClassLambdaMetafactory
      

      去動態的創建實現類。而在動態創建實現類時則會判斷函數式接口是否實現了Serializable,如果實現了,則添加writeReplace方法

      也就是說我們代碼BeanUtils#getSerializedLambda方法中反射調用的writeReplace方法是在生成函數式接口實現類時添加進去的.

      SFunction Class中的writeReplace方法

      從上文中我們得知 當SFunction繼承Serializable時, 底層在動態生成SFunction的實現類時添加了writeReplace方法, 那這個方法有什么用?

      首先 我們將動態生成的類保存到磁盤上看一下

      我們可以通過如下屬性配置將 動態生成的Class保存到 磁盤上

      java8中可以通過硬編碼

       System.setProperty("jdk.internal.lambda.dumpProxyClasses", ".");
      

      例如:

      jdk11 中只能使用jvm參數指定,硬編碼無效,原因是模塊化導致的

      -Djdk.internal.lambda.dumpProxyClasses=.
      

      例如:

      執行方法后輸出文件如下:

      其中實現類的類名是有具體含義的

      其中Test$Lambda$15.class信息如下:

      //
      // Source code recreated from a .class file by IntelliJ IDEA
      // (powered by FernFlower decompiler)
      //
      
      package test.java8.lambdaimpl;
      
      import java.lang.invoke.SerializedLambda;
      import java.lang.invoke.LambdaForm.Hidden;
      import test.java8.lambdaimpl.Test.User;
      
      // $FF: synthetic class
      final class Test$$Lambda$15 implements SFunction {
          private Test$$Lambda$15() {
          }
      
          @Hidden
          public Object apply(Object var1) {
              return ((User)var1).getName();
          }
      
          private final Object writeReplace() {
              return new SerializedLambda(Test.class, "test/java8/lambdaimpl/SFunction", "apply", "(Ljava/lang/Object;)Ljava/lang/Object;", 5, "test/java8/lambdaimpl/Test$User", "getName", "()Ljava/lang/String;", "(Ltest/java8/lambdaimpl/Test$User;)Ljava/lang/Object;", new Object[0]);
          }
      }
      
      

      通過源碼得知 調用writeReplace方法是為了獲取到方法返回的SerializedLambda對象

      SerializedLambda: 是Java8中提供,主要就是用于封裝方法引用所對應的信息,主要的就是方法名、定義方法的類名、創建方法引用所在類。拿到這些信息后,便可以通過反射獲取對應的Field。

      值得注意的是,代碼中多次編寫的同一個方法引用,他們創建的是不同Function實現類,即他們的Function實例對象也并不是同一個。

      一個方法引用創建一個實現類,他們是不同的對象,那么BeanUtils中將SFunction作為緩存key還有意義嗎?

      答案是肯定有意義的!!!因為同一方法中的定義的Function只會動態的創建一次實現類并只實例化一次,當該方法被多次調用時即可走緩存中查詢該方法引用對應的Field。

      通過內部類實現類的類名規則我們也能大致推斷出來, 只要申明lambda的相對位置不變, 那么對應的Function實現類包括對象都不會變。

      通過在剛才的示例代碼中添加一行, 就能說明該問題, 之前15號對應的是getName, 而此時的15號class對應的是getAge這個函數引用

      我們再通過代碼驗證一下 剛才的猜想

      參考:

      https://blog.csdn.net/u013202238/article/details/105779686

      https://blog.csdn.net/qq_39809458/article/details/101423610

      posted @ 2023-10-19 18:11  張鐵牛  閱讀(1608)  評論(1)    收藏  舉報
      主站蜘蛛池模板: 亚洲精品久久一区二区三区四区| 久久亚洲精品无码播放| 蜜臀av入口一区二区三区| 天堂亚洲免费视频| 亚洲va久久久噜噜噜久久狠狠| 日韩精品国产中文字幕| 国产AV影片麻豆精品传媒| 2018年亚洲欧美在线v| 最新永久免费AV无码网站| 国产蜜臀一区二区在线播放| 国产一级二级三级毛片| 东京热大乱系列无码| 国内精品久久久久影院日本| 国产精成人品日日拍夜夜| 国内不卡一区二区三区| 国产精品女生自拍第一区| 99久久国产综合精品女图图等你| 欧美人成精品网站播放| 国产老熟女乱子一区二区| 久久精品国产免费观看频道| 午夜免费视频国产在线| 成人午夜在线观看刺激| 中文字幕 日韩 人妻 无码| 国产免费午夜福利在线观看| 又湿又紧又大又爽A视频男| 国产不卡精品视频男人的天堂| 人妻中文字幕亚洲一区| 亚洲午夜激情久久加勒比| 久久精品亚洲精品国产色婷| 成人一区二区三区久久精品| 欧美国产激情18| 国产成人亚洲精品青草天美| 我国产码在线观看av哈哈哈网站| 亚洲日韩久热中文字幕| 在线看片免费人成视久网| 久久久久人妻精品一区三寸| 老色鬼永久精品网站| 亚洲蜜臀av乱码久久| 国产老妇伦国产熟女老妇高清 | 蜜桃一区二区三区免费看| 少妇上班人妻精品偷人|