前言
Javassist作为一款强大的class编辑器,它能够读取class文件内容,class文件的常量池中包含了当前类所有需要交互的其他类。要获取当前类所有依赖的类,只需要先获取当前类直接依赖的类,再继续广度优先遍历直接依赖类依赖的类,最终遍历了整棵依赖树之后就能获取当前类依赖。对于动态代理其实也是通过在运行过程中动态新的代理类,代理类不但会增加额外的用户逻辑,还会调用被代理对象的对应方法,Javassist强大的类编辑功能能够很好的完成这个任务。
类依赖实现
首先需要定义当前类直接依赖的类有哪些,首先继承的父类很显然是当前类一定会依赖的,接下来是当前类实现的接口,接着是定义的字段值类型,最后就是方法传入参数类型、返回参数类型还有方法中使用到其他工具类。得到这些直接依赖的类之后,需要再获取这些直接依赖类直接依赖的类,我们发现这时候问题变成了上面的问题,只不过当前类变成了当前类依赖的其中一个类,递归调用就可以解决这种重复问题。
前面讲解Class文件格式的时候讨论过如何使用Javassist来获取父类、接口和方法中依赖的类,不过里面并不完全,还缺少实现接口类、方法签名里的类型,我们可以通过方法签名的分析得到使用到的类。
/**
* 查看某个类引用的所有类
* @param clazz
* @param set
* @throws NotFoundException
*/
private static void bfs(String clazz, Set<String> set) throws NotFoundException {
CtClass ctClass = ClassPool.getDefault().get(clazz);
// 当前类直接依赖的类
Set<String> levelClasses = new TreeSet<>();
ClassFile classFile = ctClass.getClassFile();
/**
* 遍历代码内使用的类,包含方法实现里使用的类,不包含方法签名里的类
*/
for (String className : classFile.getConstPool().getClassNames()) {
if (className.startsWith("[L")) {
className = className.substring(2, className.length() - 1);
} else if (className.startsWith("[")) {
continue;
}
className = getClassName(className);
addClassName(set, levelClasses, className);
}
/**
* 获取父类
*/
String superClass = classFile.getSuperclass();
if (!"".equals(superClass) && superClass != null && !set.contains(superClass)) {
levelClasses.add(superClass);
set.add(superClass);
}
/**
* 获取所有接口
*/
String[] interfaces = classFile.getInterfaces();
if (interfaces != null) {
for (String face : interfaces) {
String className = getClassName(face);
addClassName(set, levelClasses, className);
}
}
/**
* 获取字段的类型
*/
List<FieldInfo> fieldInfoList = classFile.getFields();
if (fieldInfoList != null) {
for (FieldInfo fieldInfo : fieldInfoList) {
String descriptor = fieldInfo.getDescriptor();
if (descriptor.startsWith("L") && descriptor.endsWith(";")) {
String className = descriptor.substring(1, descriptor.length() - 1);
className = getClassName(className);
addClassName(set, levelClasses, className);
}