在Java开发中我们经常用Spring的AOP方式动态来拦截类方法的执行,做一些公共处理,比如日志、异常等等,这里我要介绍一种静态修改Java代码的方法,这种方法经常被用在破解一些Java工具上面,通过启动时静态修改Java代码,修改里面的处理逻辑达到破解的目的,当然这种方式也可以用于不适合使用Spring的项目中,比如比较有名的监控系统pinpoint就是通过此方式来记录方法的执行时间。
1、在Maven中引入依赖包:
<properties>
<aspectj.version>1.8.13</aspectj.version>
<javassist.version>3.24.1-GA</javassist.version>
</properties>
<dependencies>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>${aspectj.version}</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>${aspectj.version}</version>
</dependency>
<dependency>
<groupId>org.javassist</groupId>
<artifactId>javassist</artifactId>
<version>${javassist.version}</version>
</dependency>
</dependencies>
2、配置编译插件:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>2.4</version>
<configuration>
<archive>
<manifestFile>src/main/resources/META-INF/MANIFEST.MF</manifestFile>
</archive>
</configuration>
</plugin>
3、在项目的src/man/resources目录下面创建META-INF目录,并添加MANIFEST.MF文件,文件内容为:
Agent-Class: com.nettm.exercise.agent.StaticAgent
Premain-Class: com.nettm.exercise.agent.StaticAgent
Can-Redefine-Classes: true
Can-Retransform-Classes: true
Can-Set-Native-Method-Prefix: true
4、创建Agent类:
package com.nettm.exercise.agent;
import java.lang.instrument.Instrumentation;
public class StaticAgent {
public static void premain(String args, Instrumentation inst) {
TimeLogger transformer = new TimeLogger();
inst.addTransformer(transformer);
}
public static void agentmain(String args, Instrumentation inst) {
TimeLogger transformer = new TimeLogger();
inst.addTransformer(transformer);
}
}
5、创建修改Java类的处理逻辑,这里以添加打印方法执行时间为例:
package com.nettm.exercise.agent;
import javassist.*;
import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.security.ProtectionDomain;
public class TimeLogger implements ClassFileTransformer {
@Override
public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined,
ProtectionDomain protectionDomain, byte[] classfileBuffer)
throws IllegalClassFormatException {
String clsName = className.replaceAll("/", ".");
ClassPool pool = ClassPool.getDefault();
pool.appendClassPath(new ClassClassPath(this.getClass()));
pool.appendClassPath(new LoaderClassPath(Thread.currentThread().getContextClassLoader()));
pool.appendSystemPath();
CtClass ctclass;
try {
ctclass = pool.get(clsName);
} catch (Exception e) {
System.out.println("class not found: " + className);
return classfileBuffer;
}
if (ctclass.isInterface() || ctclass.isAnnotation() || ctclass.isPrimitive() || ctclass.isEnum() || ctclass
.isFrozen() || ctclass.isModified()) {
return classfileBuffer;
}
try {
for (CtBehavior method : ctclass.getDeclaredBehaviors()) {
String methodName = method.getName();
// 得到这方法实例
CtMethod ctmethod;
try {
ctmethod = ctclass.getDeclaredMethod(methodName);
} catch (Exception e) {
continue;
}
String val = "消耗时间:" + clsName + "." + methodName + ":";
ctmethod.addLocalVariable("strTime", CtClass.longType);
ctmethod.addLocalVariable("endTime", CtClass.longType);
ctmethod.addLocalVariable("cost", CtClass.longType);
ctmethod.insertBefore("strTime = System.currentTimeMillis();");
ctmethod.insertAfter("endTime = System.currentTimeMillis();\n");
ctmethod.insertAfter("cost = endTime - strTime;\n");
ctmethod.insertAfter(
"if (cost > 1000) {\n" + "System.out.println(\"" + val + "\" + (endTime - strTime));\n" + "}");
}
byte[] buf = ctclass.toBytecode();
ctclass.detach();
return buf;
} catch (Exception e) {
System.out.println("编译错误:" + clsName + ":" + e.getMessage());
}
return classfileBuffer;
}
}
6、使用方法
通过Maven命令打成Jar包,在其他项目中添加启动参数,如:
-javaagent:exercise-agent-0.0.1-SNAPSHOT.jar
备注:
1、Javassist是一种可以在JVM加载类之前修改Java字节码的工具。
2、Instrumentation API是JVM提供的当JVM加载类时修改Java字节码的工具。
参考:
1、https://www.baeldung.com/java-instrumentation[](https://docs.oracle.com/javase/8/docs/api/java/lang/instrument/Instrumentation.html)
/>2、https://www.javassist.org/tutorial/tutorial.html
3、https://docs.oracle.com/javase/8/docs/api/java/lang/instrument/Instrumentation.html