java中对lambda进行反射

createh53周前 (12-05)技术教程20

在java中如何对一个函数式调用解析出原有的调用结构

例如有如下类的定义

 @Data
    public class City {
        public int cityId;
        public Address addr;
        public List<Address> addrList;
        public Address[] addrArray;

    }
    @Data
    public class Address {
        public String location;

        public Address2 address2;
    }

    @Data
    public class Address2 {
        public String location2;

    }

现在有函数调用如下,希望获取整个函数的定义信息

//希望解析到 函数的T为City.class, 调用为 getAddr方法
Function<City, ?> f1 = City::getAddr;  
//希望解析到 函数的T为City.class, 调用为 addrs属性获取
Function<City, ?> f2 = city -> city.addr; 
//希望解析到 函数的T为City.class, 调用为 addrs属性获取,继续解析是调用Address.class的getAddress2方法
Function<City, ?> f3= city -> city.addr.getAddress2();
//希望解析....
Function<City, ?> f4= city -> city.addr.address2.getLocation2();

可以使用asm和SerializedLambda来解析来获取整个调用链过程

1.重新定义function


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

/**
 * @author maodali
 */
public interface DmFunction<T, R> extends Function<T, R>, Serializable {
}

2.定义一个解析结果类


import lombok.Data;
import lombok.SneakyThrows;
import org.springframework.asm.Type;
import org.springframework.util.ClassUtils;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Arrays;


/**
 * @author maodali
 */
@Data
public class DmMeta {
    //ownerClass的字符串表达方式
    private String owner;
    //name类型:方法,属性
    private DmMetaNameType nameType;
    //name
    private String name;
    //方法签名,或者属性签名
    private String descriptor;
    //具体的类class
    private Class<?> ownerClass;
    //返回结果类型
    private Class<?> returnClass;


    @SneakyThrows
    public DmMeta(String owner, DmMetaNameType nameType, String name, String descriptor) {
        this.owner = owner;
        this.nameType = nameType;
        this.name = name;
        this.descriptor = descriptor;
        //
        ownerClass = Class.forName(owner.replaceAll("/", "."));

        if (nameType == DmMetaNameType.FIELD) {
            Field field = ownerClass.getField(name);
            returnClass = field.getType();
        }
        if (nameType == DmMetaNameType.METHOD) {
            Method method = method();
            returnClass = method.getReturnType();
        }
    }

    @SneakyThrows
    public Field field() {
        if (nameType != DmMetaNameType.FIELD) {
            throw new RuntimeException("not field");
        }
        return ownerClass.getField(name);
    }

    @SneakyThrows
    public Method method() {
        if (nameType != DmMetaNameType.METHOD) {
            throw new RuntimeException("not method");
        }
        Type methodType = Type.getMethodType(descriptor);
        Type[] argTypes = methodType.getArgumentTypes();
        Class<?>[] array = Arrays.stream(argTypes)
          .map(x -> strToClass(x.getClassName()))
          .toArray(Class<?>[]::new);
        return ownerClass.getMethod(name, array);
    }

    @SneakyThrows
    private Class<?> strToClass(String className) {
        return ClassUtils.forName(className, this.getClass().getClassLoader());
    }

    public enum DmMetaNameType {
        METHOD,
        FIELD,
        ;
    }

}

3.解析方法


import lombok.SneakyThrows;
import org.hamcrest.beans.PropertyUtil;
import org.springframework.asm.ClassReader;
import org.springframework.asm.ClassVisitor;
import org.springframework.asm.MethodVisitor;
import org.springframework.asm.Opcodes;

import java.io.InputStream;
import java.io.Serializable;
import java.lang.invoke.SerializedLambda;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import static org.springframework.asm.Opcodes.ASM9;
import static org.springframework.asm.Opcodes.INVOKEVIRTUAL;

/**
 * @author maodali
 */
public class DmMetaUtil implements Serializable {

    private static final Map<DmFunction<?, ?>, List<DmMeta>> cache = new HashMap<>();


    @SuppressWarnings("unchecked")
    @SneakyThrows
    public static <T, R> List<DmMeta> resolve(DmFunction<T, R> function) {

        var s = cache.get(function);
        if (s != null) {
            return s;
        }

        Method method = function.getClass().getDeclaredMethod("writeReplace");
        method.setAccessible(true);
        SerializedLambda serializedLambda = (SerializedLambda) method.invoke(function);
        String methodName = serializedLambda.getImplMethodName();

        List<DmMeta> metaList = new ArrayList<>();

        if (methodName.startsWith("lambda#34;)) {
            String implClass = serializedLambda.getImplClass().replace('/', '.');
            Class<?> clazz = Class.forName(implClass, true, PropertyUtil.class.getClassLoader());
            try (InputStream is = clazz.getResourceAsStream(clazz.getSimpleName() + ".class");) {
                ClassReader classReader = new ClassReader(is);
                classReader.accept(new ClassVisitor2(metaList, methodName), ClassReader.EXPAND_FRAMES);
            }
        } else {
            DmMeta meta = new DmMeta(serializedLambda.getImplClass(), DmMeta.DmMetaNameType.METHOD, serializedLambda.getImplMethodName(), serializedLambda.getImplMethodSignature());
            metaList.add(meta);
        }
        cache.put(function, metaList);
        return metaList;
    }


    private static class ClassVisitor2 extends ClassVisitor {
        private final List<DmMeta> metaList;
        private final String lambdaMethodName;

        public ClassVisitor2(List<DmMeta> metaList, String lambdaMethodName) {
            super(ASM9);
            this.metaList = metaList;
            this.lambdaMethodName = lambdaMethodName;
        }

        @Override
        public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
            if (name.equals(lambdaMethodName)) {
                return new MethodVisitor2(metaList);
            }
            return super.visitMethod(access, name, descriptor, signature, exceptions);
        }

        @Override
        public void visitEnd() {
            super.visitEnd();
        }
    }

    private static class MethodVisitor2 extends MethodVisitor {
        private final List<DmMeta> metaList;

        public MethodVisitor2(List<DmMeta> metaList) {
            super(ASM9);
            this.metaList = metaList;
        }

        @Override
        public void visitFieldInsn(int opcode, String owner, String name, String descriptor) {
            if (opcode == Opcodes.GETFIELD) {
                DmMeta meta = new DmMeta(owner, DmMeta.DmMetaNameType.FIELD, name, descriptor);
                metaList.add(meta);
            }
            super.visitFieldInsn(opcode, owner, name, descriptor);
        }

        @Override
        public void visitMethodInsn(int opcode, String owner, String name, String descriptor, boolean isInterface) {
            if (opcode == INVOKEVIRTUAL) {
                DmMeta meta = new DmMeta(owner, DmMeta.DmMetaNameType.METHOD, name, descriptor);
                metaList.add(meta);
            }
            super.visitMethodInsn(opcode, owner, name, descriptor, isInterface);
        }
    }


}

调用DmMetaUtil#resolve 即可得到一个List<DmMeta>结果,DmMeta记录了每一集调用的详细信息,得到了list的DmMeta 就可以通过反射,获取更多的相关信息

如里面的method()方法,可以获取完整的Method信息,field()方法,可以获取完整的Field信息,

如果反射的信息不够,还可以自己在通过反射进行扩展

下面是一个简单的扩展


import lombok.Data;
import lombok.SneakyThrows;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.WildcardType;
import java.util.Locale;
import java.util.Map;


/**
 * @author maodali
 */
@Data
public class DmMetaExt {

    private DmMeta dmMeta;

    /**
     * 预期属性名
     */
    private String expectFieldName;

    /**
     * returnClass 是否是集合类型:数组,集合,map
     */
    private boolean isReturnCollecting;

    /**
     * <p>只做一个简单的返回类型计算;</p>
     * <p>如果returnClass 为数组,集合,的时候真实类型</p>
     * <p>否则为returnClass</p>
     */
    private Class<?> actReturnClass0;

    public DmMetaExt(DmMeta dmMeta) {

        this.dmMeta = dmMeta;

        resolve();
    }

    private boolean isCollecting(Class<?> c) {
        return c.isArray() || Iterable.class.isAssignableFrom(c) || Map.class.isAssignableFrom(c);
    }

    private String methodNameToFieldName() {
        String name = this.dmMeta.getName();
        if (name.startsWith("get") || name.startsWith("set")) {
            String s = name.substring(3);
            if (s.isEmpty()) {
                return s;
            }
            return s.substring(0, 1).toLowerCase(Locale.ROOT) + s.substring(1);

        }
        if (name.startsWith("is")) {
            String s = name.substring(2);
            if (s.isEmpty()) {
                return s;

            }
            return s.substring(0, 1).toLowerCase(Locale.ROOT) + s.substring(1);

        }
        return name.substring(0, 1).toLowerCase(Locale.ROOT) + name.substring(1);
    }

    private void resolve() {
        if (this.dmMeta.getNameType() == DmMeta.DmMetaNameType.FIELD) {
            resolveField();
        } else if (this.dmMeta.getNameType() == DmMeta.DmMetaNameType.METHOD) {
            resolveMethod();
        } else {
            throw new RuntimeException("unknown name type: " + this.dmMeta.getNameType());
        }
    }

    @SneakyThrows
    private void resolveField() {
        this.expectFieldName = this.dmMeta.getName();
        Field field = this.getDmMeta().field();
        Class<?> returnClass = this.dmMeta.getReturnClass();
        isReturnCollecting = isCollecting(returnClass);
        if (returnClass.isArray()) {
            this.actReturnClass0 = returnClass.getComponentType();
        } else {
            Type genericType = field.getGenericType();
            actReturnClass0 = typeClass(genericType);
        }
    }

    @SneakyThrows
    private void resolveMethod() {
        this.expectFieldName = methodNameToFieldName();

        Method method = this.dmMeta.method();
        Class<?> returnClass = this.dmMeta.getReturnClass();
        isReturnCollecting = isCollecting(returnClass);
        if (returnClass.isArray()) {
            this.actReturnClass0 = returnClass.getComponentType();
        } else {
            Type genericReturnType = method.getGenericReturnType();
            actReturnClass0 = typeClass(genericReturnType);
        }
    }

    private Class<?> typeArrayToClass(Type[] types) {
        //只取第一个
        Type type = types[0];
        if (type instanceof Class<?>) {
            return (Class<?>) type;
        }
        if (type instanceof WildcardType wildcardType) {
            //只取upperBounds
            Type[] upperBounds = wildcardType.getUpperBounds();
            if (upperBounds.length == 0) {
                return Object.class;
            }
            //取到实际类型
            return typeArrayToClass(upperBounds);
        }
        return type.getClass();
    }

    private Class<?> typeClass(Type type) {
        if (type instanceof ParameterizedType parameterizedType) {
            Type[] actType = parameterizedType.getActualTypeArguments();
            return typeArrayToClass(actType);
        }
        if (type instanceof Class<?>) {
            return (Class<?>) type;
        }
        // 否则为returnClass
        return this.dmMeta.getReturnClass();
    }
}

可以使用DeMetaExt将整个方法调用,解析为属性的链式字符串,类似为spel的反方向运行

public static void main(String[] args) {

        DmFunction<City, ?> f4= city -> city.addr.getAddress2().getLocation2();
        List<DmMeta> l = DmMetaUtil.resolve(f4);
        String join = l.stream().map(DmMetaExt::new)
          .map(DmMetaExt::getExpectFieldName)
          .collect(Collectors.joining("."));
        //输出结果为:addr.address2.location2      
       System.out.println(join);
    }

在很多需要输入字符串进行操作的框架上非常的好用,可以防止手动输入字符串后,由于改动没有及时的更新对应的代码导致在编译阶段无法识别出代码错误,甚至导致线上事故

例如:jpa中Path ,Form,Root,Join都是字符串操作,在编译阶段无法确认是否错误,可以扩展DmMeta来使用方法引用,这样可以在编译阶段就辨识出错误问题

相关文章

Java 回调函数

前言最近在复习一些编程的基础知识,发现回调函数这个东西用起来很方便。于是就研究了一下Java是如何实现真正意义上的回调函数。直接调用与间接调用在理解回调函数之前,我们需要先来理解在 c/c++ 中,什...

碎片时间掌握C语言(十五)——函数的调用

上一节介绍了函数的概念和定义方式。我们知道了存在两类函数:库函数和自定义函数。其中库函数是系统声明和定义的,比如scanf、printf函数,他们是现成的工具。自定义函数是程序员自己声明和定义的,是程...