美文网首页
使用SerializedLambda获取到方法引用的方法名

使用SerializedLambda获取到方法引用的方法名

作者: 小胖学编程 | 来源:发表于2022-12-18 17:21 被阅读0次

    引子:MyBatisPlus的lambdaQuery,可以在构造查询条件时传递方法的引用,MyBatis能够将方法引用解析成为要查询的DB字段名,如下:

    Wrappers.<Member>lambdaQuery().eq(Member::getMemberId, memberId);
    

    解释:构建出的sql条件:member_id=1000L

    上述方法传入的是lambda表达式,拿到的却是方法引用的方法名,并将驼峰命名法转化为蛇形命名法。

    借鉴

    lambda表达式实现了Serializable接口:

    @FunctionalInterface
    public interface IGetter<T> extends Serializable {
        Object get(T source);
    }
    
    @FunctionalInterface
    public interface ISetter<T, U> extends Serializable {
        void set(T t, U u);
    }
    

    工具类:

    import java.io.Serializable;
    import java.lang.invoke.SerializedLambda;
    import java.lang.reflect.Method;
    import java.util.Map;
    import java.util.concurrent.ConcurrentHashMap;
    
    import com.google.common.base.CaseFormat;
    
    import lombok.extern.slf4j.Slf4j;
    
    @Slf4j
    public class BeanUtils {
        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);
            }
            return toSnake(methodName.replace(prefix, ""));
        }
    
        public static <T, U> String convertToFieldName(ISetter<T, U> fn) {
            SerializedLambda lambda = getSerializedLambda(fn);
            String methodName = lambda.getImplMethodName();
            if (!methodName.startsWith("set")) {
                log.warn("无效的setter方法:" + methodName);
            }
            return toSnake(methodName.replace("set", ""));
        }
    
        /**
         * 关键在于这个方法
         */
        public static SerializedLambda getSerializedLambda(Serializable fn) {
            SerializedLambda lambda = CLASS_LAMDBA_CACHE.get(fn.getClass());
            if (lambda == null) {
                try {
                    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("", e);
                }
            }
            return lambda;
        }
    
        private static String toSnake(String fieldName) {
            return CaseFormat.LOWER_CAMEL.to(CaseFormat.LOWER_UNDERSCORE, fieldName);
        }
    }
    

    测试方法:

    @Data
    public class Person {
        private int id;
        private String name;
    }
    
    public class Test {
    
        public static void main(String[] args) {
            String getName = BeanUtils.convertToFieldName(Person::getId);
            System.out.println(getName);
            String setName = BeanUtils.convertToFieldName(Person::setName);
            System.out.println(setName);
        }
    }
    

    原理

    如果一个函数式接口实现了Serializable接口,那么它的实例就会自动生成了一个返回SerializedLambda实例的 writeReplace方法,可以从SerializedLambda实例中获取到这个函数式接口的运行时信息。这些运行时的信息就是 SerializedLambda的属性:

    相关文章

      网友评论

          本文标题:使用SerializedLambda获取到方法引用的方法名

          本文链接:https://www.haomeiwen.com/subject/weprqdtx.html