介绍
在JDK1.5以后,我们可以使用agent技术构建一个独立于应用程序的代理程序(即为Agent),用来协助监测、运行甚至替换其他JVM上的程序。使用它可以实现虚拟机级别的AOP功能。
Agent分为两种,一种是在主程序之前运行的Agent,一种是在主程序之后运行的Agent(前者的升级版,1.6以后提供)。
实现DEMO
定义HelloAgent
预加载类,并且定义premain
方法。
public class HelloAgent {
public static void premain(String agentOps, Instrumentation inst) {
System.out.println("[TLOG AGENT] START!");
// AspectLogEnhance.enhance();
inst.addTransformer(new HelloTransformer());
}
}
指定转换器,使用javassist
对类进行修改。
public class HelloTransformer implements ClassFileTransformer {
public final String TEST_CLASS_NAME = "com.charles.agent.HelloController";
public final String METHOD_NAME = "hello";
public byte[] transform(ClassLoader loader,
String className,
Class<?> classBeingRedefined,
ProtectionDomain protectionDomain,
byte[] classfileBuffer) throws IllegalClassFormatException {
// System.out.println("className : " + className);
String finalClassName = className.replace("/", ".");
if (TEST_CLASS_NAME.equals(finalClassName)) {
System.out.println("class name 匹配上了 !");
CtClass ctClass;
try {
ctClass = ClassPool.getDefault().get(finalClassName);
System.out.println("ctClass is OK !");
CtMethod ctMethod = ctClass.getDeclaredMethod(METHOD_NAME);
System.out.println("CtMethod is OK !");
ctMethod.insertBefore("System.out.println(\"字节码添加成功,打印日志 !\");");
return ctClass.toBytecode();
} catch (Exception e) {
e.printStackTrace();
System.out.println(e.getMessage());
}
}
return null;
}
}
修改maven打包方式
<build>
<plugins>
<!--
META-INF 下 MANIFEST.MF 文件 内容
Manifest-Version: 1.0
Premain-Class: com.jenson.TestAgent
下面Maven插件可以自动实现
-->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>1.4</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<transformers>
<transformer
implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<manifestEntries>
<Premain-Class>com.charles.agent.HelloAgent</Premain-Class>
<!--<Can-Redefine-Classes>${can.redefine.classes}</Can-Redefine-Classes>-->
<!--<Can-Retransform-Classes>${can.retransform.classes}</Can-Retransform-Classes>-->
</manifestEntries>
</transformer>
</transformers>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
在打好包的jar包中就会在META-INF目录下自动生成MANIFEST.MF
。
Manifest-Version: 1.0
Premain-Class: com.charles.agent.HelloAgent
Archiver-Version: Plexus Archiver
Built-By: admin
Created-By: Apache Maven 3.5.0
Build-Jdk: 1.8.0_291
使用方式:
java -javaagent:demo-agent-1.0-SNAPSHOT.jar app.jar
- 在vm options中设置
-javaagent: demo-agent-1.0-SNAPSHOT.jar
在JDK1.6之后新增的agentmain
方法,能够实如今main方法执行之后进行插入执行。
public class HelloAgent {
public static void agentmain(String args, Instrumentation instrumentation) {
try {
instrumentation.addTransformer(new TestTransformer());
instrumentation.retransformClasses(needRetransFormClasses.toArray(new Class[0]));
} catch (Exception e) {
}
}
}
打包方式
<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>
<Agent-Class>com.charles.agent.HelloAgent</Agent-Class>
<Can-Redefine-Classes>true</Can-Redefine-Classes>
<Can-Retransform-Classes>true</Can-Retransform-Classes>
</manifestEntries>
</archive>
</configuration>
</plugin>
</plugins>
</build>
执行耗时的监控DEMO
public class MyMonitorTransformer implements ClassFileTransformer {
private static final Set<String> classNameSet = new HashSet<>();
static {
classNameSet.add("org.itstack.demo.test.ApiTest");
}
@Override
public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) {
try {
String currentClassName = className.replaceAll("/", ".");
if (!classNameSet.contains(currentClassName)) { // 提升classNameSet中含有的类
return null;
}
System.out.println("transform: [" + currentClassName + "]");
CtClass ctClass = ClassPool.getDefault().get(currentClassName);
CtBehavior[] methods = ctClass.getDeclaredBehaviors();
for (CtBehavior method : methods) {
enhanceMethod(method);
}
return ctClass.toBytecode();
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
private void enhanceMethod(CtBehavior method) throws Exception {
if (method.isEmpty()) {
return;
}
String methodName = method.getName();
if ("main".equalsIgnoreCase(methodName)) {
return;
}
final StringBuilder source = new StringBuilder();
// 前置增强: 打入时间戳
// 保留原有的代码处理逻辑
source.append("{")
.append("long start = System.nanoTime();\n") //前置增强: 打入时间戳
.append("$_ = $proceed($$);\n") //调用原有代码,类似于method();($$)表示所有的参数
.append("System.out.print(\"method:[")
.append(methodName).append("]\");").append("\n")
.append("System.out.println(\" cost:[\" +(System.nanoTime() - start)+ \"ns]\");") // 后置增强,计算输出方法执行耗时
.append("}");
ExprEditor editor = new ExprEditor() {
@Override
public void edit(MethodCall methodCall) throws CannotCompileException {
methodCall.replace(source.toString());
}
};
method.instrument(editor);
}
}