前言
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);
}
if (descriptor.startsWith("[L") && descriptor.endsWith(";")) {
String className = descriptor.substring(2, descriptor.length() - 1);
className = getClassName(className);
addClassName(set, levelClasses, className);
}
}
}
/**
* 获取方法声明的参数和返回值包含的所有类
*/
List<MethodInfo> methodInfoList = classFile.getMethods();
if (methodInfoList != null) {
for (MethodInfo methodInfo : methodInfoList) {
String descriptor = methodInfo.getDescriptor();
extractClassNames(descriptor, set, levelClasses);
}
}
/**
* 对当前类直接依赖的类,继续查寻它们依赖的其他类
*/
if (!levelClasses.isEmpty()) {
for (String className : levelClasses) {
bfs(className, set);
}
}
}
private static void addClassName(Set<String> set, Set<String> levelClasses, String className) {
// 如果当前节点已经被访问过,不再将它添加到当前类的直接依赖中
if (!set.contains(className)) {
levelClasses.add(className);
set.add(className);
}
}
private static String getClassName(String className) {
return className.replaceAll("/", ".");
}
private static void extractClassNames(String descriptor, Set<String> set, Set<String> levelClasses) {
String reg = "(L.+?;)";
Pattern pattern = Pattern.compile(reg);
Matcher matcher = pattern.matcher(descriptor);
while (matcher.find()) {
String className = matcher.group();
className = className.substring(1, className.length() - 1);
className = getClassName(className);
addClassName(set, levelClasses, className);
}
}
上面的extractClassNames会根据方法的descriptor来获取方法签名中的类型,注意这里使用了勉强匹配符“+?“也就是说只要匹配到重L开始的第一个“;”就代表查找到一个类型,当所有匹配到被找到了,当前这个方法里所有传入参数和返回参数的类型都被查找到了。
动态代理实现
动态代理最主要的是理解生成的代理类内部会执行InvocationHandler的回调,而InvocationHandler内部又会调用被代理对象对应的方法,用户添加的额外逻辑是放在InvocationHandler的invoke方法里的,先看JDK的默认实现。
// 被代理接口
public interface IManager {
public void add();
public void remove();
}
// InvocationHandler实例
public static class MyInvocationHandler implements InvocationHandler {
private Object target;
public MyInvocationHandler(IManager manager) {
this.target = manager;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 添加计算执行时间的逻辑
long startTime = System.currentTimeMillis();
System.out.println("StartTime = " + startTime);
// 调用实际的执行方法逻辑
method.invoke(target, args);
long endTime = System.currentTimeMillis();
System.out.println("EndTime = " + endTime);
System.out.println("Total Time = " + (endTime - startTime));
return null;
}
}
// 实际执行方法对象,也就是被代理对象
public static class BookManager implements IManager {
@Override
public void add() {
System.out.println("Start add books");
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("finish add book");
}
@Override
public void remove() {
System.out.println("Start remove books");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("finish remove book");
}
}
public static void main(String[] args) {
IManager obj = (IManager) MyProxy.newProxyInstance(ProxyTest.class.getClassLoader(), new Class<?>[] {
IManager.class
}, new MyInvocationHandler(new BookManager()));
// IManager obj = new ProxyClass(new MyInvocationHandler(new BookManager()));
obj.add();
}
上面的JDK实际上是生成了一个代理对象,代理对象会调用InvocationHandler的invoke方法,这里写一个简单的代理对象来代表实际执行的逻辑。
public static class ProxyClass implements IManager {
// InvocationHandler对象,包含统计时间逻辑和实际执行逻辑
private InvocationHandler h;
public ProxyClass(InvocationHandler h) {
this.h = h;
}
@Override
public void add() {
try {
// 获取要执行的method对象
Method method = IManager.class.getDeclaredMethod("add");
// 调用InvocationHandler的invoke方法
h.invoke(this, method, null);
} catch (Throwable e) {
throw new RuntimeException(e);
}
}
@Override
public void remove() {
try {
Method method = IManager.class.getDeclaredMethod("remove");
h.invoke(this, method, null);
} catch (Throwable e) {
throw new RuntimeException(e);
}
}
}
所以动态代理实际上就是生成ProxyClass这个类,这个类需要在运行是动态生成编译加载到内存中产生动态代理对象,用户调用动态代理对象的方法会被派发给InvocationHandler。现在通过Javassist来动态的生成一个ProxyClass类。
public static class MyProxy {
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler handler) {
Class<?> inter = interfaces[0];
Method[] methods = inter.getDeclaredMethods();
ClassPool pool = ClassPool.getDefault();
// 生成ProxyClass对象
CtClass proxy = pool.makeClass("com.test.ProxyClass");
try {
CtClass interfaceCt = pool.get(inter.getName());
CtClass invocation = pool.get(InvocationHandler.class.getName());
// 动态代理类实现了接口
proxy.addInterface(interfaceCt);
// 动态代理类包含InvocationHandler的字段h
CtField h = new CtField(invocation, "h", proxy);
h.setModifiers(Modifier.PRIVATE);
proxy.addField(h);
// 动态代理类包含一个包含InvocationHandler参数的构造函数
CtConstructor constructor = new CtConstructor(new CtClass[] { invocation }, proxy);
constructor.setBody("{$0.h = $1;}");
proxy.addConstructor(constructor);
// 为每个方法添加派发到InvocationHandler的执行逻辑
for (Method method : methods) {
String name = method.getName();
String retType = method.getReturnType().getName();
StringBuilder builder = new StringBuilder();
builder.append("public ").append(retType).append(" ").append(name).append("() {");
builder.append("try { ");
builder.append("java.lang.reflect.Method method = ").append(inter.getName())
.append(".class.getDeclaredMethod(\"").append(name).append("\", null);");
builder.append("h.invoke(this, method, null);");
builder.append("} catch (Throwable e)");
builder.append("{ throw new RuntimeException(e); } }");
// System.out.println(builder.toString());
CtMethod m = CtNewMethod.make(builder.toString(), proxy);
proxy.addMethod(m);
}
// 通过反射生成动态代理对象
Class<?> clazz = proxy.toClass();
Constructor<?> construct = clazz.getConstructor(InvocationHandler.class);
return construct.newInstance(handler);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}