浅说Java8的::(Java8中通过方法引用获取属性名、SerializedLambda)

362 阅读2分钟

首先来看两段MyBatis-Puls的代码

LambdaQueryWrapper<Channel> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper
        .eq(ObjectUtil.isNotEmpty(param.getName()),Channel::getName, param.getName());   
QueryWrapper<Channel> queryWrapper = new QueryWrapper<>();
queryWrapper
        .eq(ObjectUtil.isNotEmpty(param.getName()),"name", param.getName());

发现什么没有?

Channel::getName代替了"name"原本的字段名

那么Channel::getName明明是调用getName的方法,怎么会变成"name"???

不得不说,这很酷,不是吗?我们的方法代码是不是也可以这样呢?怎么实现呢?

Java8中给我们提供了实现方式,首先要做的就是定义一个可序列化的函数式接口(实现Serializable),实现如下:

import java.io.Serializable;
import java.util.function.Function;

@FunctionalInterface
public interface SerializableFunction<T, R> extends Function<T, R>, Serializable {

}

然后需要解析这个SerializableFunction,完整实现如下ReflectionUtil

import com.baomidou.mybatisplus.annotation.TableName;
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;

public class ReflectionUtil {

    private static Map<SerializableFunction<?, ?>, Field> cache = new ConcurrentHashMap<>();

    public static <T, R> String getFieldName(SerializableFunction<T, R> function) {
        Field field = ReflectionUtil.getField(function);
        return field.getName();
    }

    public static <T, R> Field getField(SerializableFunction<T, R> function) {
        return cache.computeIfAbsent(function, ReflectionUtil::findField);
    }

    public static <T, R> Field findField(SerializableFunction<T, R> function) {
        Field field = null;
        String fieldName = null;
        try {
            SerializedLambda serializedLambda = getSerializedLambda(function);
            // 第2步 implMethodName 即为Field对应的Getter方法名
            String implMethodName = serializedLambda.getImplMethodName();
            if (implMethodName.startsWith("get") && implMethodName.length() > 3) {
                fieldName = Introspector.decapitalize(implMethodName.substring(3));
            } else if (implMethodName.startsWith("is") && implMethodName.length() > 2) {
                fieldName = Introspector.decapitalize(implMethodName.substring(2));
            } else if (implMethodName.startsWith("lambda$")) {
                throw new IllegalArgumentException("SerializableFunction不能传递lambda表达式,只能使用方法引用");
            } else {
                throw new IllegalArgumentException(implMethodName + "不是Getter方法引用");
            }

            Class<?> aClass = getClass(serializedLambda);
            // 第4步  Spring 中的反射工具类获取Class中定义的Field
            field = ReflectionUtils.findField(aClass, fieldName);
        } catch (Exception e) {
            e.printStackTrace();
        }
        // 第5步 如果没有找到对应的字段应该抛出异常
        if (field != null) {
            return field;
        }
        throw new NoSuchFieldError(fieldName);
    }

    public static <T, R> String tableName(SerializableFunction<T, R> function) {
        Class<?> aClass = null;
        try {
            aClass = getClass(function);
            TableName annotation = aClass.getAnnotation(TableName.class);
            if (annotation == null){
                throw new NoSuchFieldError("注解不存在!");
            }
            return annotation.value();
        } catch (Exception e) {
            e.printStackTrace();
            throw new NoSuchFieldError(e.toString());
        }
    }

    public static <T, R> SerializedLambda getSerializedLambda(SerializableFunction<T, R> function) throws Exception {
        // 第1步 获取SerializedLambda
        Method method = function.getClass().getDeclaredMethod("writeReplace");
        method.setAccessible(Boolean.TRUE);
        return (SerializedLambda) method.invoke(function);
    }

    public static Class<?> getClass(SerializedLambda serializedLambda) throws ClassNotFoundException {
        // 第3步 获取的Class是字符串,并且包名是“/”分割,需要替换成“.”,才能获取到对应的Class对象
        String declaredClass = serializedLambda.getImplClass().replace("/", ".");
        Class<?> aClass = Class.forName(declaredClass, false, ClassUtils.getDefaultClassLoader());
        return aClass;
    }
    public static <T, R>  Class<?> getClass(SerializableFunction<T, R> function) throws Exception {
        return getClass(getSerializedLambda(function));
    }
}

接下来我们就可以自定义自己想要的方法了

public static void main(String[] args) {
    test(BsMarketingChannel::getChannelName,BsMarketingChannel::getId,BsMarketingChannel::getCompany);
}
public static  <T, R> void test(SerializableFunction<T, R> fieldNameMethod,SerializableFunction<T, ?>... columns){
    String tableName = ReflectionUtil.tableName(fieldNameMethod);
    String selectSql = "";
    if (ArrayUtils.isNotEmpty(columns)) {
        List<SerializableFunction<T, ?>> sFunctions = Arrays.asList(columns);
        selectSql =  sFunctions.stream().map((i) -> {
            return ReflectionUtil.getFieldName(i);
        }).collect(Collectors.joining(","));
    }
}

发现什么没?我明明传的是getChannelName方法,但是我获取的是tableName,即@TableName注解中的表名

如果你详细看了ReflectionUtil,就会发现,怎么通过方法就获取到class了,那拿到类class,通过反射,不就什么都可以拿到嘛!重要的是SerializedLambda

 SerializedLambda serializedLambda = getSerializedLambda(function);
 String declaredClass = serializedLambda.getImplClass().replace("/", ".");
        Class<?> aClass = Class.forName(declaredClass, false, ClassUtils.getDefaultClassLoader());

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

更详细的可以查看以下文章

# Java8的SerializedLambda详解

然后?你已经大概明白了原理,然后当然就可以为所欲为了,嘿嘿嘿。

感谢:

# 详解Java8中如何通过方法引用获取属性名

# Java8的SerializedLambda详解