前言
- javaagent介绍:
javaagent是依赖java底层提供的一个叫instrument的JVMTI Agent。简单来说,javaagent是一个JVM的“插件”。 在java运行命令中 javaagent是一个参数,用来指定agent。
启动时加载的 JavaAgent 是 JDK1.5 之后引入的新特性,此特性为用户提供了在 JVM 将字节码文件读入内存之后,JVM 使用对应的字节流在 Java 堆中生成一个 Class 对象之前,用户可以对其字节码进行修改的能力,从而 JVM 也将会使用用户修改过之后的字节码进行 Class 对象的创建。
- javassit介绍:
Javaassist 就是一个用来 处理 Java 字节码的类库。它可以在一个已经编译好的类中添加新的方法,或者是修改已有的方法,并且不需要对字节码方面有深入的了解。同时也可以去生成一个新的类对象,通过完全手动的方式。
javaassist 就是一个用来处理 Java 字节码的类库。
javassist 也称为动态编译,动态编译技术通过操作 class 文件,动态添加元素或者修改代码。
代码实战
1.创建maven工程、并添加相关依赖
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">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>jdk-agent</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>org.javassist</groupId>
<artifactId>javassist</artifactId>
<version>3.25.0-GA</version>
</dependency>
</dependencies>
</project>
2.编码拦截代码
新建入口类,添加premain方法;
```java
public class AgentDemo {public static void premain(String args, Instrumentation instrumentation) {
System.out.println("coming in"); instrumentation.addTransformer(new ClassConstructorTransform());
}
}
premain会优先于main方法启动;
- 编码class转换代码
```java
public class ClassConstructorTransform implements ClassFileTransformer {
@Override
public byte[] transform(ClassLoader loader,
String className,
Class<?> classBeingRedefined,
ProtectionDomain protectionDomain,
byte[] classfileBuffer) {
byte[] bytes = null;
if(className.equals("com/client/Test")){
System.out.println("...");
ClassPool classPool = ClassPool.getDefault();
try {
System.out.println(classPool);
System.out.println("......");
String name = className.replaceAll("/","\\.");
System.out.println(name);
ClassClassPath classPath = new ClassClassPath(this.getClass());
System.out.println(classPath);
String path = loader.getResource("").getPath();
System.out.println(path);
//classPool.insertClassPath(classPath);
classPool.appendClassPath(new LoaderClassPath(Thread.currentThread().getContextClassLoader()));
CtClass ctClass = classPool.get(name);
System.out.println(ctClass.getDeclaredConstructors().length);
CtConstructor constructor = ctClass.getDeclaredConstructors()[0];
System.out.println(constructor);
constructor.setBody("System.out.println(\"实例化\");");
CtMethod ctMethod = ctClass.getDeclaredMethod("getValue",
new CtClass[]{classPool.get("java.lang.String"),
classPool.get("java.lang.String"),
classPool.get("java.lang.String")});
ctMethod.insertBefore("{ " +
" System.out.println($2); "+
" java.util.Map/*<String, String>*/ map = new java.util.HashMap/*<>*/(); " +
" map.put(\"user_count\",\"50000\");" +
" return (String)map.get($2);" +
"}");
CtMethod ctMethod2 = ctClass.getDeclaredMethod("getLic",
new CtClass[]{classPool.get("java.lang.String")});
ctMethod2.setBody("{ " +
" java.util.Map/*<String, String>*/ map = new java.util.HashMap/*<>*/(); " +
" map.put(\"begin\",\"2000-01-01\");" +
" map.put(\"end\",\"2000-01-01\");" +
" return map;" +
"}");
CtMethod ctMethod3 = ctClass.getDeclaredMethod("getJson");
StringBuffer stringBuffer = new StringBuffer("{");
stringBuffer.append("String body_json = null;" +
" return body_json;");
stringBuffer.append("}");
ctMethod3.setBody(stringBuffer.toString());
bytes = ctClass.toBytecode();
System.out.println("over..");
}catch (Throwable e){
System.err.println(e.getMessage());
e.printStackTrace();
}
}
return bytes;
}
}
主要概念:
在 Javassist 中,类 Javaassit.CtClass 表示 class 文件。一个 CtClass (编译时类)对象可以处理一个 class 文件;。ClassPool 是 CtClass 对象的容器。它按需读取类文件来构造 CtClass 对象,并且保存 CtClass 对象以便以后使用。
CtClass 提供了 getName(),getSuperclass(),getMethods() 等方法来获取类的信息,也提供了修改类定义的方法(添加字段,添加构造函数、添加方法),同时也可以对方法体的语句进行检测。
方法由 CtMethod 对象表示。提供了 insertBefore(),insertAfter() 和 addCatch() 方法。 它们可以将用 Java 编写的代码片段插入到现有方法中。Javassist 包括一个用于处理源代码的简单编译器,它接收用 Java 编写的源代码,并将其编译成 Java 字节码,并内联方法体中。
传递给目标方法的参数使用 $1,$2,... 访问,而不是原始的参数名称。 1 表示第一个参数,2 表示第二个参数,以此类推。 这些变量的类型与参数类型相同。 0 等价于this
指针。 如果方法是静态的,则0 不可用。
3. 打包
在pom.xml中添加打包插件配置;
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<version>3.3.0</version>
<configuration>
<archive>
<manifestEntries>
<Premain-Class>com.jdk.agent.AgentDemo</Premain-Class>
<Can-Redefine-Classes>true</Can-Redefine-Classes>
<Can-Retransform-Classes>true</Can-Retransform-Classes>
</manifestEntries>
</archive>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
</configuration>
<executions>
<execution>
<id>one</id>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
在idea中执行package命令;
4. 使用
适用idea时,在vmoptions参数中添加
-javaagent:**\jdk-agent-1.0-SNAPSHOT.jar
启动jar包时添加javaagent参数:
java -jar -javaagent:**\jdk-agent-1.0-SNAPSHOT.jar your.jar