探索java agent技术和Javassist使用

Java 字节码技术

一、Java Agent

  1. Javaagent是java1.5以后引入的特性,其主要作用是在class被加载之前对其加载,以插入我们想要修改的代码

  1. 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使用

实例:计算某个方法的执行时间。

  1. 引入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>
  1. 先写一个目标方法
public class UserService {

    public void sayHello(){
        System.out.println("hello");
    }
}
  1. 写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,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();
    }
}
  1. 测试执行

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纳秒

相关文章

jinfo 命令可以用来查看 Java 进程运行的 JVM 参数,命令如下...
原文链接:https://www.cnblogs.com/niejunlei/p/5987611.ht...
java 语言, 开发者不能直接控制程序运行内存, 对象的创建都是...
jvm
1.jvm的简单抽象模型:  2.类加载机制     双亲委派模...
堆外内存JVM启动时分配的内存,称为堆内存,与之相对的,在代...
1.springboot和tomcat2.springcloud的请求如何通过网关鉴权?...