背景
Java Agent是一种特殊类型的类,通过使用Java Instrumentation API,可以拦截在JVM上运行的应用程序,修改它们的字节码。Java代理非常强大,但也很危险。
简介
Java Agent 是基于工具的,来自Java平台,存在于Java.lang工具包,它提供了允许代理检测运行在JVM上的程序的服务。这个包非常简单且自包含,因为它包含一对异常类、一个数据类、类定义和两个接口。在这两个接口中,如果我们想要编写Java代理,我们只需要实现classFileTransformer接口。
两种实现方法
静态代理
这意味着我们构建我们的代理,我们将它打包为一个jar文件,当我们启动我们的Java应用程序时,我们传入一个特殊的JVM参数,称为javaagent。然后,我们给它提供代理jar文件在磁盘上的位置。
1.创建一个agent的项目
1.1 创建agent类
1 2 3 4 5 6 7 8 9 10 11
| @Slf4j public class JavaAgent { public static void premain(String agentArgs, Instrumentation instrumentation) { log.info("execute premain args:{}",agentArgs); ClassFileTransformer interceptingClassTransformer = (loader, className, classBeingRedefined, protectionDomain, classfileBuffer) -> { log.info("ClassLoader:{} className:{}",loader,className); return classfileBuffer; }; instrumentation.addTransformer(interceptingClassTransformer,true); } }
|
1.2 创建MATE-INF/MANIFEST.MF文件
1 2 3 4
| Manifest-Version: 1.0 Can-Redefine-Classes: true Can-Retransform-Classes: true Premain-Class: io.demo.agent.JavaAgent
|
1.3 配置pom文件
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
| <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-jar-plugin</artifactId> <version>3.1.0</version> <configuration> <archive> <!--自动添加META-INF/MANIFEST.MF --> <manifest> <addClasspath>true</addClasspath> </manifest> <manifestEntries> <Premain-Class>io.demo.agent.JavaAgent</Premain-Class> <Agent-Class>io.demo.agent.JavaAgent</Agent-Class> <Can-Redefine-Classes>true</Can-Redefine-Classes> <Can-Retransform-Classes>true</Can-Retransform-Classes> </manifestEntries> </archive> </configuration> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <configuration> <source>8</source> <target>8</target> </configuration> </plugin> </plugins> </build>
|
1.4 将agent项目打成jar包
2.创建另一个可运行的项目execute
2.1创建一个可运行的类
1 2 3 4 5 6
| @Slf4j public class Execute { public static void main(String[] args) { log.info("i am execute main"); } }
|
2.2 配置pom文件
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
| <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-shade-plugin</artifactId> <version>3.2.1</version> <executions> <execution> <phase>package</phase> <goals> <goal>shade</goal> </goals> <configuration> <transformers> <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer"> <!--这里写你的main函数所在的类的路径名,也就是Class.forName的那个字符串--> <mainClass>io.demo.execute.Execute</mainClass> </transformer> </transformers> </configuration> </execution> </executions> </plugin> </plugins> </build>
|
2.3 运行命令
1
| java -javaagent:agent-1.0-SNAPSHOT.jar -jar execute-1.0-SNAPSHOT.jar
|
2.4 查看输出
动态代理
与启动应用程序的方式不同,您可以编写一小段代码,获取并连接到现有的JVM,并告诉它加载某个代理。
1 2 3 4 5 6 7 8 9 10 11
| public class DynamicLoadAgent { public static final String AGENT_FILE_PATH = "/Users/biaoyang/IdeaProjects/java-agent/agent/target/agent-1.0-SNAPSHOT.jar"; public static void main(String[] args) throws AgentLoadException, IOException, AgentInitializationException, AttachNotSupportedException { VirtualMachine vm = VirtualMachine.attach("4895"); vm.loadAgent(AGENT_FILE_PATH); vm.detach(); } }
|
这个参数agentFilePath与静态代理方法中的参数完全相同。它必须是agent jar的文件名,因此没有输入流就没有字节。对于这种方法有两个注意事项。第一个这是sun包下的私有API,通常适用于热点实现。第二个是,用java 9,你不能再使用这些代码来连接它运行的JVM。
详解
Premain方法有两个参数:
- agentArgs:字符串参数,用户选择将其作为参数传递给Java代理调用。
- Instrumentation:来自java.lang包,我们可以添加一个新的ClassFileTransformer对象,它包含我们的代理的实际逻辑。
1 2 3 4 5 6 7 8
|
public static void premain(String agentArgs, Instrumentation instrumentation) public static void premain(String agentArgs)
public static void agentmain (String agentArgs, Instrumentation inst) public static void agentmain (String agentArgs)
|
案例
通过javassist实现修改字节码的操作。
1.编写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
| @Slf4j public class JavaAgent { public static void premain(String agentArgs, Instrumentation instrumentation) { log.info("execute premain args:{}", agentArgs); ClassFileTransformer interceptingClassTransformer = (loader, className, classBeingRedefined, protectionDomain, classfileBuffer) -> {
log.info("ClassLoader:{} className:{}", loader, className); if ("io/demo/execute/Execute".equals(className)) { try { log.info("aaaaaaaa"); final ClassPool classPool = ClassPool.getDefault(); log.info("ClassPool={}", classPool); final CtClass clazz = classPool.get("io.demo.execute.Execute"); log.info("clazz={}", clazz); CtMethod convertToAbbr = clazz.getDeclaredMethod("getInfo"); String methodBody = "{return \"aget update\";}"; convertToAbbr.setBody(methodBody); byte[] byteCode = clazz.toBytecode(); clazz.detach(); return byteCode; } catch (Throwable ex) { ex.printStackTrace(); } } return null; }; instrumentation.addTransformer(interceptingClassTransformer); } }
|
2.执行的类
1 2 3 4 5 6 7 8 9 10
| @Slf4j public class Execute { public static void main(String[] args) { log.info("i am execute main"); log.error(getInfo()); } public static String getInfo(){ return "Execute getInfo"; } }
|
3.执行
1
| java -javaagent:agent-1.0-SNAPSHOT.jar -jar execute-1.0-SNAPSHOT.jar
|
4.执行结果
资料
什么是 Java Agent ?: https://www.developer.com/design/what-is-java-agent/
Understanding Java Agents:https://dzone.com/articles/java-agent-1