之前在用到arthas就想到过可以研究下java的agent,这里算是个初入门
首先我们有个应用,需要挂上agent来探测一些事情
比如就是简单的主方法1 2 3 4 5 6 7
| public class Main { public static void main(String[] args) { System.out.println("Hello world!"); Demo.start(); Demo.start2(); } }
|
然后有个Demo类1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| public class Demo { public static void start() { try { System.out.println("Demo starting ..."); Thread.sleep(123); } catch (InterruptedException ignore) { } }
public static void start2() { try { System.out.println("Demo starting 2..."); Thread.sleep(567); } catch (InterruptedException ignore) { } } }
|
然后我们想要像切面一样在start方法前后织入一些前后置的逻辑
我们需要将一个maven的module
然后agent代码是这样, premain 方法会在我们指定挂载agent执行,并且就是入口方法1 2 3 4 5 6 7 8 9
| public class DemoAgent {
public static void premain(String arg, Instrumentation instrumentation) { System.out.println("agent start"); System.out.println("arg is " + arg); instrumentation.addTransformer(new DemoTransformer()); }
}
|
然后这个 transformer 就是我要怎么在目标类里如何织入代码的逻辑1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| public class DemoTransformer implements ClassFileTransformer { private static final String INJECTED_CLASS = "org.example.Demo";
@Override public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException { String realClassName = className.replace("/", "."); if (realClassName.equals(INJECTED_CLASS)) { System.out.println("injected class :" + realClassName); CtClass ctClass; try { ClassPool classPool = ClassPool.getDefault(); ctClass = classPool.get(realClassName);
CtMethod[] declaredMethods = ctClass.getDeclaredMethods(); for (CtMethod declaredMethod : declaredMethods) { System.out.println(declaredMethod.getName() + "method be inject"); declaredMethod.addLocalVariable("time", CtClass.longType); declaredMethod.insertBefore("System.out.println(\"--- start ---\");"); declaredMethod.insertBefore("time = System.currentTimeMillis();"); declaredMethod.insertAfter("System.out.println(\"--- end ---\");"); declaredMethod.insertAfter("System.out.println(\"cost: \" + (System.currentTimeMillis() - time));"); } return ctClass.toBytecode(); } catch (Throwable ignore) { } } return classfileBuffer; } }
|
接下去要在pom.xml里配置如何打包agent的命令1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.5.1</version> <configuration> <source>8</source> <target>8</target> </configuration> </plugin>
<plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-jar-plugin</artifactId> <version>3.2.0</version> <configuration> <archive> <manifest> <addClasspath>true</addClasspath> </manifest> <manifestEntries> <Menifest-Version>1.0</Menifest-Version> <Premain-Class>org.example.DemoAgent</Premain-Class> <Can-Redefine-Classes>true</Can-Redefine-Classes> <Can-Retransform-Classes>true</Can-Retransform-Classes> </manifestEntries> </archive> </configuration> </plugin> </plugins> </build>
|
需要指定 Premain-Class 来指定入口方法所在的类
然后用 mvn package
进行打包
打包后在我们的目标代码的启动vm参数里加上1
| -javaagent:/{path}/demo-agent-1.0-SNAPSHOT.jar
|
{path} 需要替换成agent的jar包所在路径
然后执行目标main方法
就可以看到结果了,这样我们第一个agent的demo就跑起来了