利用Lambda实现通过gettersetter方法引用拿到属性名

转载自:https://blog.csdn.net/cradle2006/article/details/95073133
    https://blog.csdn.net/u012503481/article/details/100896507

  
  很多开发场景需要用到Java Bean的属性名,直接写死属性名字符串的形式容易产生bug(属性名一旦变化,IDE不会提示字符串需要同步修改)。JDK8的Lambda可以通过方法引用简化代码,同样也可以通过getter/setter的方法引用拿到属性名,避免潜在的bug。期望实现效果:

1
2
3
4
// 传统方式:hard code写死属性名
// String ITEM_NAME = "orgName";
// 方法引用:替代hard code字符串,当属性名变化时IDE会同步提示,避免未同步产生bug
String ITEM_NAME = BeanUtils.convertToFieldName(User::getOrgName);

  原理是使用SerializedLambda类来完成,SerializedLambda是Lambda表达式在序列化的时候,用来描述Lambda表达式信息的类:

1
2
3
4
5
6
7
8
9
10
11
12
public final class SerializedLambda implements Serializable {
private static final long serialVersionUID = 8025925345765570181L;
private final Class<?> capturingClass;
private final String functionalInterfaceClass;
private final String functionalInterfaceMethodName;
private final String functionalInterfaceMethodSignature;
private final String implClass;
private final String implMethodName;
private final String implMethodSignature;
private final int implMethodKind;
private final String instantiatedMethodType;
private final Object[] capturedArgs;

  获取方法引用的方法名只需要用到implMethodName即可。
  需要注意的是,SerializedLambda是对Lambda表达式进行描述的对象,在Lambda表达式可序列化的时候(函数式接口继承Serializable)才能得到,也就是说,定义的FunctionalInterface必须实现继承Serializable。函数式接口继承Serializable时,编译器在编译Lambda表达式时,生成了一个writeReplace方法,这个方法会返回SerializedLambda,可以反射调用这个方法。

  
  简单的了解一下对象序列化中的 writeReplace 和 readResolve:
  writeReplace:在将对象序列化之前,如果对象的类或父类中存在writeReplace方法,则使用writeReplace的返回值作为真实被序列化的对象;writeReplace在writeObject之前执行;
  readResolve:在将对象反序列化之后,ObjectInputStream.readObject返回之前,如果从对象流中反序列化得到的对象所属类或父类中存在readResolve方法,则使用readResolve的返回值作为ObjectInputStream.readObject的返回值;readResolve在readObject之后执行;

  函数式接口如果继承了Serializable,使用Lambda表达式来传递函数式接口时,编译器会为Lambda表达式生成一个writeReplace方法,这个生成的writeReplace方法会返回java.lang.invoke.SerializedLambda。SerializedLambda类中有readResolve方法,这个readResolve方法中通过反射调用了Lambda表达式所在外部类中的$deserializeLambda$方法,这个方法是编译器自动生成的,$deserializeLambda$方法内部解析SerializedLambda,并调用LambdaMetafactory.altMetafactory或LambdaMetafactory.metafactory方法(引导方法)得到一个调用点(CallSite),CallSite会被动态指定为Lambda表达式代表的函数式接口类型,并作为Lambda表达式返回。

  


具体实现代码封装

  1. 定义FunctionalInterface 接收方法引用
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    /**
    * getter方法接口定义
    */
    @FunctionalInterface
    public interface IGetter<T> extends Serializable {
    Object apply(T source);
    }
    /**
    * setter方法接口定义
    */
    @FunctionalInterface
    public interface ISetter<T, U> extends Serializable {
    void accept(T t, U u);
    }
  2. 定义getter/setter引用转换属性名的工具类
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    public class BeanUtils {
    ...
    /**
    * 缓存类-Lambda的映射关系
    */
    private static Map<Class, SerializedLambda> CLASS_LAMDBA_CACHE = new ConcurrentHashMap<>();

    /***
    * 转换方法引用为属性名
    * @param fn
    * @return
    */
    public static <T> String convertToFieldName(IGetter<T> fn) {
    SerializedLambda lambda = getSerializedLambda(fn);
    String methodName = lambda.getImplMethodName();
    String prefix = null;
    if(methodName.startsWith("get")){
    prefix = "get";
    }
    else if(methodName.startsWith("is")){
    prefix = "is";
    }
    if(prefix == null){
    log.warn("无效的getter方法: "+methodName);
    }
    // 截取get/is之后的字符串并转换首字母为小写(S为diboot项目的字符串工具类,可自行实现)
    return S.uncapFirst(S.substringAfter(methodName, prefix));
    }

    /***
    * 转换setter方法引用为属性名
    * @param fn
    * @return
    */
    public static <T,R> String convertToFieldName(ISetter<T,R> fn) {
    SerializedLambda lambda = getSerializedLambda(fn);
    String methodName = lambda.getImplMethodName();
    if(!methodName.startsWith("set")){
    log.warn("无效的setter方法: "+methodName);
    }
    // 截取set之后的字符串并转换首字母为小写(S为diboot项目的字符串工具类,可自行实现)
    return S.uncapFirst(S.substringAfter(methodName, "set"));
    }

    /***
    * 获取类对应的Lambda
    * @param fn
    * @return
    */
    private static SerializedLambda getSerializedLambda(Serializable fn){
    //先检查缓存中是否已存在
    SerializedLambda lambda = CLASS_LAMDBA_CACHE.get(fn.getClass());
    if(lambda == null){
    try{//提取SerializedLambda并缓存
    Method method = fn.getClass().getDeclaredMethod("writeReplace");
    method.setAccessible(Boolean.TRUE);
    lambda = (SerializedLambda) method.invoke(fn);
    CLASS_LAMDBA_CACHE.put(fn.getClass(), lambda);
    }
    catch (Exception e){
    log.error("获取SerializedLambda异常, class="+fn.getClass().getSimpleName(), e);
    }
    }
    return lambda;
    }
    }