我必须在每个线程的开头和结尾处检测任何给定代码(不直接更改给定代码)。简单来说,我如何在任何线程的入口和出口点打印一些东西。
如何使用javassist做到这一点?
答案 0 :(得分:1)
您可以通过创建ExprEditor并使用它来修改与线程对象的启动和连接匹配的MethodCall来完成此操作。
在我们开始之前,让我说你不应该被长篇文章吓倒,大多数只是代码,一旦你打破了它,它很容易理解!
让我们忙碌然后......
想象一下,你有以下虚拟代码:
public class GuineaPig {
public void test() throws InterruptedException {
Thread t = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 10; i++)
System.out.println(i);
}
});
t.start();
System.out.println("Sleeping 10 seconds");
Thread.sleep(10 * 1000);
System.out.println("Done joining thread");
t.join();
}
}
运行此代码时执行
new GuineaPig().test();
你得到一个输出(睡眠的system.out可能会出现在计数的中间,因为它在主线程中运行):
Sleeping 10 seconds
0
1
2
3
4
5
6
7
8
9
Done joining thread
我们的目标是创建一个代码注入器,使输出更改为以下内容:
Detected thread starting with id: 10
Sleeping 10 seconds
0
1
2
3
4
5
6
7
8
9
Done joining thread
Detected thread joining with id: 10
我们对我们能做的事情有点限制,但我们能够注入代码并访问线程参考。希望这对你来说已经足够了,如果不是,我们仍然可以尝试再讨论一下。
考虑到所有这些想法,我们创建了以下注入器:
ClassPool classPool = ClassPool.getDefault();
CtClass guineaPigCtClass = classPool.get(GuineaPig.class.getName());
guineaPigCtClass.instrument(new ExprEditor() {
@Override
public void edit(MethodCall m) throws CannotCompileException {
CtMethod method = null;
try {
method = m.getMethod();
} catch (NotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
String classname = method.getDeclaringClass().getName();
String methodName = method.getName();
if (classname.equals(Thread.class.getName())
&& methodName.equals("start")) {
m.replace("{ System.out.println(\"Detected thread starting with id: \" + ((Thread)$0).getId()); $proceed($$); } ");
} else if (classname.equals(Thread.class.getName())
&& methodName.equals("join")) {
m.replace("{ System.out.println(\"Detected thread joining with id: \" + ((Thread)$0).getId()); $proceed($$); } ");
}
}
});
guineaPigCtClass
.writeFile("<Your root directory with the class files>");
}
那么这段小巧的代码中发生了什么?我们使用ExprEdit来检测我们的GuineaPig类(不会对它造成任何伤害!)并拦截所有方法调用。
当我们拦截一个方法调用时,我们首先检查方法的声明类是否是一个Thread类,如果是这样的话,那就意味着我们在Thread对象中调用一个方法。然后,我们会检查它是否是start
和join
两种特定方法中的一种。
当这两个案例中的一个发生时,我们使用javassist高级API来进行代码替换。替换很容易在代码中找到,提供的实际代码可能有点棘手,所以让我们拆分其中一行,让我们以例如检测Thread开始的行为例:
{ System.out.println(\"Detected thread starting with id: \" + ((Thread)$0).getId()); $proceed($$); } "
您可以在Javassist教程的第4.2 Altering a Method Body部分中阅读有关此特殊操作符的更多信息(搜索MethodCall子部分,对不起,那个子部分没有锚点)
最后,在所有这些 kung fu 之后,我们将ctClass的字节码写入类文件夹(因此它会覆盖现有的GuinePig.class文件),当我们执行它时... voila ,输出现在是我们想要的: - )
只是最后一次警告,请记住,此注射器非常简单,不会检查是否已经注射了该类,因此最终可能会进行多次注射。