前言

我们现在开始回顾之前的ASM代码,简单的说,把自己想实现的功能先写成java代码,然后借助ASM工具类进行代码翻译,我们再抄写即可。但是呢,这种描述相当不精确。举例来说,下面代码是怎么用,在什么场景用

  1. org.objectweb.asm.commons.GeneratorAdapter#newArray

很多人到了这一步,要么是硬着头皮开始啃ASM,研究字节码规范,要么就直接放弃了。那么,责任在于我们。我们没有做好由浅入深的阶梯,第一天讲小学数学,第二天就直接讲大学的微积分,从而让读者从入门到放弃。要不,我们换一个比较容易理解,容易上手的中间过渡系统,让系统可以平滑过渡。经过多方调研,javassist是一个比较成熟好用的框架。我们先把重心转移到此,等我们非常熟练后,再切回来研究更纯粹的ASM字节码技术。

javassist基础知识

我们通过几个简单的例子来让大家更方便的了解javassist

快速上手

引入maven依赖

  1. <dependency>
  2. <groupId>org.javassist</groupId>
  3. <artifactId>javassist</artifactId>
  4. <version>3.29.0-GA</version>
  5. </dependency>
  1. package com.deer.agent.sandbox;
  2. import javassist.ClassPool;
  3. import javassist.CtClass;
  4. import javassist.CtMethod;
  5. import java.lang.instrument.ClassFileTransformer;
  6. import java.lang.instrument.IllegalClassFormatException;
  7. import java.security.ProtectionDomain;
  8. public class AgentClazzForJavassistTransformer implements ClassFileTransformer {
  9. @Override
  10. public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
  11. try {
  12. if(className!= null && className.startsWith("com/deer/base/service")){
  13. String dotClassName = className.replaceAll("/", "\\.");
  14. ClassPool cp = ClassPool.getDefault();
  15. CtClass cc = cp.get(dotClassName);
  16. CtMethod[] methods = cc.getDeclaredMethods();
  17. if(cc.isInterface()){
  18. return classfileBuffer;
  19. }
  20. for (CtMethod method : methods) {
  21. System.out.println(dotClassName+"."+method.getName());
  22. //
  23. method.addLocalVariable("startTime",CtClass.longType);
  24. method.insertBefore("startTime = System.nanoTime();");
  25. StringBuilder endBlock = new StringBuilder();
  26. method.addLocalVariable("endTime", CtClass.longType);
  27. method.addLocalVariable("opTime", CtClass.longType);
  28. endBlock.append(
  29. "endTime = System.nanoTime();");
  30. endBlock.append(
  31. "opTime = endTime-startTime;");
  32. endBlock.append(
  33. "System.out.println(\" our inner code calculator current method cost :" +
  34. "\" + opTime + \" ns!\");");
  35. method.insertAfter(endBlock.toString());
  36. classfileBuffer = cc.toBytecode();
  37. cc.detach();
  38. }
  39. }
  40. }catch (Exception e){
  41. e.printStackTrace();
  42. }
  43. return classfileBuffer;
  44. }
  45. }
  1. package com.deer.agent;
  2. import com.deer.agent.sandbox.AgentClazzForJavassistTransformer;
  3. import java.lang.instrument.Instrumentation;
  4. import java.util.Arrays;
  5. public class AgentMainForJavassist {
  6. public static void premain(String agentArgs, Instrumentation inst) {
  7. System.out.println("premain");
  8. }
  9. public static void agentmain(String agentArgs, Instrumentation inst){
  10. System.out.println("load agent...");
  11. Class[] clazzList = inst.getAllLoadedClasses();
  12. Arrays.stream(clazzList).forEach(clazz->{
  13. if(clazz.getName().startsWith("com.deer.base.service")){
  14. //
  15. try {
  16. inst.addTransformer(new AgentClazzForJavassistTransformer(), true);
  17. inst.retransformClasses(clazz);
  18. }catch (Exception e){
  19. //
  20. e.printStackTrace();
  21. }
  22. }
  23. });
  24. }
  25. }

回顾比较

此时此刻,我们发现原来在ASM字节码里的复杂代码,在这里不存在了,反而是原生的java代码,整体理解难度直接降了不少。我们可以这么理解,使用javassist帮我们快速理清思路,使用ASM字节码帮我们深刻理解底层原理,我们通过javassist作为跳板,作为我们后续梳理技术实现的核心思路。
那么,我们之前的ASM代码,还有哪些不完善的呢?这里给出一部分提示

  1. 新的代码判断了如果是接口直接return,那么注解,枚举呢?是不是也要相同的处理方案
  2. 假如说我们在代码开头结尾插代码,怎么把这个能力抽象出来,变成一个普通开发就能随时改动的功能
  3. 假如我们定义一个字段,用来记录这个信息,要怎么记录
  4. 如果方法跨线程了,要怎么处理
  5. 如果想实现链路跟踪,怎么实现全局的traceID
  6. 怎么跳过构造函数,怎么跳过抽象类里面的抽象方法,怎么跳过Object里面的默认方法(toString,getClass,equals,hashCode)
  7. 我们的代码是否会影响原来逻辑,是否要实现代码隔离或者类隔离

暂时交给读者去思考学习实现,后面我们也会逐步实现这些功能