java中对lambda进行反射
在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来使用方法引用,这样可以在编译阶段就辨识出错误问题