探索java agent技术和Javassist使用
Java 字节码技术
一、Java Agent
- Javaagent是java1.5以后引入的特性,其主要作用是在class被加载之前对其加载,以插入我们想要修改的代码
- javaagent最后展示形式是一个jar包,具有以下特性
- 必须在META-INF/MANIFEST.MF中指定Premain-Class设定agent启动类
- 在agent启动类中写启动方法public static void premain(String agentArgs, Instrumentation inst){}
- 不可直接运行,只能通过jvm参数-javaagent:xxx.jar附着于其他JVM进程运行
3.入门实例
3.1 编写Agent启动类
public class TestAgent {
public static void premain(String agentArgs, Instrumentation inst){
System.out.println("premain start");
}
}
3.2 编写pomx.xml文件并将Agent启动类打包成jar文件
<build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-jar-plugin</artifactId> <version>2.3.1</version> <configuration> <archive> <manifest> <addClasspath>true</addClasspath> </manifest> <manifestEntries> <Premain-Class>com.mmc.agent.TestAgent</Premain-Class> </manifestEntries> </archive> </configuration> </plugin> </plugins> </build>
使用package命令打包。
3.3 编写一个main方法,并运行
public class Main {
public static void main(String[] args) {
System.out.println("start");
}
}
增加启动参数:-javaagent:D:\webProjects\dubbo-master\dubbo-study-parent\java-agent\target\java-agent-1.0-SNAPSHOT.jar
路径为第二步打包的jar包的文件路径
3.4 运行情况
premain start
start
二、Javassist使用
javassist是一个修改字节码的工具库。可以拿来配合JavaAgent使用
实例:计算某个方法的执行时间。
- 引入pom文件
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.javassist</groupId>
<artifactId>javassist</artifactId>
<version>3.24.1-GA</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>2.3.1</version>
<configuration>
<archive>
<manifest>
<addClasspath>true</addClasspath>
</manifest>
<manifestEntries>
<Premain-Class>com.mmc.agent.TestAgent</Premain-Class>
<Can-Redefine-Classes>true</Can-Redefine-Classes>
<Can-Retransform-Classes>true</Can-Retransform-Classes>
<Boot-Class-Path>javassist-3.24.1-GA.jar</Boot-Class-Path>
</manifestEntries>
</archive>
</configuration>
</plugin>
</plugins>
</build>
- 先写一个目标方法
public class UserService {
public void sayHello(){
System.out.println("hello");
}
}
- 写Agent方法
public class TestAgent {
public static void premain(String agentArgs, Instrumentation inst) throws NotFoundException, UnmodifiableClassException, ClassNotFoundException, CannotCompileException, IOException {
inst.addTransformer(new ClassFileTransformer() {
@Override
public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
if(!"com/mmc/agent/UserService".equals(className)){
return null;
}else {
byte[] bytes= new byte[0];
try {
bytes = buildMonitorClass();
} catch (NotFoundException e) {
e.printStackTrace();
} catch (CannotCompileException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return bytes;
}
}
},true);
}
public static byte[] buildMonitorClass() throws NotFoundException, CannotCompileException, IOException {
/**
* 1. 拷贝一个新方法
* 2. 修改原方法名称
* 3. 加监听代码
*/
ClassPool classPool=new ClassPool();
classPool.appendSystemPath();
CtClass ctClass = classPool.get("com.mmc.agent.UserService");
CtMethod ctMethod = ctClass.getDeclaredMethod("sayHello");
CtMethod newMethod = CtNewMethod.copy(ctMethod, ctClass, new ClassMap());
ctMethod.setName("sayHello$agent");
newMethod.setBody("{ long start=System.nanoTime();\n" +
" try{\n" +
" sayHello$agent();\n" +
" }finally {\n" +
" System.out.println(\"执行耗时:\"+(System.nanoTime()-start)+\"纳秒\");\n" +
" }}");
ctClass.addMethod(newMethod);
return ctClass.toBytecode();
}
}
- 测试执行
Main方法参数要加如下:
-javaagent:D:\webProjects\dubbo-master\dubbo-study-parent\java-agent\target\java-agent-1.0-SNAPSHOT.jar
public class Main {
public static void main(String[] args) {
System.out.println("start");
new UserService().sayHello();
}
}
执行结果:
start
hello
执行耗时:70300纳秒