javaagent简介
javaagent是一种能够在不影响正常编译的情况下,修改字节码。java作为一种强类型的语言,不通过编译就不能能够进行jar包的生成。而有了javaagent技术,就可以在字节码这个层面对类和方法进行修改。同时,也可以把javaagent理解成一种代码注入的方式。但是这种注入比起spring的aop更加的优美。
javaagent主要作用
- 可以在加载java文件之前做拦截把字节码做修改
- 可以在运行期将已经加载的类的字节码做变更,但是这种情况下会有很多的限制,后面会详细说
还有其他的一些小众的功能 - 获取所有已经被加载过的类
- 获取所有已经被初始化过了的类(执行过了clinit方法,是上面的一个子集)
- 获取某个对象的大小
- 将某个jar加入到bootstrapclasspath里作为高优先级被bootstrapClassloader加载
- 将某个jar加入到classpath里供AppClassload去加载
- 设置某些native方法的前缀,主要在查找native方法的时候做规则匹配
javaagent的使用
通过 -javaagent:xxx.jar=name=lisi&age=30 其中 xxx.jar 指定对应要加载的jar包的名字和路径,后面跟踪自己传入的参数即可
IDEA中传入参数可以参考下图
新建agent
package com.mergades.apm.javaagent;
import java.lang.instrument.Instrumentation;
/**
* https://blog.csdn.net/fd2025/article/details/80280033
*/
public class HelloAgent {
public static void premain(String arg, Instrumentation instrumentation) {
System.out.println("装载成功 方法 premain 参数:" + arg);
}
}
maven pom文件引入对应的jar包插件,指定mainfestEntries,指定自己的jar包路径
org.apache.maven.plugins
maven-jar-plugin
2.6
${project.name}
${project.version}
com.mergades.apm.javaagent.HelloAgent
true
org.apache.maven.plugins
maven-compiler-plugin
2.3.2
8
utf8
mvn install 打包
运行引入
编写单元测试
package com.mergades.apm.javaagent;
public class HelloAgentTest {
public static void main(String[] args) {
System.err.println("TestHelloAgent main 方法");
}
}
单元测试run config引入自己的agent jar,直接运行,获取agent植入的输出结果。
装载成功 方法 premain 参数:name=lisi&age=30
TestHelloAgent main 方法
Agentjar和普通jar的区别
运行时jar | agent | |
---|---|---|
入口方法名称 | Main | premain |
Maninfe.MF 主要参数 | Main-class | Premain-Class |
启动参数 | java -jar xxx.jar | -javaagent:xxx.jar |
执行顺序 | 先 | |
是否独立启动 | 是 | 否 |
agent 代码植入
执行方法前加入代码,执行方法后加入代码
package com.mergades.apm.classpool;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;
import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.security.ProtectionDomain;
public class FirstAgent implements ClassFileTransformer {
public final String injectedClassName = "com.mergades.apm.javaagent.HelloAgentTest";
public final String methodName = "hello";
@Override
public byte[] transform(ClassLoader loader, String className, Class> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
className = className.replace("/", ".");
if (className.equals(injectedClassName)) {
CtClass ctclass;
Long start = System.nanoTime();
try {
ctclass = ClassPool.getDefault().get(className);// 使用全称,用于取得字节码类<使用javassist>
CtMethod ctmethod = ctclass.getDeclaredMethod(methodName);// 得到这方法实例
ctmethod.insertBefore("System.out.println(11111111);");
return ctclass.toBytecode();
} catch (Exception e) {
System.out.println(e.getMessage());
e.printStackTrace();
} finally {
System.out.println("className took:" + (System.nanoTime() - start));
}
}
return null;
}
}
package com.mergades.apm.javaagent;
import com.mergades.apm.classpool.FirstAgent;
import java.lang.instrument.Instrumentation;
/**
* https://blog.csdn.net/fd2025/article/details/80280033
*/
public class HelloAgent {
public static void premain(String arg, Instrumentation instrumentation) {
System.out.println("premain 装载成功 方法 premain 参数:" + arg);
// 添加Transformer
instrumentation.addTransformer(new FirstAgent());
}
}
单元测试
package com.mergades.apm.javaagent;
public class HelloAgentTest {
public static void main(String[] args) {
System.err.println("TestHelloAgent main 方法");
hello();
}
public static void hello() {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("HelloAgentTest hello() output");
}
}
输出结果
premain 装载成功 方法 premain 参数:name=lisi&age=30
className tooks:201016907
TestHelloAgent main 方法
11111111
HelloAgentTest hello() output