java-agent在无侵入监控上很常见,skywalking和Arthas都有用到这项技术。

两类主要的入口:premain与agentmain。前者使用-javaagent指定后执行,后者使用VM api loadAgent后执行。
premian和agentmain都有两种重载:

public static void premain(String agentArgs)
public static void premain(String agentArgs, Instrumentation instrumentation)
public static void agentmain(String agentArgs)
public static void agentmain(String agentArgs, Instrumentation instrumentation)

其中:agentArgs 是跟随 -javaagent:xxx.jar=yyy 传入的 yyy 字符串,而instrumentation就是我们的agent增强应用的关键。本文不会详细介绍instrumentation。Instrumentation javadoc多参方法优先单参方法,同时存在时单参不会生效。
定义HookMain和ClassFileTransformer,前者提供premain和agentmain方法入口,后者提供一个class文件字节码转换器,在premain方法通过instrumentation将该转换器添加进去,ClassFileTransformer的transform可以将byte[]转换为byte[],结合字节码操作库(ASM、Javassist、ByteBuddy)可以方便的对类就行加载时增强。

/**
 * 入口
 */
public class HookMain {
    /**
     * -javaagent:xxx方式 增强
     *
     * @param agentArgs agentArgs 是跟随 -javaagent:xxx.jar=yyy 传入的 yyy 字符串
     * @param instrumentation 主要是调用这个对象的方法增强
     */
    public static void premain(String agentArgs, Instrumentation instrumentation){
        System.out.println("premain ----------------"+agentArgs);
        instrumentation.addTransformer(new ClassExceptionTransformer());
    }

    /**
     * @param agentArgs agentArgs 是跟随 javaagent:xx.jar=yyy 传入的 yyy 字符串
     * @param instrumentation
     */
    public static void agentmain(String agentArgs, Instrumentation instrumentation){
        System.out.println("agentmain ----------------"+agentArgs);
    }
}

/**
 * 输出所有的class name
 * @author lige
 * @date 2021-9-18
 */
public class ClassExceptionTransformer implements ClassFileTransformer {
    /**
     * 这一步需要借助ASM、Javassist、ByteBuddy这样的类库方便操作
     */
    @Override
    public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
        System.out.println("loading " + className);
        return classfileBuffer;
    }
}

上面两个类将HookMain的premain方法会将ClassFileTransformer添加到instrumentation,同时输出信息,ClassExceptionTransformer会输出所有(其实也不是所有,有些基本类比如lang下面的时先于premain加载的:基本环境 > premain > main > agentmain)加载的类名称到控制台。
打包可以借助一个maven插件自动添加META-INF/MANIFEST.MF

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-jar-plugin</artifactId>
                <version>3.1.0</version>
                <configuration>
                    <archive>
                    <!--自动添加META-INF/MANIFEST.MF -->
                    <manifest>
                        <addClasspath>true</addClasspath>
                    </manifest>
                    <manifestEntries>
                        <Premain-Class>com.tomatogames.agent.HookMain</Premain-Class>
                        <Agent-Class>com.tomatogames.agent.HookMain</Agent-Class>
                        <Can-Redefine-Classes>true</Can-Redefine-Classes>
                        <Can-Retransform-Classes>true</Can-Retransform-Classes>
                    </manifestEntries>
                    </archive>
                </configuration>
            </plugin>
        </plugins>
   </build>

效果和你自己写是一样的:

Manifest-Version: 1.0
Premain-Class: com.demo.agent.HookMain
Built-By: lige
Agent-Class: com.demo.agent.HookMain
Can-Redefine-Classes: true
Can-Retransform-Classes: true
Created-By: Apache Maven 3.6.3
Build-Jdk: 1.8.0_291

得到agent.jar之后你有两种方式使用它:(假设你要监控的目标jar是example.jar,应用名称是com.example.demo.DemoApplication)

  • 1.启动时设置-javaagent参数

    java -javaagent:pathto/agent.jar -jar example.jar
  • 2.通过attach手段动态使用

      public static void main(String[] args) throws IOException, AttachNotSupportedException, AgentLoadException, AgentInitializationException {
         //列出所有的虚拟机
         List<VirtualMachineDescriptor> list = VirtualMachine.list();
         //找到应用名为com.example.demo.DemoApplication的
         VirtualMachineDescriptor descriptor = list.stream().filter(i -> "com.example.demo.DemoApplication".equals(i.displayName())).findAny().orElse(null);
         //通过id attach该vm
         VirtualMachine virtualMachine= VirtualMachine.attach(descriptor.id());
         //加载loadAgent并设置参数aaaa
         virtualMachine.loadAgent("pathto/agent-1.0.jar","aaaa");
      }

    注:com.sun.tools.attach.VirtualMachine是 jdk1.8.0_291\lib\tools.jar里面的类如果,找不到你可以将该jar添加到你的项目classpath。

标签: javaagent

仅有一条评论

  1. 带土

    强啊

添加新评论