• 怎么javaagent简单使用: 为类对象添加toString方法


    简单来说,Javaagent可以让我们在不修改程序代码的前提下通过Instrumentation API改变运行中的java程序。

    当Java虚拟机启动时,在执行 main 函数之前,JVM会先运行-javaagent所指定jar包内Premain-Class这个类的premain方法。

    这个premain方法应该怎么写呢?这就是我们要讲的。

    前言

    基础描述什么的我就懒得复制粘贴了,找了俩博文放在这里,先粗略了解一下。

    基础案例:重写Test对象的main方法

    假设有 demo.Test 如下,我们要将main方法里的 sayHello("java"); 改为 sayHello("修改过后的java");

    package demo;
    
    public class Test{
    
        String aa = "value-aa";
        String bb = "value-bb";
    
        public static void main(String[] args) {
            System.out.println(new Test());
            sayHello("java");
        }
    
        public static void sayHello(String name) {
            System.out.println("Hello, " + name);
        }
    }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    我们采取最傻瓜的办法,自己重新再写一个Test类,然后编译成字节码,当需要的时候直接将编译好的内容给上。

    生成字节码

    编译写好的 demo.Test ,生成 demo.Test.class ,为了方便区别,重命名为 Test.class.modified

    以下为具体实现:

    package demo;
    
    public class Test{
    
        String aa = "value-aa";
        String bb = "value-bb";
    
        public static void main(String[] args) {
            System.out.println(new Test());
            sayHello("修改过后的java");
        }
    
        public static void sayHello(String name) {
            System.out.println("Hello, " + name);
        }
    }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    创建agent

    如果待处理的类是 demo.Test ,那么提供修改后的字节码;否则原封不动。

    public class MyAgent {
        public static void premain(String args, Instrumentation inst) {
            // args 是命令行的入参
            inst.addTransformer(new Transformer(args));
        }
    
        private static class Transformer implements ClassFileTransformer {
    
            public Transformer(String args) {
            }
    
            static byte[] readAll(InputStream in) throws IOException {
                ByteArrayOutputStream out = new ByteArrayOutputStream();
                byte[] buffer = new byte[1024];
                for (int len = 0; (len = in.read(buffer)) != -1;) {
                    out.write(buffer, 0, len);
                }
                in.close();
                return out.toByteArray();
            }
    
            @Override
            public byte[] transform(ClassLoader loader, String className, Class classBeingRedefined,
                    ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
                //System.out.println(className);
                // 转换指定包名开头的类
                if (className.equals("demo/Test")) {
                    try {
                        System.out.println("demo/test被修改");
                        return readAll(MyAgent.class.getResourceAsStream("/resources/Test.class.modified"));
                    } catch (Throwable t) {
                        t.printStackTrace();
                        return classfileBuffer;
                    }
                } else {
                    return classfileBuffer;
                }
    
            }
        }
    }
    • 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
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41

    创建MANIFEST.MF

    创建MANIFEST.MF,指定上面premain方法所在的类

    为了方便,我把agent和待测试的都打包成一个 test.jar ,所以下面还多了一行 Main-Class: demo.Test

    Manifest-Version: 1.0
    Class-Path: .
    Main-Class: demo.Test
    Premain-Class: demo.javaagent.MyAgent
    Can-Redefine-Classes: true
    • 1
    • 2
    • 3
    • 4
    • 5

    测试效果

    > java -jar test.jar
    [email protected]
    Hello, java
    
    > java -javaagent:test.jar=args -jar test.jar
    demo/test被修改
    [email protected]
    Hello, 修改过后的java
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    基础案例:添加对象的toString方法

    Test对象并没有重载Object的toString方法,所以我们打印的时候会直接出现内存地址。

    我们期望的Test应该如下:

    package demo;
    
    public class Test{
    
        String aa = "value-aa";
        String bb = "value-bb";
    
        public String toString() {
            StringBuilder sb =new StringBuilder("Test(");
            sb.append("aa").append("=").append(aa).append(",");
            sb.append("bb").append("=").append(bb).append(",");
            sb.append(")");
            return sb.toString();
        }
    
        public static void main(String[] args) {
            System.out.println(new Test());
            sayHello("java");
        }
    
        public static void sayHello(String name) {
            System.out.println("Hello, " + name);
        }
    }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    现在我们换个方法,不再死死的自己去重写代码然后编译,而是使用javaassisit去实现agent。

    public class MyAgentJavaAssist {
        public static void premain(String args, Instrumentation inst) {
            // args 是命令行的入参
            inst.addTransformer(new Transformer(args));
        }
    
        private static class Transformer implements ClassFileTransformer {
    
            public Transformer(String args) {
            }
    
            @Override
            public byte[] transform(ClassLoader loader, String className, Class classBeingRedefined,
                    ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
                try {
                    ClassPool pool = ClassPool.getDefault();
                    ByteArrayInputStream in = new ByteArrayInputStream(classfileBuffer);
                    CtClass cc = pool.makeClass(in);
                    // 判断有无声明 toString() 方法,没有的话就生成一个
                    try {
                        cc.getDeclaredMethod("toString", new CtClass[] {});
                    } catch (NotFoundException e) {
                        CtMethod cm = new CtMethod(pool.getCtClass("java.lang.String"), "toString", new CtClass[] {}, cc);
                        StringBuilder sBody = new StringBuilder();
                        sBody.append("{StringBuilder sb =new StringBuilder(\"").append(cc.getSimpleName()).append("(\");");
                        CtField[] cfs = cc.getDeclaredFields();
                        for (CtField cf : cfs) {
                            sBody.append("sb.append(\"").append(cf.getName()).append("\").append(\"=\").append(")
                                    .append(cf.getName()).append(").append(\",\");");
                        }
                        sBody.append("sb.append(\")\");").append("return sb.toString();}");
                        cm.setBody(sBody.toString());
                        cc.addMethod(cm);
                    }
                    // 判断是否是 demo/Test, 是的话修改 main方法
                    if (className.equals("demo/Test")) {
                        CtMethod cm = cc.getDeclaredMethod("main",
                                new CtClass[] { pool.getCtClass("[Ljava.lang.String;") });
                        cm.setBody("{System.out.println(new demo.Test());sayHello(\"修改过后的java\");}");
                    }
                    return cc.toBytecode();
                } catch (Throwable t) {
                    t.printStackTrace();
                    return classfileBuffer;
                }
    
            }
        }
    }
    • 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
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49

    测试效果

  • 相关阅读:
    Springboot开发Webservice服务端和客户端
    javaWeb监听器Listener(一)
    动手学深度学习_目标检测
    常见web信息泄露
    UTS 数据库同步原理
    企业级GIT分支管控方案
    扬帆际海:shopee跨境电商客服回复流程
    想参加专转本,线上课怎样呀?
    web阶段javascript
    【算法题】2873. 有序三元组中的最大值 I
  • 原文地址:https://blog.csdn.net/m0_62051288/article/details/126460239