前言
我们现在开始回顾之前的ASM代码,简单的说,把自己想实现的功能先写成java代码,然后借助ASM工具类进行代码翻译,我们再抄写即可。但是呢,这种描述相当不精确。举例来说,下面代码是怎么用,在什么场景用
org.objectweb.asm.commons.GeneratorAdapter#newArray
很多人到了这一步,要么是硬着头皮开始啃ASM,研究字节码规范,要么就直接放弃了。那么,责任在于我们。我们没有做好由浅入深的阶梯,第一天讲小学数学,第二天就直接讲大学的微积分,从而让读者从入门到放弃。要不,我们换一个比较容易理解,容易上手的中间过渡系统,让系统可以平滑过渡。经过多方调研,javassist是一个比较成熟好用的框架。我们先把重心转移到此,等我们非常熟练后,再切回来研究更纯粹的ASM字节码技术。
javassist基础知识
我们通过几个简单的例子来让大家更方便的了解javassist
快速上手
引入maven依赖
<dependency>
<groupId>org.javassist</groupId>
<artifactId>javassist</artifactId>
<version>3.29.0-GA</version>
</dependency>
package com.deer.agent.sandbox;
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 AgentClazzForJavassistTransformer implements ClassFileTransformer {
@Override
public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
try {
if(className!= null && className.startsWith("com/deer/base/service")){
String dotClassName = className.replaceAll("/", "\\.");
ClassPool cp = ClassPool.getDefault();
CtClass cc = cp.get(dotClassName);
CtMethod[] methods = cc.getDeclaredMethods();
if(cc.isInterface()){
return classfileBuffer;
}
for (CtMethod method : methods) {
System.out.println(dotClassName+"."+method.getName());
//
method.addLocalVariable("startTime",CtClass.longType);
method.insertBefore("startTime = System.nanoTime();");
StringBuilder endBlock = new StringBuilder();
method.addLocalVariable("endTime", CtClass.longType);
method.addLocalVariable("opTime", CtClass.longType);
endBlock.append(
"endTime = System.nanoTime();");
endBlock.append(
"opTime = endTime-startTime;");
endBlock.append(
"System.out.println(\" our inner code calculator current method cost :" +
"\" + opTime + \" ns!\");");
method.insertAfter(endBlock.toString());
classfileBuffer = cc.toBytecode();
cc.detach();
}
}
}catch (Exception e){
e.printStackTrace();
}
return classfileBuffer;
}
}
package com.deer.agent;
import com.deer.agent.sandbox.AgentClazzForJavassistTransformer;
import java.lang.instrument.Instrumentation;
import java.util.Arrays;
public class AgentMainForJavassist {
public static void premain(String agentArgs, Instrumentation inst) {
System.out.println("premain");
}
public static void agentmain(String agentArgs, Instrumentation inst){
System.out.println("load agent...");
Class[] clazzList = inst.getAllLoadedClasses();
Arrays.stream(clazzList).forEach(clazz->{
if(clazz.getName().startsWith("com.deer.base.service")){
//
try {
inst.addTransformer(new AgentClazzForJavassistTransformer(), true);
inst.retransformClasses(clazz);
}catch (Exception e){
//
e.printStackTrace();
}
}
});
}
}
回顾比较
此时此刻,我们发现原来在ASM字节码里的复杂代码,在这里不存在了,反而是原生的java代码,整体理解难度直接降了不少。我们可以这么理解,使用javassist帮我们快速理清思路,使用ASM字节码帮我们深刻理解底层原理,我们通过javassist作为跳板,作为我们后续梳理技术实现的核心思路。
那么,我们之前的ASM代码,还有哪些不完善的呢?这里给出一部分提示
- 新的代码判断了如果是接口直接return,那么注解,枚举呢?是不是也要相同的处理方案
- 假如说我们在代码开头结尾插代码,怎么把这个能力抽象出来,变成一个普通开发就能随时改动的功能
- 假如我们定义一个字段,用来记录这个信息,要怎么记录
- 如果方法跨线程了,要怎么处理
- 如果想实现链路跟踪,怎么实现全局的traceID
- 怎么跳过构造函数,怎么跳过抽象类里面的抽象方法,怎么跳过Object里面的默认方法(toString,getClass,equals,hashCode)
- 我们的代码是否会影响原来逻辑,是否要实现代码隔离或者类隔离
暂时交给读者去思考学习实现,后面我们也会逐步实现这些功能