Спасибо, что подняли этот вопрос, чтобы дать мне возможность взглянуть на Java Instrumentation.
Потратив некоторое время на перекрестную проверку образцов кода и предоставленного учебного пособия. Проблема не в кодах программы, а в способе запуска вашей программы.
Если вы добавите несколько регистраторов в метод transform () в Transformer.java, вы обнаружите, что путь к коду не работает после выполнения:
ClassPool cp = ClassPool.getDefault();
И после замены перехвата исключениякод в том же методе от:
} catch (Exception e) {
до:
} catch (NotFoundException | CannotCompileException | IOException e) {
Это даст вам больше подсказок, как показано ниже:
Exception in thread "main" java.lang.reflect.InvocationTargetException
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
at java.lang.reflect.Method.invoke(Unknown Source)
at sun.instrument.InstrumentationImpl.loadClassAndStartAgent(Unknown Source)
at sun.instrument.InstrumentationImpl.loadClassAndCallPremain(Unknown Source)
Caused by: java.lang.NoClassDefFoundError: javassist/NotFoundException
at doeke.static_agent.Static_Agent.transform(Static_Agent.java:60)
at doeke.static_agent.Static_Agent.transformClass(Static_Agent.java:40)
at doeke.static_agent.Static_Agent.premain(Static_Agent.java:28)
... 6 more
Caused by: java.lang.ClassNotFoundException: javassist.NotFoundException
at java.net.URLClassLoader.findClass(Unknown Source)
at java.lang.ClassLoader.loadClass(Unknown Source)
at sun.misc.Launcher$AppClassLoader.loadClass(Unknown Source)
at java.lang.ClassLoader.loadClass(Unknown Source)
... 9 more
FATAL ERROR in native method: processing of -javaagent failed
До этого момента,коренная причина более очевидна. Это происходит потому, что при запуске программы эти соответствующие классы javassist (например, ClassPool, CtClass, CtMethod и т. Д.) Не могут ссылаться на соответствующие библиотеки во время выполнения.
Итак, решение - :
при условии, что вы экспортировали static_agent.jar в ту же папку «build», что и application.jar
все остальные структуры папок остаются такими же, как показано в предоставленном вами github
давайте "cd" для build папка в командной консоли
исправление исходного сценария запуска программы, как показано ниже
ОС Windows:
java -javaagent:static_agent.jar="doeke.application.TestApplication;test" -cp ../libs/javassist-3.12.1.GA.jar;application.jar doeke.application.TestApplication
Unix/ ОС Linux:
java -javaagent:static_agent.jar="doeke.application.TestApplication;test" -cp ../libs/javassist-3.12.1.GA.jar:application.jar doeke.application.TestApplication
Вы, наконец, получите ожидаемый результат:
[Agent] In premain method.
>> doeke.application.TestApplication
>> test
[Agent] Transforming class
--- start ---
0
[Application] Withdrawal operation completed in:0 seconds!
1
[Application] Withdrawal operation completed in:0 seconds!
РЕДАКТИРОВАТЬ
Кроме того, позвольтеЯ вставил несколько кодов, касающихся того, как вставлять коды в середине метода через javassist.
В случае, если метод test () в TestApplication.java был изменен на:
line 30 public static void test() {
line 31 System.out.println(count++);
line 32
line 33 System.out.println("Last line of test() method");
line 34 }
Предположим, чтомы хотим добавить строку между count и ========= , скажем, «Это разделитель строк», результат которого будет выглядеть какe:
1
-- This is line separator --
Last line of test() method
Затем, в методе transform (...) Transformer.java, вы можете добавить строку кода следующим образом:
m.insertAt(32,"System.out.println(\"-- This is line separator --\");");
, что делает его:
@Override
public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined,
ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
byte[] byteCode = classfileBuffer;
String finalTargetClassName = this.targetClassName.replaceAll("\\.", "/");
if (!className.equals(finalTargetClassName)) {
return byteCode;
}
if (className.equals(finalTargetClassName) && loader.equals(targetClassLoader)) {
System.out.println("[Agent] Transforming class TestApplication");
try {
// Step 1 Preparation
ClassPool cp = ClassPool.getDefault();
CtClass cc = cp.get(targetClassName);
CtMethod m = cc.getDeclaredMethod(targetMethodName);
// Step 2 Declare variables
m.addLocalVariable("startTime", CtClass.longType);
m.addLocalVariable("endTime", CtClass.longType);
m.addLocalVariable("opTime", CtClass.longType);
// Step 3 Insertion of extra logics/implementation
m.insertBefore("startTime = System.currentTimeMillis();");
m.insertAt(32,"System.out.println(\"-- This is line separator --\");");
StringBuilder endBlock = new StringBuilder();
endBlock.append("endTime = System.currentTimeMillis();");
endBlock.append("opTime = (endTime-startTime)/1000;");
endBlock.append("System.out.println(\"[Application] Withdrawal operation completed in:\" + opTime + \" seconds!\");");
m.insertAfter(endBlock.toString());
// Step 4 Detach from ClassPool and clean up stuff
byteCode = cc.toBytecode();
cc.detach();
} catch (NotFoundException | CannotCompileException | IOException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
}
return byteCode;
}
Наконец, будет получен результат, подобный приведенному ниже, при печати кода в середине метода:
[Agent] In premain method.
className=doeke.application.TestApplication
methodName=test
>> doeke.application.TestApplication
>> test
[Agent] Transforming class TestApplication
--- start ---
0
-- This is line separator --
=========
[Application] Withdrawal operation completed in:0 seconds!
1
-- This is line separator --
=========
[Application] Withdrawal operation completed in:0 seconds!
2
-- This is line separator --
=========
[Application] Withdrawal operation completed in:0 seconds!