JavaAgent

1.0 先看几个面试题:

  • 怎么获取某个对象的大小?
  • 运行期将已经加载的类的字节码能做变更吗,怎么做?有什么限制?
  • 如何获取所有已经被加载过的类?
  • 怎么在加载java文件之前做拦截把字节码做修改?
  • 如何获取所有已经被初始化过了的类(clinit后的方法)
  • 如何设置某些native方法的前缀?
  • 如何在查找native方法的时候做规则匹配?
  • 如何将某个jar加入到bootstrapclasspath 里作为高优先级被bootstrapClassloader 加载?
  • AppClassloader怎么去加载某个加入到 classpath 里的 jar?

我:…(#$%^&*)

1.1 先从来源说起

JavaAgent 是JDK 1.5 以后引入的,也可以叫做Java代理。

JavaAgent 是运行在 main方法之前的拦截器,它内定的方法名叫 premain ,也就是说先执行 premain 方法然后再执行 main 方法。

主要功能如下:

  • 看问题 1.0

按照加载时机可以分为两种:

  • 进程启动前
  • 进程启动后

1.2 如何实操?

准备工作:

  • 电脑一台、带键盘鼠标显示器,能通电能开机,内存512M,一核CPU,差不多就行
  • 安装 jdk(1.8) ,安装 IDEA(2021.2.4),安装 maven

实施步骤:

  • 首先创建一个 Java Aganet 的 maven 项目,步骤如下:
    打开 IDEA:
  • java 自定义classLoader 加载低版本二方包 javaagent classloader_java

  • 新建项目:

java 自定义classLoader 加载低版本二方包 javaagent classloader_java_02


java 自定义classLoader 加载低版本二方包 javaagent classloader_jdk_03

java 自定义classLoader 加载低版本二方包 javaagent classloader_maven_04

java 自定义classLoader 加载低版本二方包 javaagent classloader_jar_05

走到这一步的小伙伴就可以编写具体的 JavaAgent 的代码了。

java 自定义classLoader 加载低版本二方包 javaagent classloader_jar_06

java 自定义classLoader 加载低版本二方包 javaagent classloader_java_07

具体代码:


package com.carrot.sec;

import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.lang.instrument.Instrumentation;
import java.security.ProtectionDomain;

public class MyAgent {

    public static void premain(String arg, Instrumentation instrumentation){

        System.err.println("MyAgent 开始执行了! , 参数是 " + arg);

        //注册我们的函数
        instrumentation.addTransformer(new ClassFileTransformer() {

            public byte[] transform(
                    ClassLoader loader,
                    String className,
                    Class<?> classBeingRedefined,
                    ProtectionDomain protectionDomain,
                    byte[] classfileBuffer
            ) throws IllegalClassFormatException {

                System.out.println("加载的类名字是: " + className);

                return classfileBuffer;
            }

        });

    }

}

写好了就打包一个 java 的 jar 包。

pom文件增加以下内容:

<build>
        <plugins>

            <!-- jdk 1.8 编译 -->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.1</version>
                <configuration>
                    <source>8</source>
                    <target>8</target>
                </configuration>
            </plugin>

            <!-- java agent -->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-jar-plugin</artifactId>
                <configuration>
                    <archive>
                        <manifestFile>src/main/resources/META-INF/MANIFEST.MF</manifestFile>
                    </archive>
                </configuration>
            </plugin>

        </plugins>
    </build>

执行 maven 打包命令:

java 自定义classLoader 加载低版本二方包 javaagent classloader_加载_08

java 自定义classLoader 加载低版本二方包 javaagent classloader_加载_09

mvn clean package -U -DMaven.test.skip=true

java 自定义classLoader 加载低版本二方包 javaagent classloader_java_10

出现这个错误,不要慌,去 resources 下手动创建 pom 文件里面指定的目录:src/main/resources/META-INF/MANIFEST.MF

java 自定义classLoader 加载低版本二方包 javaagent classloader_maven_11

java 自定义classLoader 加载低版本二方包 javaagent classloader_jdk_12

java 自定义classLoader 加载低版本二方包 javaagent classloader_jar_13

java 自定义classLoader 加载低版本二方包 javaagent classloader_jdk_14

META-INF/MANIFEST.MF 的内容:

Manifest-Version: 1.0
Premain-Class: com.carrot.sec.MyAgent
Can-Redefine-Classes: true
Can-Retransform-Classes: true

属性

说明

是否必填

默认值

Premain-Class

包含premain方法的类

依赖启动方式


Agent-Class

包含agentmain方法的类

依赖启动方式


Boot-Class-Path

启动类加载器搜索路径



Can-Redefine-Classis

是否可以重定义代理所需的类


false

Can-Retransform-Classis

是否能够重新转换此代理所需的类


false

Can-Set-Native-Method-Prefix

是否能够设置此代理所需的本机方法前缀


false

再次打包就可以了。。。

java 自定义classLoader 加载低版本二方包 javaagent classloader_jdk_15

找到 jar 包,检查里面的内容,如果不对就修改一下。

java 自定义classLoader 加载低版本二方包 javaagent classloader_maven_16

java 自定义classLoader 加载低版本二方包 javaagent classloader_maven_17

java 自定义classLoader 加载低版本二方包 javaagent classloader_jar_18


java 自定义classLoader 加载低版本二方包 javaagent classloader_maven_19


java 自定义classLoader 加载低版本二方包 javaagent classloader_jar_20


没啥问题就过了。。。

  • 再建一个普通的项目,我这边比较懒直接建了个 Spring boot 项目。
  • 步骤如下:

java 自定义classLoader 加载低版本二方包 javaagent classloader_加载_21

java 自定义classLoader 加载低版本二方包 javaagent classloader_maven_22

java 自定义classLoader 加载低版本二方包 javaagent classloader_加载_23

java 自定义classLoader 加载低版本二方包 javaagent classloader_加载_24

java 自定义classLoader 加载低版本二方包 javaagent classloader_maven_25

java 自定义classLoader 加载低版本二方包 javaagent classloader_maven_26

java 自定义classLoader 加载低版本二方包 javaagent classloader_jdk_27

java 自定义classLoader 加载低版本二方包 javaagent classloader_java_28

java 自定义classLoader 加载低版本二方包 javaagent classloader_jdk_29

java 自定义classLoader 加载低版本二方包 javaagent classloader_加载_30

java 自定义classLoader 加载低版本二方包 javaagent classloader_jar_31

java 自定义classLoader 加载低版本二方包 javaagent classloader_加载_32

java 自定义classLoader 加载低版本二方包 javaagent classloader_jar_33

IDEA中的VM命令

-javaagent:D:\local-project\FirstAgent\target\FirstAgent-1.0-SNAPSHOT.jar
这些工作做完了之后就可以启动我们的 Spring Boot 项目了。

java 自定义classLoader 加载低版本二方包 javaagent classloader_jdk_34

假如你想用命令来启动,也是可以做到的:

  • 把 Spring Boot 项目打包,和 MyAgent 的打包方式是一样的~~~
  • 找到 Spring Boot 的 jar 包的位置,也找到 MyAgent 的位置。

java 自定义classLoader 加载低版本二方包 javaagent classloader_jdk_35

java 自定义classLoader 加载低版本二方包 javaagent classloader_jdk_36

java 自定义classLoader 加载低版本二方包 javaagent classloader_java_37

  • 执行命令:
java -javaagent:D:\local-project\FirstAgent\target\FirstAgent-1.0-SNAPSHOT.jar -jar demo-0.0.1-SNAPSHOT.jar

更多的用法或者用途,我们一起发现一起创造!
比如:

  • 怎么获取某个对象的大小?
  • 运行期将已经加载的类的字节码能做变更吗,怎么做?有什么限制?
  • 如何获取所有已经被加载过的类?
  • 怎么在加载java文件之前做拦截把字节码做修改?
  • 如何获取所有已经被初始化过了的类(clinit后的方法)
  • 如何设置某些native方法的前缀?
  • 如何在查找native方法的时候做规则匹配?
  • 如何将某个jar加入到bootstrapclasspath 里作为高优先级被bootstrapClassloader 加载?
  • AppClassloader怎么去加载某个加入到 classpath 里的 jar?

这些案例会抽时间逐一做出解答。