主要观点总结
文章介绍了「芋道快速开发平台」知识星球及其提供的部分资料,如《项目实战(视频)》、《互联网高频面试题》等,并提及了一个开源项目,具有多项功能。同时,文章还探讨了Java Agent技术、Instrumentation接口以及ClassFileTransformer的使用,以及Arthas的trace功能,用于无侵入式地解决代码性能优化问题。最后,文章鼓励读者加入知识星球,以获取更多的技术提升。
关键观点总结
关键观点1: 「芋道快速开发平台」知识星球及其提供的资料
「芋道快速开发平台」知识星球提供了一系列有用的技术资料,如《项目实战(视频)》、《互联网高频面试题》等,帮助读者提升技术能力。
关键观点2: 开源项目及其功能
文章提到的开源项目具有多项功能,如RBAC权限、SaaS多租户、数据权限等,并提供了相应的技术文档和源码。
关键观点3: Java Agent技术与Instrumentation接口
Java Agent技术通过Instrumentation接口及其ClassFileTransformer实现无侵入式地解决代码性能优化问题。
关键观点4: Arthas的trace功能
Arthas的trace功能通过字节码增强技术,无侵入式地跟踪和统计方法耗时,用于性能优化。
关键观点5: 加入知识星球
文章鼓励读者加入「芋道快速开发平台」知识星球,以获取更多技术提升和资料。
正文
static
void
premain
(String agentArgs, Instrumentation instrumentation)
{
}
从字面上理解,就是运行在main()函数之前的类。在Java虚拟机启动时,在执行main()函数之前,会先运行指定类的premain()方法,在premain()方法中对class文件进行修改,它有两个入参
-
-
instrumentation:上文所将的Instrumentation的实例,我们可以在方法中调用上文所讲的方法,注册对应的Class转换器,对Class文件进行修改
如下图,借助Instrumentation,JVM启动时的处理流程是这样的:JVM会执行指定类的premain()方法,在premain()中可以调用Instrumentation对象的addTransformer方法注册ClassFileTransformer。当JVM加载类时会将类文件的字节数组传递给ClassFileTransformer的transform方法,在transform方法中对Class文件进行解析和修改,之后JVM就会加载转换后的Class文件
JVM启动时的处理流程
那我们需要做的就是写一个转换Class文件的ClassFileTransformer,下面用一个计算函数耗时的小例子看看Java Agent是怎么使用的
public class MyClassFileTransformer implements ClassFileTransformer {
@Override
public byte[] transform(ClassLoader loader, String className, Class> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) {
if ("com/example/aop/agent/MyTest".equals(className)) {
// 使用ASM框架进行字节码转换
ClassReader cr = new ClassReader(classfileBuffer);
ClassWriter cw = new ClassWriter(cr, ClassWriter.COMPUTE_FRAMES);
ClassVisitor cv = new TimeStatisticsVisitor(Opcodes.ASM7, cw);
cr.accept(cv, ClassReader.SKIP_FRAMES | ClassReader.SKIP_DEBUG);
return cw.toByteArray();
}
return classfileBuffer;
}
}
public class TimeStatisticsVisitor extends ClassVisitor {
public TimeStatisticsVisitor(int api, ClassVisitor classVisitor) {
super(Opcodes.ASM7, classVisitor);
}
@Override
public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
MethodVisitor mv = cv.visitMethod(access, name, descriptor, signature, exceptions);
if (name.equals("")) {
return mv;
}
return new TimeStatisticsAdapter(api, mv, access, name, descriptor);
}
}
public class TimeStatisticsAdapter extends AdviceAdapter {
protected TimeStatisticsAdapter(int api, MethodVisitor methodVisitor, int access, String name, String descriptor) {
super(api, methodVisitor, access, name, descriptor);
}
@Override
protected void onMethodEnter() {
// 进入函数时调用TimeStatistics的静态方法start
super.visitMethodInsn(Opcodes.INVOKESTATIC, "com/example/aop/agent/TimeStatistics", "start", "()V", false);
super.onMethodEnter();
}
@Override
protected void onMethodExit(int opcode) {
// 退出函数时调用TimeStatistics的静态方法end
super.onMethodExit(opcode);
super.visitMethodInsn(Opcodes.INVOKESTATIC, "com/example/aop/agent/TimeStatistics", "end", "()V", false);
}
}
public class TimeStatistics {
public static ThreadLocal t = new ThreadLocal<>();
public static void start() {
t.set(System.currentTimeMillis());
}
public static void end() {
long time = System.currentTimeMillis() - t.get();
System.out.println(Thread.currentThread().getStackTrace()[2] + " spend: " + time);
}
}
public class AgentMain {
// premain()函数中注册MyClassFileTransformer转换器
public static void premain (String agentArgs, Instrumentation instrumentation) {
System.out.println("premain方法");
instrumentation.addTransformer(new MyClassFileTransformer(), true);
}
}
org.apache.maven.plugins
maven-assembly-plugin
3.1.1
jar-with-dependencies
// 指定premain()的所在方法
com.example.aop.agent.AgentMain
com.example.aop.agent.AgentMain
true
true
package
single
org.apache.maven.plugins
maven-compiler-plugin
3.1
${maven.compiler.source}
${maven.compiler.target}
使用命令行执行下面的测试类
java -javaagent:/Users/zhangxiaobin/IdeaProjects/aop-demo/target/aop-0.0.1-SNAPSHOT-jar-with-dependencies.jar com.example.aop.agent.MyTest
public class MyTest {
public static void main(String[] args) throws InterruptedException {
Thread.sleep(3000);
}
}
计算出了某个方法的耗时
计算出某个方法的耗时
在上面的例子中,我们只能在JVM启动时指定一个Agent,这种方式局限在main()方法执行前,如果我们想在项目启动后随时随地地修改Class文件,要怎么办呢?这个时候需要借助Java Agent的另外一个方法,该方法的签名如下
public static void agentmain (String agentArgs, Instrumentation inst) {
}
agentmain()的参数与premain()有着同样的含义,但是agentmain()是在Java Agent被Attach到Java虚拟机上时执行的,当Java Agent被attach到Java虚拟机上,Java程序的main()函数一般已经启动,并且程序很可能已经运行了相当长的时间,此时通过Instrumentation.retransformClasses()方法,可以动态转换Class文件并使之生效,下面用一个小例子演示一下这个功能
下面的类启动后,会不断打印出100这个数字,我们通过Attach功能使之打印出50这个数字
public class PrintNumTest {
public static void main(String[] args) throws InterruptedException {
while (true) {
System.out.println(getNum());
Thread.sleep(3000);
}
}
private static int getNum() {
return 100;
}
}
依然是定义一个ClassFileTransformer,使用ASM框架修改getNum()方法
public class PrintNumTransformer implements ClassFileTransformer {
@Override
public byte[] transform(ClassLoader loader, String className, Class> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
if ("com/example/aop/agent/PrintNumTest".equals(className)) {
System.out.println("asm");
ClassReader cr = new ClassReader(classfileBuffer);
ClassWriter cw = new ClassWriter(cr, ClassWriter.COMPUTE_FRAMES);
ClassVisitor cv = new TransformPrintNumVisitor(Opcodes.ASM7, cw);
cr.accept(cv, ClassReader.SKIP_FRAMES | ClassReader.SKIP_DEBUG);
return cw.toByteArray();
}
return classfileBuffer;
}
}
public class