什么是javaagent
简单来说, javaagent 是在class 被装在到ClassLoader之前对其拦截,插入自定义的监听字节码,可实现零侵入的监控,是APM的核心技术
Java1.5之后引入的特性
JavaAgent 运行在 main方法之前 ,内置的方法名为premain,即先执行premain方法,然后再执行main方法。通过premain方法,可实现一个JavaAgent。
javaagent 应用场景:监控、代码覆盖率分析 、JProfiler、应用破解等等等
javaagent的jar包 和 普通jar包的区别
javaagent 其实就是一个jar 包,通过-javaagent:xxx.jar 引入监控目标应用。那这个jar 和普通的jar 的区别在哪里呢?
我们来先看个结论
从零搭建第一个javaagent
maven搭建 编译
【pom.xml】
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.artisan</groupId> <artifactId>javaagent</artifactId> <version>1.0-SNAPSHOT</version> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-jar-plugin</artifactId> <version>2.2</version> <configuration> <archive> <manifestEntries> <Project-name>${project.name}</Project-name> <Project-version>${project.version}</Project-version> <Premain-Class>com.artisan.ssist.JavaAgentDemo</Premain-Class> <Boot-Class-Path>javassist-3.18.1-GA.jar</Boot-Class-Path> <Can-Redefine-Classes>false</Can-Redefine-Classes> </manifestEntries> </archive> <skip>true</skip> </configuration> </plugin> </plugins> </build> <dependencies> <dependency> <groupId>org.javassist</groupId> <artifactId>javassist</artifactId> <version>3.18.1-GA</version> </dependency> </dependencies> </project>
- Premain-Class:指定包含 premain 方法的类名 ,改成自己的类
- Can-Redefine-Class:是否能重新定义此代理所需的类,默认为 false。
【Agent Code】
package com.artisan.ssist; import java.lang.instrument.Instrumentation; public class JavaAgentDemo { public static void premain(String args ,Instrumentation instrumentation){ System.out.println("premain first agent demo"); } }
【编译成jar】
点击 M, 执行 mvn clean package
查看jar包中的 MANIFEST.MF文件 , MANIFEST.MF文件用于描述Jar包的信息,例如指定入口函数等。
【引入agent jar 为当前应用启动前插入premain逻辑】
jvm参数指定
-javaagent:E:\IdeaProjects\javaagent\target\javaagent-1.0-SNAPSHOT.jar
执行,观察我们引入的这个jar包中的premain方法是否优先于这个测试类的main方法执行
OK ,这个就是Java Agent的 简单小栗子, 更强大的功能继续开篇
javaagent 流程示意图
进阶Demo
public class AgentMain { public static void premain(String args, Instrumentation instrumentation) throws Exception, ClassNotFoundException { // 实例化对象 UserService userService = new UserService(); // 类比ClassLoader ClassPool classPool = new ClassPool(); // 追加系统ClassLoader classPool.appendSystemPath(); // 获取一个类 CtClass ctClass = classPool.get("com.artisan.agent.UserService"); // 获取方法 CtMethod sayHello = ctClass.getDeclaredMethod("sayHello"); // 在方法执行之后插入下面这行语句 sayHello.insertAfter("System.out.println(\"I am fine\");"); // 重新定义一个类 instrumentation.redefineClasses(new ClassDefinition(UserService.class,ctClass.toBytecode())); // 调用服务 这里的userservice 已经是被重新定义的 对象了 userService.sayHello(); }
总结
1.instrumentation addTransformer 类装载拦截
2.只能拦截未装载过的类
3.instrumentation#retransformClasses方法 重新装载类 ,必须开启相关参数
4.instrumentation.redefineClasses 重新定义一个类 ,不能添加新方法 ,必须开启相关参数
开启参数
agent 依懒包逗号分割 Boot-Class-Path: javassist-3.18.1-GA.jar 是否允许重定义 Can-Redefine-Classes: true 允许重载 Can-Retransform-Classes:true
Javassist 引入
既然是搞字节码,有没有类库 ?
其实上面的栗子 其实已经使用了Javassist 类库了~
Javassist是一个开源的分析、编辑和创建Java字节码的类库。
关于java字节码的处理, 目前有很多开源工具可用,比如asm,bcel, 不过这些都需要直接跟虚拟机指令打交道,实在是太难。。。。。
如果不想了解虚拟机指令,可以采用javassist。
javassist是jboss的一个子项目,优点简单 快速 ,直接使用java编码的形式,而不需要了解虚拟机指令,就能动态改变类的结构,或者动态生成类。