测试团队会做java agent的事,实现测试模拟,各种数据采集等等工作,而这些不需要开发改代码来做到,只需要挂载下agent。

javaagent认识和例子代码

例子:

java -javaagent:/Users/mubi/git_workspace/common1/common-test/target/common-test-1.0-SNAPSHOT-jar-with-dependencies.jar com.test.AgentTest

在这里插入图片描述

不改变代码,而是在java命令后加入-javaagent:xx.jar实现对class的干预

java.lang.instrument

https://docs.oracle.com/javase/8/docs/api/java/lang/instrument/package-summary.html

Provides services that allow Java programming language agents to instrument programs running on the JVM.

即在Java编程语言中,允许Java代理(Agents)进行代码插装(Instrumentation)的服务主要由Java Agent API提供。这个API允许开发者在运行时动态修改或增强Java应用程序的字节码,而无需修改源代码或重新编译。这对于性能监控、调试、安全增强等场景非常有用。

自定义实现一个javaagent

  • MyAgent
package com.test;

import java.lang.instrument.Instrumentation;

/**
 * @Author mubi
 * @Date 2025/2/11 21:45
 */
public class MyAgent {
    /**
     * jvm 参数形式启动,运行此方法
     *
     * @param agentArgs agentArgs 是我们启动 Java Agent 时带进来的参数,比如-javaagent:xxx.jar agentArgs
     * @param inst
     */
    public static void premain(String agentArgs, Instrumentation inst) {
        System.out.println("premain");
        customLogic(inst);
    }

    /**
     * 动态 attach 方式启动,运行此方法
     *
     * @param agentArgs
     * @param inst
     */
    public static void agentmain(String agentArgs, Instrumentation inst) {
        System.out.println("agentmain");
        customLogic(inst);
    }

    /**
     * 打印所有已加载的类名称 修改字节码
     *
     * @param inst
     */
    private static void customLogic(Instrumentation inst) {
        inst.addTransformer(new MyTransformer(), true);
    }
}

  • 使用javaassist改变class文件
package com.test;

import java.io.ByteArrayInputStream;
import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.security.ProtectionDomain;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;

/**
 * javassist 官方文档:http://www.javassist.org/tutorial/tutorial.html
 */
public class MyTransformer implements ClassFileTransformer {

    @Override
    public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined,
                            ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
//        System.out.println("正在加载类:" + className);
        if (!"com/test/Hello".equals(className)) {
            return classfileBuffer;
        }
        CtClass cl = null;
        try {
            ClassPool classPool = ClassPool.getDefault();
            cl = classPool.makeClass(new ByteArrayInputStream(classfileBuffer));

            CtMethod ctMethod = cl.getDeclaredMethod("test");
            System.out.println("获取方法名称:" + ctMethod.getName());
            // 声明本地变量
            ctMethod.addLocalVariable("start", CtClass.longType);
            ctMethod.addLocalVariable("end", CtClass.longType);

            ctMethod.insertBefore("System.out.println(\" 动态插入的打印语句 \");");
            ctMethod.insertBefore("start = System.currentTimeMillis();");
            // $_在Javassist中是一个特殊的变量,代表当前正在处理的类或方法中的表达式
            ctMethod.insertAfter("System.out.println($_);");
            ctMethod.insertAfter("end = System.currentTimeMillis();");
            // 输出耗时
            ctMethod.insertAfter("System.out.println((end-start) + \" ms\");");
            byte[] transformed = cl.toBytecode();
            return transformed;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return classfileBuffer;
    }
}
  • pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>common1</artifactId>
        <groupId>com.container</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>common-test</artifactId>

    <repositories>

    </repositories>

    <build>
        <plugins>
            <plugin>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                </configuration>
            </plugin>

            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-assembly-plugin</artifactId>
                <version>2.5.5</version>
                <executions>
                    <execution>
                        <goals>
                            <goal>attached</goal>
                        </goals>
                        <id>make-assembly</id>
                        <phase>package</phase>
                        <configuration>
                            <descriptorRefs>
                                <descriptorRef>jar-with-dependencies</descriptorRef>
                            </descriptorRefs>
                            <archive>
                                <manifestEntries>
                                    <Premain-Class>com.test.MyAgent</Premain-Class>
                                    <Agent-Class>com.test.MyAgent</Agent-Class>
                                    <Can-Redefine-Classes>true</Can-Redefine-Classes>
                                    <Can-Retransform-Classes>true</Can-Retransform-Classes>
                                </manifestEntries>
                            </archive>
                        </configuration>
                    </execution>
                </executions>
            </plugin>

        </plugins>
    </build>

    <properties>
        <spring.verson>5.1.3.RELEASE</spring.verson>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.javassist</groupId>
            <artifactId>javassist</artifactId>
            <version>3.27.0-GA</version>
        </dependency>

        <dependency>
            <groupId>org.ow2.asm</groupId>
            <artifactId>asm</artifactId>
            <version>9.2</version>
        </dependency>
        <dependency>
            <groupId>org.ow2.asm</groupId>
            <artifactId>asm-commons</artifactId>
            <version>9.2</version>
        </dependency>

        <dependency>
            <groupId>cglib</groupId>
            <artifactId>cglib</artifactId>
            <version>3.3.0</version>
        </dependency>

        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
        </dependency>

    </dependencies>

</project>

即加入:

<plugin>
     <groupId>org.apache.maven.plugins</groupId>
     <artifactId>maven-assembly-plugin</artifactId>
     <version>2.5.5</version>
     <executions>
         <execution>
             <goals>
                 <goal>attached</goal>
             </goals>
             <id>make-assembly</id>
             <phase>package</phase>
             <configuration>
                 <descriptorRefs>
                     <descriptorRef>jar-with-dependencies</descriptorRef>
                 </descriptorRefs>
                 <archive>
                     <manifestEntries>
                         <Premain-Class>com.test.MyAgent</Premain-Class>
                         <Agent-Class>com.test.MyAgent</Agent-Class>
                         <Can-Redefine-Classes>true</Can-Redefine-Classes>
                         <Can-Retransform-Classes>true</Can-Retransform-Classes>
                     </manifestEntries>
                 </archive>
             </configuration>
         </execution>
     </executions>
 </plugin>

按照要求:

An agent JAR file may have both the Premain-Class and Agent-Class attributes present in the manifest. When the agent is started on the command-line using the -javaagent option then the Premain-Class attribute specifies the name of the agent class and the Agent-Class attribute is ignored. Similarly, if the agent is started sometime after the VM has started, then the Agent-Class attribute specifies the name of the agent class (the value of Premain-Class attribute is ignored).

一个代理 JAR 文件可能在清单中同时具有 Premain-Class 和 Agent-Class 属性。 当使用 -javaagent 选项在命令行上启动代理时,Premain-Class 属性指定代理类的名称,而 Agent-Class 属性将被忽略。 同样,如果代理在 VM 启动后的某个时间启动,则 Agent-Class 属性指定代理类的名称(忽略 Premain-Class 属性的值)

打包生成agent jar

在这里插入图片描述

agent jar测试

定义Hello类

package com.test;

import java.util.concurrent.TimeUnit;

/**
 * @Author mubi
 * @Date 2025/2/11 23:20
 */
public class Hello {
    public String test() {
        try {
            TimeUnit.SECONDS.sleep(1);
            System.out.println("hello logic");
        } catch (Exception e) {

        }
        return "hello test";
    }
}

测试类并加上agent参数-javaagent:/Users/mubi/git_workspace/common1/common-test/target/common-test-1.0-SNAPSHOT-jar-with-dependencies.jar

在这里插入图片描述

测试输出如下:

在这里插入图片描述
即完成了代理行为。

回顾javaagent的作用

JVM启动后,JVM会执行指定Agent类的premain()方法,在premain()中可以调用Instrumentation对象的addTransformer方法注册ClassFileTransformer。当JVM加载类时会将类文件的字节数组传递给ClassFileTransformer的transform方法,在transform方法中对Class文件进行解析和修改,之后JVM就会加载转换后的Class文件。

这样实例化后执行代理了的方法就能完成,因为class文件被改过且加载好了。

在这里插入图片描述

项目中用到agent的例子

当一个入口流程开始后,可能因为不同的输入,执行了不同的链路。业务如果有扩展,那么就需要判断是否有执行,以及执行情况。也可以通过设计测试和查看功能,但是肯定不想修改原始代码,所以Java agent 将很适合。

注解看如下 AppProcess的entry方法 的入参不同,调用的扩展点实现也是不同;不用修改代码逻辑,通过java agent 可以采集到信息。
在这里插入图片描述

  • 主程序
package com.pro.biz;

import java.util.ArrayList;
import java.util.List;

public class AppProcess {

    /**
     * 扩展点
     */
    List<StrService> strServiceList;


    public AppProcess() {
        strServiceList = new ArrayList<>();
    }

    public void setStrServiceList(){
        strServiceList.add(new X());
        strServiceList.add(new Y());
    }

    public void entry(String name) {
        String s = null;
        if (name.equals("one")) {
            s = strServiceList.get(0).dealStr(name);
            System.out.println("entry:" + s);
            return;
        }
        if (name.equals("all")) {
            for (StrService strService : strServiceList) {
                s = strService.dealStr(name);
                System.out.println("entry:" + s);
            }
            return;
        }
    }
}
  • 扩展点和实现类
    在这里插入图片描述

  • Agent类

package com.pro.agent;

import java.lang.instrument.Instrumentation;

/**
 * @Author mubi
 * @Date 2025/2/12 22:54
 */
public class StatAgent {
    /**
     * jvm 参数形式启动,运行此方法
     *
     * @param agentArgs agentArgs 是我们启动 Java Agent 时带进来的参数,比如-javaagent:xxx.jar agentArgs
     * @param inst
     */
    public static void premain(String agentArgs, Instrumentation inst) {
//        System.out.println("premain");
        customLogic(inst);
    }

    /**
     * 动态 attach 方式启动,运行此方法
     *
     * @param agentArgs
     * @param inst
     */
    public static void agentmain(String agentArgs, Instrumentation inst) {
//        System.out.println("agentmain");
        customLogic(inst);
    }

    /**
     * 打印所有已加载的类名称 修改字节码
     *
     * @param inst
     */
    private static void customLogic(Instrumentation inst) {
        inst.addTransformer(new StatTransformer(), true);
    }
}

package com.pro.agent;

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

import java.io.ByteArrayInputStream;
import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.security.ProtectionDomain;

/**
 * @Author mubi
 * @Date 2025/2/12 22:55
 */
public class StatTransformer implements ClassFileTransformer {


    @Override
    public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
//        if (!"com/pro/biz/AppProcess".equals(className)) {
//            return classfileBuffer;
//        }
        CtClass cl = null;
        try {
            ClassPool classPool = ClassPool.getDefault();
            cl = classPool.makeClass(new ByteArrayInputStream(classfileBuffer));

            CtMethod ctMethod = null;
            try{
                ctMethod = cl.getDeclaredMethod("dealStr");
            }catch (Exception e){

            }
            if(ctMethod != null && !className.equals("com/pro/biz/StrService")) {
//                System.out.println("获取到方法名称:" + ctMethod.getName());
//                System.out.println(className);

//                ctMethod.insertBefore("System.out.println(\"入参: \" + $1);");
//                ctMethod.insertAfter("System.out.println(\"出参: \" + $_);");

                ctMethod.insertAfter(
                        "com.pro.stat.StrServiceCallStats.addCallInfo($0.getClass().getName(), $1, $_);"
                );

//                StringBuilder methodBody = new StringBuilder();
//                methodBody.append("{")
//                        .append("System.out.println(\"Input: \" + $1);") // input parameter
//                        .append("$r = $proceed($$);") // proceed with the original method
////                        .append("System.out.println(\"Output: \" + result);") // output
//                        .append("return $r;")
//                        .append("}");
//
//                // Modify the method
//                ctMethod.setBody(methodBody.toString());


            }
            // 插入统计代码
//            String insertBeforeCode = "{ $_ = $proceed($$); " +
//                    "StrServiceCallStats.addCallInfo($0.strServiceList.get($index).getClass().getName(), $1, $_); }";
//            entryMethod.insertBefore(insertBeforeCode);

            byte[] transformed = cl.toBytecode();
            return transformed;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return classfileBuffer;
    }
}

  • StrServiceCallStats 采集信息收集类,即采集信息可以收集存储
package com.pro.stat;

import java.util.ArrayList;
import java.util.List;

/**
 * @Author mubi
 * @Date 2025/2/12 23:25
 */
public class StrServiceCallStats {
    static class CallInfo {
        String implementationClass;
        String input;
        String output;

        public CallInfo(String implementationClass, String input, String output) {
            this.implementationClass = implementationClass;
            this.input = input;
            this.output = output;
        }

        @Override
        public String toString() {
            return "Implementation: " + implementationClass + ", Input: " + input + ", Output: " + output;
        }
    }

    private static final List<CallInfo> callInfos = new ArrayList<>();

    public static void addCallInfo(String implementationClass, String input, String output) {
        callInfos.add(new CallInfo(implementationClass, input, output));
    }

    public static void printCallStats() {
        for (CallInfo info : callInfos) {
            System.out.println(info);
        }
    }

    public static void reset(){
        if(callInfos != null && !callInfos.isEmpty()){
            callInfos.clear();
        }
    }
}

  • pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>common1</artifactId>
        <groupId>com.container</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>common-project</artifactId>


    <build>
        <plugins>
            <plugin>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                </configuration>
            </plugin>

            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-assembly-plugin</artifactId>
                <version>2.5.5</version>
                <executions>
                    <execution>
                        <goals>
                            <goal>attached</goal>
                        </goals>
                        <id>make-assembly</id>
                        <phase>package</phase>
                        <configuration>
                            <descriptorRefs>
                                <descriptorRef>jar-with-dependencies</descriptorRef>
                            </descriptorRefs>
                            <archive>
                                <manifestEntries>
                                    <Premain-Class>com.pro.agent.StatAgent</Premain-Class>
                                    <Agent-Class>com.pro.agent.StatAgent</Agent-Class>
                                    <Can-Redefine-Classes>true</Can-Redefine-Classes>
                                    <Can-Retransform-Classes>true</Can-Retransform-Classes>
                                </manifestEntries>
                            </archive>
                        </configuration>
                    </execution>
                </executions>
            </plugin>

        </plugins>
    </build>

    <properties>
        <spring.verson>5.1.3.RELEASE</spring.verson>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.javassist</groupId>
            <artifactId>javassist</artifactId>
            <version>3.29.0-GA</version>
        </dependency>

        <dependency>
            <groupId>org.ow2.asm</groupId>
            <artifactId>asm</artifactId>
            <version>9.2</version>
        </dependency>
        <dependency>
            <groupId>org.ow2.asm</groupId>
            <artifactId>asm-commons</artifactId>
            <version>9.2</version>
        </dependency>

        <dependency>
            <groupId>cglib</groupId>
            <artifactId>cglib</artifactId>
            <version>3.3.0</version>
        </dependency>

        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
        </dependency>

    </dependencies>

</project>

打包后生成agent jar, 运行时加上-javaagent:/Users/mubi/git_workspace/common1/common-project/target/common-project-1.0-SNAPSHOT-jar-with-dependencies.jar

Logo

电影级数字人,免显卡端渲染SDK,十行代码即可调用,工业级demo免费开源下载!

更多推荐