Как определить, что поток начал использовать javassist?

Я должен инструментировать любой заданный код (без прямого изменения данного кода) в начале и в конце каждого потока. Проще говоря, как я могу напечатать что-то в точках входа и выхода любого потока.

Как я могу сделать это с помощью javassist?


person Curious    schedule 07.07.2015    source источник
comment
Ваш вопрос не совсем ясен... Вы спрашиваете, как определить, что новый объект Thread создается (или запускается) в коде, который вы инструментируете с помощью javassist, это или что-то еще? Я предлагаю вам отредактировать вопрос более подробно! Также предоставляя некоторый код того, что вы пытаетесь сделать. Пожалуйста, посетите stackoverflow.com/help/how-to-ask, чтобы узнать немного больше о том, как задавать вопросы на ТАК   -  person pabrantes    schedule 07.07.2015
comment
Да, это очень похоже на то, что вы поняли. Я обновил вопрос. Пожалуйста, посмотрите.   -  person Curious    schedule 08.07.2015


Ответы (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 для оснащения нашего класса Морской свинки (не причиняя ему никакого вреда!) и перехватывая все вызовы методов.

Когда мы перехватываем вызов метода, мы сначала проверяем, является ли объявляющий класс метода классом Thread, если это так, это означает, что мы вызываем метод в объекте Thread. Затем мы переходим к проверке, является ли это одним из двух конкретных методов start и join.

Когда происходит один из этих двух случаев, мы используем высокоуровневый API javassist для замены кода. Замену легко заметить в коде, фактический предоставленный код может быть немного сложным, поэтому давайте разделим одну из этих строк, возьмем, например, строку, которая обнаружит запуск потока:

{ System.out.println(\"Detected thread starting with id: \" + ((Thread)$0).getId()); $proceed($$); } "

  1. Во-первых, весь код находится внутри фигурных скобок, иначе javassist его не примет
  2. Затем у вас есть System.out, который ссылается на $0. $0 — это специальный параметр, который можно использовать в манипуляциях с кодом javassist и представляет целевой объект вызова метода, в этом случае мы точно знаем, что это будет Thread.
  3. $proceed($$) Это, вероятно, самая сложная инструкция, если вы не знакомы с javassist, так как это весь особый сахар javassist и никакой java. $proceed — это способ, которым вы должны ссылаться на фактический вызов метода, который вы обрабатываете, а $$ ссылается на полный список аргументов, переданный вызову метода. В этом конкретном случае start и join будут иметь этот список пустым, тем не менее, я думаю, что лучше сохранить эту информацию.

Подробнее об этих специальных операторах можно прочитать в руководстве по Javassist, раздел 4.2 Изменение метода Тело (ищите подраздел MethodCall, извините, для этого подраздела нет привязки)

Наконец, после всего этого кунг-фу мы записываем байт-код нашего ctClass в папку класса (так что он перезаписывает существующий файл GuinePig.class), и когда мы его выполняем... вуаля теперь результат такой, какой мы хотели :-)

Просто последнее предупреждение. Имейте в виду, что этот инжектор довольно прост и не проверяет, был ли уже внедрен класс, поэтому вы можете получить несколько инъекций.

person pabrantes    schedule 08.07.2015