Я должен инструментировать любой заданный код (без прямого изменения данного кода) в начале и в конце каждого потока. Проще говоря, как я могу напечатать что-то в точках входа и выхода любого потока.
Как я могу сделать это с помощью javassist?
Я должен инструментировать любой заданный код (без прямого изменения данного кода) в начале и в конце каждого потока. Проще говоря, как я могу напечатать что-то в точках входа и выхода любого потока.
Как я могу сделать это с помощью javassist?
Вы можете сделать это, создав 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 для оснащения нашего класса Морской свинки (не причиняя ему никакого вреда!) и перехватывая все вызовы методов.
Когда мы перехватываем вызов метода, мы сначала проверяем, является ли объявляющий класс метода классом Thread, если это так, это означает, что мы вызываем метод в объекте Thread. Затем мы переходим к проверке, является ли это одним из двух конкретных методов start
и join
.
Когда происходит один из этих двух случаев, мы используем высокоуровневый API javassist для замены кода. Замену легко заметить в коде, фактический предоставленный код может быть немного сложным, поэтому давайте разделим одну из этих строк, возьмем, например, строку, которая обнаружит запуск потока:
{ System.out.println(\"Detected thread starting with id: \" + ((Thread)$0).getId()); $proceed($$); } "
Подробнее об этих специальных операторах можно прочитать в руководстве по Javassist, раздел 4.2 Изменение метода Тело (ищите подраздел MethodCall, извините, для этого подраздела нет привязки)
Наконец, после всего этого кунг-фу мы записываем байт-код нашего ctClass в папку класса (так что он перезаписывает существующий файл GuinePig.class), и когда мы его выполняем... вуаля теперь результат такой, какой мы хотели :-)
Просто последнее предупреждение. Имейте в виду, что этот инжектор довольно прост и не проверяет, был ли уже внедрен класс, поэтому вы можете получить несколько инъекций.