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

javaagent简单使用:为类对象添加toString方法简单来说,Javaagent可以让我们在不修改程序代码的前提下通过Instrumentation API改变运行中的java程序。

欢迎大家来到IT世界,在知识的湖畔探索吧!

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

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

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

前言

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

  • 什么是JavaAgent?
  • Javaagent使用指南

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

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

<pre class="prettyprint hljs cs" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">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);
    }
}</pre>

欢迎大家来到IT世界,在知识的湖畔探索吧!

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

生成字节码

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

以下为具体实现:

欢迎大家来到IT世界,在知识的湖畔探索吧!<pre class="prettyprint hljs cs" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">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);
    }
}</pre>

创建agent

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

<pre class="prettyprint hljs cs" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">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;
            }

        }
    }
}</pre>

创建MANIFEST.MF

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

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

欢迎大家来到IT世界,在知识的湖畔探索吧!<pre class="prettyprint hljs yaml" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">Manifest-Version: 1.0
Class-Path: .
Main-Class: demo.Test
Premain-Class: demo.javaagent.MyAgent
Can-Redefine-Classes: true</pre>

测试效果

<pre class="prettyprint hljs xml" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">> java -jar test.jar
<a href="/cdn-cgi/l/email-protection" data-cfemail="a0c4c5cdcf8ef4c5d3d4e09795c29894c39992">[email protected]</a>
Hello, java

> java -javaagent:test.jar=args -jar test.jar
demo/test被修改
<a href="/cdn-cgi/l/email-protection" data-cfemail="640001090b4a30011710245351065c50075d56">[email protected]</a>
Hello, 修改过后的java</pre>

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

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

我们期望的Test应该如下:

<pre class="prettyprint hljs gradle" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">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);
    }
}</pre>

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

<pre class="prettyprint hljs gradle" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">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;
            }

        }
    }
}</pre>

测试效果

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

来源: https://nicelee.top/blog/2022/08/05/java-agent/

免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://itzsg.com/36504.html

(0)

相关推荐

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

联系我们YX

mu99908888

在线咨询: 微信交谈

邮件:itzsgw@126.com

工作时间:时刻准备着!

关注微信