Java 的反射可以获取一个类里的所有方法,并且可以看到方法参数的类型,但因为运行时泛型擦除的原因导致不能看到泛型内部的具体类型信息,比如有一个方法里的某个参数类型为 List<String> ,那反射的时候我们就只能知道这个参数的类型为 List<E>,但 List 里面的类型 E 就不知道了,在某些情况下我们需要知道 List 里的 E 是什么类型,因为我们需要根据这个类型 E 来mock 假数据去测试这个类或接口,例如要测试我们项目里的所有 sql 是否都正确时,我们会Mock 假数据填充到所有的Mapper代理类接口方法里面去执行并看执行是否成功,其中特殊的数据格式特殊处理。 此时有两种办法。
第一种办法就是我们直接读取代码源文件,然后我们读取某行代码时根据某些特征或正则表达式确定了它是方法头,那么我们就读取方法参数列表字符串,一个一个地解析,把方法id和它的参数类型列表关联起来。
第二种方法就是读取项目编译好后的 .class 字节码文件,使用 ASM 或 bytebuddy 直接解析类的方法,获取到类所有的方法签名,就能直接知道编译后方法的参数类型列表。如果有问题,可以添加 javac 编译参数 javac -g -parameters,给 class 文件附带更多的调试信息。
比如我们获取到这个
(Ljava/util/List<Ljava/lang/String;>;Ljava/lang/String;)Ljava/lang/String;
这样的方法签名字符串,那么我们就很容易知道 这个方法的参数列表是 (List<String>,String) ,返回值是 String,这个字符串也可以使用 SignatureVisitor 去解析浏览,但此时已达到了我们的目的。
下面的方法参数类型列表很明显就是 (List<String> , Map<String,Long> ),返回值是 List<Content>。
(Ljava/util/List<Ljava/lang/String;>;
Ljava/util/Map<Ljava/lang/String;Ljava/lang/Long;>;)
Ljava/util/List<Lcom/yzp/demo/util/TestClass$Content;>;
代码:
public static void main(String[] args) {
String path = "D:\\Java\\JavaStudy\\mybatis-study\\src\\main\\java\\com\\xxx\\demo\\util\\TestClass.class";
byte[] bytes = FileUtil.readBytes(path);
ClassReader classReader = new ClassReader(bytes);
ClassWriter classWriter = new ClassWriter(0);
ClassVisitor classVisitor = new ClassVisitor(Opcodes.ASM9) {
@Override
public MethodVisitor visitMethod(int access, String methodName, String descriptor, String signature, String[] exceptions) {
//(Ljava/util/List<Ljava/lang/String;>;)Ljava/lang/String;
System.out.println(signature);
return super.visitMethod(access, name, descriptor, signature, exceptions);
}
};
classReader.accept(classVisitor, 0);
FileUtil.writeBytes(classWriter.toByteArray(), new File("D:\\Demo.class"));
}
}
<dependency>
<groupId>net.bytebuddy</groupId>
<artifactId>byte-buddy</artifactId>
<version>1.14.12</version>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.12</version>
</dependency>