前言
我们现在开始回顾之前的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 {@Overridepublic 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)
- 我们的代码是否会影响原来逻辑,是否要实现代码隔离或者类隔离
暂时交给读者去思考学习实现,后面我们也会逐步实现这些功能
