是什么?
在JDK1.5以后,我们可以使用agent技术构建一个独立于应用程序的代理程序(即为Agent),用来协助监测、运行甚至替换其他JVM上的程序。使用它可以实现虚拟机级别的AOP功能。
怎么做:
agent有两种方式
-
jvm 参数形式:调用 premain 方法
-
attach 方式:调用 agentmain 方法
- 定义Agent类,为了方便,定义好了两种方式的代码
public class AgentTest {
/**
* 以jvm 参数形式启动,运行此方法 -javaagent:xxx.jar
* @Description 在类加载之前修改字节码
* @param args
* @param inst
*/
public static void premain(String args, Instrumentation inst) {
System.out.println("agentmain by jvm args");
inst.addTransformer(new CustomerTransformer(),true);
}
/**
* 动态 attach 方式启动,运行此方法
* @Description 支持在类加载后再次加载该类
* @param agentArgs
* @param inst
*/
public static void agentmain(String agentArgs, Instrumentation inst) {
System.out.println("agentmain by process attach");
inst.addTransformer(new CustomerTransformer(),true);
// 运行时类定义动态转换需要指定重新定义的类,否则 JVM 无法处理
try {
inst.retransformClasses(MyTest.class);
} catch (UnmodifiableClassException e) {
e.printStackTrace();
}
}
}
2 pom文件新增插件,指定jvm 参数形式运行的Premain-Class 与attach方式运行的Agent-Class
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<configuration>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
<archive>
<manifestEntries>
<Premain-Class>com.jxt.AgentTest</Premain-Class>
<Agent-Class>com.jxt.AgentTest</Agent-Class>
<Can-Redefine-Classes>true</Can-Redefine-Classes>
<Can-Retransform-Classes>true</Can-Retransform-Classes>
</manifestEntries>
</archive>
</configuration>
<executions>
<execution>
<goals>
<goal>attached</goal>
</goals>
<phase>package</phase>
</execution>
</executions>
</plugin>
</plugins>
3 assembly:assembly打包,会生成一个以jar-with-dependencies.jar结尾的jar包
4 定义了对class动态修改的类CustomerTransformer,用来记录方法的执行时间,此代码就是需要自己实现的对业务代码的拦截处理,类似于aop中@before,@After里面的内容:
public class CustomerTransformer implements java.lang.instrument.ClassFileTransformer {
@Override
public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined,
ProtectionDomain protectionDomain, byte[] classfileBuffer) {
// 这里我们限制下,只针对目标包下进行耗时统计
if (!className.startsWith("com/jxt/")) {
return classfileBuffer;
}
CtClass cl = null;
try {
ClassPool classPool = ClassPool.getDefault();
cl = classPool.makeClass(new ByteArrayInputStream(classfileBuffer));
for (CtMethod method : cl.getDeclaredMethods()) {
// 所有方法,统计耗时;请注意,需要通过`addLocalVariable`来声明局部变量
method.addLocalVariable("start", CtClass.longType);
method.insertBefore("start = System.currentTimeMillis();");
String methodName = method.getLongName();
method.insertAfter("System.out.println(\"" + methodName + " cost: \" + (System" +
".currentTimeMillis() - start));");
}
byte[] transformed = cl.toBytecode();
return transformed;
} catch (Exception e) {
e.printStackTrace();
}
return classfileBuffer;
}
}
5 编写测试类:
public class MyTest {
public static void main(String[] args) throws Exception{
for (;;){
print();
}
}
public static void print(){
System.out.println("dfasf");
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
6 运行:
- jvm参数方式执行:
执行时在运行参数上加上 -javaagent:/Users/***/IdeaProjects/mytest/target/agent_test-1.0-SNAPSHOT-jar-with-dependencies.jar。运行即可,下面是执行结果:
- attach方式运行:
通过jps查看到已经在运行中的测试类的进程号
编写一个程序,attach到测试类的进程中:如果打包的时候出现"程序包com.sun.tools.attach不存在"请参照https://blog.csdn.net/hosaos/article/details/102930561
public class AttachTest {
public static void main(String[] args) throws Exception {
// attach方法参数为目标应用程序的进程号
VirtualMachine vm = VirtualMachine.attach("72657");
// 请用你自己的agent绝对地址,替换这个
vm.loadAgent("/Users/edz/IdeaProjects/mytest/target/agent_test-1.0-SNAPSHOT-jar-with-dependencies.jar");
}
}
输出如下:
应用场景:
链路追踪skywalking、idea的debug,以及idea破解在vmoption里指定的-agent、以及一些监控工具。
参考文章:https://blog.csdn.net/weixin_45505313/article/details/110739132#3__22
https://blog.csdn.net/weixin_45505313/article/details/110872884
https://blog.csdn.net/liuyueyi25/article/details/104944657?spm=1001.2014.3001.5502