自学内容网 自学内容网

Java Agent 技术解析

什么是Java Agent

Java Agent是在 JDK1.5 引入的一种可以动态修改 Java 字节码的技术。Java 类编译之后形成字节码被 JVM 执行,在 JVM 在执行这些字节码之前获取这些字节码信息,并且通过字节码转换器对这些字节码进行修改,来完成一些额外的功能。
简单来说,通过使用agent可以实现对java虚拟机进行监控与分析,甚至干预虚拟机的运行。

Instrument

JDK 中提供了一个名为java.lang.instrument的工具包:
在这里插入图片描述
该包的原文介绍如下:
在这里插入图片描述
简单来说,借助该包,开发者可以构建一个独立于应用程序的代理jar包,通过修改方法的字节码来实现监测在 JVM 上运行的程序。

启动代理的方式

1.命令行界面

java启动时通过添加JVM参数 -javaagent启动代理

-javaagent:<jarpath>[=<options>]

jarpath指定代理的jar包路径
jar包的manifest文件中需要包含属性Premain-Class指定代理类入口
代理类必须实现premain方法,在JVM初始化后会调用premain方法,premain的定义如下

public static void premain(String agentArgs, Instrumentation inst)
public static void premain(String agentArgs)

JVM将首先寻找第一个,如果没有发现第一个,再寻找第二个

2. 运行时附加

通过attach机制,允许工具附加到正在运行的应用程序,并启动将工具的代理加载到正在运行的应用程序中。
jar包的manifest文件中需要包含属性Agent-Class指定代理类入口
代理类必须实现agentmain方法,agentmain的定义如下

public static void agentmain(String agentArgs, Instrumentation inst)
public static void agentmain(String agentArgs)

3. 与应用程序一起打包

代理可以与应用程序一起被打包到一个可执行的JAR文件中
jar包的manifest文件中需要包含属性Launcher-Agent-Class指定在主程序被调用前将会执行的代理类

manifest属性介绍

Premain-Class: 包含 premain 方法的类(类的全路径名)
Agent-Class: 包含 agentmain 方法的类(类的全路径名)
Launcher-Agent-Class: 包含 agentmain 方法的类(类的全路径名)
Boot-Class-Path: 设置引导类加载器搜索的路径列表。查找类的特定于平台的机制失败后,引导类加载器会搜索这些路径。按列出的顺序搜索路径。列表中的路径由一个或多个空格分开。路径使用分层 URI 的路径组件语法。如果该路径以斜杠字符(“/”)开头,则为绝对路径,否则为相对路径。相对路径根据代理 JAR 文件的绝对路径解析。忽略格式不正确的路径和不存在的路径。如果代理是在 VM 启动之后某一时刻启动的,则忽略不表示 JAR 文件的路径。(可选)
Can-Redefine-Classes: true表示能重定义此代理所需的类,默认值为 false(可选)
Can-Retransform-Classes: true 表示能重转换此代理所需的类,默认值为 false (可选)
Can-Set-Native-Method-Prefix: true表示能设置此代理所需的本机方法前缀,默认值为 false(可选)

修改流程

在这里插入图片描述

  1. JVM启动
  2. 创建InstrumentationImpl对象
  3. 加载类
  4. 监听ClassFileLoadHook事件
  5. 调用InstrumentationImpl的loadClassAndCallPremain方法,调用指定的premain方法
  6. premain方法中添加实现ClassFileTransformer的自定义转换器实现修改字节码
  7. 重新加载修改后的class

demo实现

实现ClassFileTransformer

定义MyTransformer 类,重写transform方法,对业务类方法进行改写

package com.leon;

import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;

import java.lang.instrument.ClassFileTransformer;
import java.security.ProtectionDomain;

public class MyTransformer implements ClassFileTransformer {

    @Override
    public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined,
                            ProtectionDomain protectionDomain, byte[] classfileBuffer) {
        // 检查是否是我们想要修改的类
        if (!className.equals("com/leon/BusinessService")) {
            return null; //不是我们关心的类,直接返回原始字节码
        }

        try {
            //借助JavaAssist工具,进行字节码插桩
            ClassPool pool = ClassPool.getDefault();
            CtClass cc = pool.get("com.leon.BusinessService");
            CtMethod personFly = cc.getDeclaredMethod("doBusiness");

            //在目标方法前后,插入代码
            personFly.insertBefore("System.out.println(\"--- before doBusiness ---\");");
            personFly.insertAfter("System.out.println(\"--- after doBusiness ---\");");

            return cc.toBytecode();

        } catch (Exception ex) {
            ex.printStackTrace();
        }

        return null;
    }
}

定义MyAgent

MyAgent中实现premain方法,加入自定义转换器

package com.leon;

import java.lang.instrument.Instrumentation;

public class MyAgent {

    public static void premain(String agentArgs, Instrumentation inst) {
        System.out.println("agentArgs : " + agentArgs);
        //加入自定义转换器
        inst.addTransformer(new MyTransformer(), true);
    }
}

MANIFEST.MF

pom中定义自动生成MANIFEST.MF文件,定义Premain-Class入口类

<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>com.leon.MyAgent</Premain-Class>
                            <Can-Redefine-Classes>true</Can-Redefine-Classes>
                            <Can-Retransform-Classes>true</Can-Retransform-Classes>
                        </manifestEntries>
                    </archive>
                </configuration>
            </plugin>

业务类

编写一个业务类

package com.leon;

public class BusinessService {

    public void doBusiness () {

        try {
            System.out.println("doing business"); //模拟业务操作
            Thread.sleep(1000L);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

不使用agent时运行

主函数调用业务服务

package com.leon;

public class Main {
    public static void main(String[] args) {
        BusinessService service = new BusinessService();
        while (true) {
            service.doBusiness();
        }
    }
}

打印如下:
只执行业务类
在这里插入图片描述

使用agent运行

启动时添加-javaagent参数
在这里插入图片描述

打印如下:
成功执行修改后的代码
在这里插入图片描述


原文地址:https://blog.csdn.net/leonhongliang806/article/details/142873645

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!