字节码插桩技术

字节码插桩

我们知道JVM是不能直接执行.java 代码,也不能直接执行.class文件,它只能执行.class 文件中存储的指令码。这就是为什么class需要通过classLoader 装载以后才能运行。基于此机制可否在ClassLoader装载之前拦截修改class当中的内容(jvm 指令码)从而让程序中包含我们的埋点逻辑呢?答案是肯定的,但需要用到两个技术 javaagent与javassist 。前者用于拦截ClassLoad装载,后者用于操作修改class文件。

 

javaagent

javaagent介绍

javaagent 是java1.5之后引入的特性,其主要作用是在class 被加载之前对其拦截,以插入我们的监听字节码

字节码插桩技术_第1张图片

javaagent jar包

javaagent 最后展现形式是一个Jar包,有以下特性:

1.必须 META-INF/MANIFEST.MF中指定Premain-Class 设定启agent启动类。

2.在启类需写明启动方法 public static void main(String arg,)

3.不可直接运行,只能通过 jvm 参数-javaagent:xxx.jar 附着于其它jvm 进程运行。

 

javaagent使用

1、编写agent方法

public class MyAgent {
    public static void premain(String args, Instrumentation instrumentation) throws Exception {
        System.out.println("Hello javaagent permain:"+args);
    }
}

2、添加premain-class参数


    org.apache.maven.plugins
    maven-jar-plugin
    2.2
    
        
            
                ${project.name}
                ${project.version}
                com.javaagent.MyAgent
                true
                true
            
        
        true
    

3、构建打包

字节码插桩技术_第2张图片

4、在任一JAVA应用中 添加jvm 参数并启动 -javaagent:xxx.jarjavaagent META-INF/MANIFEST.MF

参数说明:

Premain-Class:必填,agent启动

classCan-Redefine-Classes:默认为false ,是否允许重新定义

classCan-Retransform-Classes:默认为false,是否允许重置Class,重置后相当于class 从classLoade中清除,下次有需要的时候会重新装载,也会重新走Transformer 流程。

Boot-Class-Path:agent 所依赖的jar 路径,多个用空格分割

创建一个测试类MyAgentTest并运行查看结果

public class MyAgentTest {
    public static void main(String[] args) {
        System.out.println("main");
    }
}
//运行结果:main

添加jvm参数

参数内容:-javaagent:/Users/jinyunlong/IdeaProjects/test-agent/target/test-agent-1.0-SNAPSHOT.jar=123

字节码插桩技术_第3张图片


再次运行测试类MyAgentTest并查看结果

字节码插桩技术_第4张图片

javassist

javassist介绍

javassist是一个开源的分析、编辑和创建Java字节码的类库。其主要的优点,在于简单,而且快速。直接使用java编码的形式,而不需要了解虚拟机指令,就能动态改变类的结构,或者动态生成(注:也可以使用ASM实现,但需要会操作字节码指令,学习使用成本高)

javassist使用

使用javassist需要引入javasssist的jar包,添加内容如下:


    
        org.javassist
        javassist
        3.18.1-GA
    


    
        
            org.apache.maven.plugins
            maven-jar-plugin
            2.2
            
                
                    
                        ${project.name}
                        ${project.version}
                        com.javaagent.MyAgent
                        true
                        true
                        javassist-3.18.1-GA.jar
                    
                
                true
            
        
    

演示插入打印当前时间

创建类MyServer

public class MyServer {
    public Integer sayHello(String name,String message){
        System.out.println("hello");
        return 0;
    }
}

myAgent类

字节码插桩技术_第5张图片

 

创建测试类并调用MyServer中的sayHello方法

字节码插桩技术_第6张图片

演示计算方法调用时间

类MyAgent

public class MyAgent {
    public static void premain(String args, Instrumentation instrumentation) throws Exception {
        instrumentation.addTransformer(new ClassFileTransformer() {
            public byte[] transform(ClassLoader loader, String className, Class classBeingRedefined, ProtectionDomain protectionDomain, 
            byte[] classfileBuffer) throws IllegalClassFormatException {
                if(!"com/javaagent/MyServer".equals(className)){
                    return null;
                }
                try {
                    return buildMonitorClass();
                } catch (Exception e) {
                    e.printStackTrace();
                }
                return null;
            }
        },true);

    }
    private static byte[] buildMonitorClass() throws Exception{
        /**
         * 1、拷贝一个新的方法
         * 2、修改原方法名
         * 3、加入监听代码
         */
        ClassPool pool = new ClassPool();
        pool.appendSystemPath();
        CtClass ctClass = pool.get("com.javaagent.MyServer");
        CtMethod ctMethod = ctClass.getDeclaredMethod("sayHello");
        CtMethod copyMethod = CtNewMethod.copy(ctMethod,ctClass,new ClassMap());
        ctMethod.setName("sayHello$agent");
        copyMethod.setBody("{\n" +
                "    long begin = System.nanoTime();\n" +
                "    try {\n" +
                "        return sayHello$agent($1,$2);\n" +
                "    } finally {\n" +
                "        System.out.println(System.nanoTime() - begin);}\n" +
                "    }");
        ctClass.addMethod(copyMethod);
        return ctClass.toBytecode();
    }
}

修改类MyServer

public class MyServer {

    public Integer sayHello(String name,String message){
        System.out.println("hello name:"+name+",message:"+message);
        return 0;
    }
}

修改测试类并运行

public class MyAgentTest {
    public static void main(String[] args) {
        MyServer myServer = new MyServer();
        myServer.sayHello("paul","1234");
    }
}
//运行结果:
hello name:paul,message:1234
186537

javassist 特殊语法

字节码插桩技术_第7张图片

 

 

转载于:https://www.cnblogs.com/paul-blog/p/11048094.html

你可能感兴趣的:(字节码插桩技术)