故事的小黄花

团队中有同事在做功用优化相关的作业,由于公司基础设施不足,同事在代码中写了许多的代码核算某个办法的耗时,大概的代码形式便是

@Override
public void method(Req req) {
    StopWatch stopWatch = new StopWatch();
    stopWatch.start("某某办法-耗时核算");
    method()
    stopWatch.stop();
    log.info("查询耗时散布:{}", stopWatch.prettyPrint());
}

这样的代码十分多,侵入性很大,联想到之前学习的Java Agent技能,能够无侵入式地处理这类问题,所以做了一个很小很小的demo

Instrumentation

在了解Agent之前需求先看看Instrumentation

JDK从1.5版别开端引入了java.lang.instrument包,该包提供了一些工具帮助开发人员实现字节码增强,Instrumentation接口的常用办法如下

public interface Instrumentation {
  /**
   * 注册Class文件转化器,转化器用于改变Class文件二进制流的数据
   *
   * @param transformer      注册的转化器
   * @param canRetransform    设置是否允许从头转化
   */
  void addTransformer(ClassFileTransformer transformer, boolean canRetransform);
​
  /**
   * 移除一个转化器
   *
   * @param transformer      需求移除的转化器
   */
  boolean removeTransformer(ClassFileTransformer transformer);
 
  /**
   * 在类加载之后,从头转化类,假如从头转化的办法有活泼的栈帧,那些活泼的栈帧持续运转未转化前的办法
   *
   * @param 从头转化的类数组
   */
  void retransformClasses(Class<?>... classes) throws UnmodifiableClassException;
  /**
   * 当时JVM装备是否支持从头转化
   */
  boolean isRetransformClassesSupported();
​
  /**
   * 获取一切已加载的类
   */
  @SuppressWarnings("rawtypes")
  Class[] getAllLoadedClasses();
}
public interface ClassFileTransformer {
  // className参数表明当时加载类的类名,classfileBuffer参数是待加载类文件的字节数组
    // 调用addTransformer注册ClassFileTransformer今后,后续一切JVM加载类都会被它的transform办法拦截
    // 这个办法接收原类文件的字节数组,在这个办法中做类文件改写,最后回来转化过的字节数组,由JVM加载这个修正正的类文件
    // 假如transform办法回来null,表明不对此类做处理,假如回来值不为null,JVM会用回来的字节数组替换本来类的字节数组
  byte[] transform( ClassLoader     loader,
        String       className,
        Class<?>      classBeingRedefined,
        ProtectionDomain  protectionDomain,
        byte[]       classfileBuffer)
    throws IllegalClassFormatException;
}

Instrumentation有两种运用方式

  1. 在JVM发动的时分添加一个Agent jar包
  2. JVM运转今后在恣意时刻经过Attach API长途加载Agent的jar包

Agent

运用Java Agent需求凭借一个办法,该办法的办法签名如下

public static void premain (String agentArgs, Instrumentation instrumentation) {
}

从字面上了解,便是运转在main()函数之前的类。在Java虚拟机发动时,在履行main()函数之前,会先运转指定类的premain()办法,在premain()办法中对class文件进行修正,它有两个入参

  1. agentArgs:发动参数,在JVM发动时指定
  2. instrumentation:上文所将的Instrumentation的实例,咱们能够在办法中调用上文所讲的办法,注册对应的Class转化器,对Class文件进行修正

如下图,凭借Instrumentation,JVM发动时的处理流程是这样的:JVM会履行指定类的premain()办法,在premain()中能够调用Instrumentation目标的addTransformer办法注册ClassFileTransformer。当JVM加载类时会将类文件的字节数组传递给ClassFileTransformer的transform办法,在transform办法中对Class文件进行解析和修正,之后JVM就会加载转化后的Class文件

Java Agent

那咱们需求做的便是写一个转化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("<init>")) {
      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<Long> 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);
   }
​
}
<build>
 <plugins>
  <plugin>
   <groupId>org.apache.maven.plugins</groupId>
   <artifactId>maven-assembly-plugin</artifactId>
   <version>3.1.1</version>
   <configuration>
    <descriptorRefs>
     <!--将运用的一切依靠包都打到jar包中。假如依靠的是 jar 包,jar 包会被解压开,平铺到最终的 uber-jar 里去。输出格式为 jar-->
     <descriptorRef>jar-with-dependencies</descriptorRef>
    </descriptorRefs>
    <archive>
     <manifestEntries>
       // 指定premain()的所在办法
      <Agent-CLass>com.example.aop.agent.AgentMain</Agent-CLass>
      <Premain-Class>com.example.aop.agent.AgentMain</Premain-Class>
      <Can-Redefine-Classes>true</Can-Redefine-Classes>
      <Can-Retransform-Classes>true</Can-Retransform-Classes>
     </manifestEntries>
    </archive>
   </configuration>
   <executions>
    <execution>
     <phase>package</phase>
     <goals>
      <goal>single</goal>
     </goals>
    </execution>
   </executions>
  </plugin>
  <plugin>
   <groupId>org.apache.maven.plugins</groupId>
   <artifactId>maven-compiler-plugin</artifactId>
   <version>3.1</version>
   <configuration>
    <source>${maven.compiler.source}</source>
    <target>${maven.compiler.target}</target>
   </configuration>
  </plugin>
 </plugins>
</build>

运用指令行履行下面的测验类

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);
   }
}

核算出了某个办法的耗时

Java Agent

Attach

在上面的比如中,咱们只能在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 TransformPrintNumVisitor extends ClassVisitor {
​
  public TransformPrintNumVisitor(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("getNum")) {
      return new TransformPrintNumAdapter(api, mv, access, name, descriptor);
     }
    return mv;
​
   }
​
}
​
public class TransformPrintNumAdapter extends AdviceAdapter {
​
  protected TransformPrintNumAdapter(int api, MethodVisitor methodVisitor, int access, String name, String descriptor) {
    super(api, methodVisitor, access, name, descriptor);
   }
​
  @Override
  protected void onMethodEnter() {
    super.visitIntInsn(BIPUSH, 50);
    super.visitInsn(IRETURN);
   }
}
public class PrintNumAgent {
​
  public static void agentmain (String agentArgs, Instrumentation inst) throws UnmodifiableClassException {
    System.out.println("agentmain");
    inst.addTransformer(new PrintNumTransformer(), true);
​
    Class[] allLoadedClasses = inst.getAllLoadedClasses();
    for (Class allLoadedClass : allLoadedClasses) {
      if (allLoadedClass.getSimpleName().equals("PrintNumTest")) {
        System.out.println("Reloading: " + allLoadedClass.getName());
        inst.retransformClasses(allLoadedClass);
        break;
       }
     }
   }
}
<build>
 <plugins>
  <plugin>
   <groupId>org.apache.maven.plugins</groupId>
   <artifactId>maven-assembly-plugin</artifactId>
   <version>3.1.1</version>
   <configuration>
    <descriptorRefs>
     <!--将运用的一切依靠包都打到jar包中。假如依靠的是 jar 包,jar 包会被解压开,平铺到最终的 uber-jar 里去。输出格式为 jar-->
     <descriptorRef>jar-with-dependencies</descriptorRef>
    </descriptorRefs>
    <archive>
     <manifestEntries>
       // 指定agentmain所在的类
      <Agent-CLass>com.example.aop.agent.PrintNumAgent</Agent-CLass>
      <Premain-Class>com.example.aop.agent.PrintNumAgent</Premain-Class>
      <Can-Redefine-Classes>true</Can-Redefine-Classes>
      <Can-Retransform-Classes>true</Can-Retransform-Classes>
     </manifestEntries>
    </archive>
   </configuration>
   <executions>
    <execution>
     <phase>package</phase>
     <goals>
      <goal>single</goal>
     </goals>
    </execution>
   </executions>
  </plugin>
  <plugin>
   <groupId>org.apache.maven.plugins</groupId>
   <artifactId>maven-compiler-plugin</artifactId>
   <version>3.1</version>
   <configuration>
    <source>${maven.compiler.source}</source>
    <target>${maven.compiler.target}</target>
   </configuration>
  </plugin>
 </plugins>
</build>

由于是跨进程通信,Attach的发起端是一个独立的java程序,这个java程序会调用VirtualMachine.attach办法开端合目标JVM进行跨进程通信

public class MyAttachMain {
​
  public static void main(String[] args) throws IOException, AttachNotSupportedException, AgentLoadException, AgentInitializationException {
    VirtualMachine virtualMachine = VirtualMachine.attach(args[0]);
​
    try {
      virtualMachine.loadAgent("/Users/zhangxiaobin/IdeaProjects/aop-demo/target/aop-0.0.1-SNAPSHOT-jar-with-dependencies.jar");
     } finally {
      virtualMachine.detach();
     }
​
   }
​
}

运用jps查询到PrintNumTest的进程id,再用下面的指令履行MyAttachMain类

java -cp /Library/Java/JavaVirtualMachines/jdk1.8.0_311.jdk/Contents/Home/lib/tools.jar:/Users/zhangxiaobin/IdeaProjects/aop-demo/target/aop-0.0.1-SNAPSHOT-jar-with-dependencies.jar com.example.aop.agent.MyAttachMain 49987

能够清楚地看到打印的数字变成了50

Java Agent

Arthas

以上是我写的小demo,有许多不足之处,看看大佬是怎么写的,arthas的trace指令能够核算办法耗时,如下图

Java Agent

建立调试环境

Arthas debug需求凭借IDEA的长途debug功用,能够参考 github.com/alibaba/art…

先写一个能够循环履行的Demo

public class ArthasTest {
​
  public static void main(String[] args) throws InterruptedException {
    int i = 0;
    while (true) {
      Thread.sleep(2000);
      print(i++);
     }
   }
​
  public static void print(Integer content) {
    System.out.println("Main print: " + content);
   }
​
}

指令行履行改demo

java -Xdebug -Xrunjdwp:transport=dt_socket,server=y,address=8000 com.example.aop.agent.ArthasTest

在Arthas源码的项目中设置长途debug

Java Agent

Java Agent

在这个办法com.taobao.arthas.agent334.AgentBootstrap#main恣意位置打上断点,切换到刚刚设置的长途debug模式,发动项目

Java Agent

能够看到刚刚处于Listening的ArthasTest开端履行,发动arthas-boot.jar,就能够看到断点跳进Arthas源码的项目中

Java Agent

bytekit

在看trace指令之前需求一点前置常识,运用ASM进行字节码增强,代码逻辑不好修正,了解困难,所以bytekit基于ASM提供了一套简洁的API,让开发人员能够比较轻松地完结字节码增强,咱们先来看一个简略的demo,来自github.com/alibaba/byt…

public class SampleInterceptor {
​
  @AtEnter(inline = false, suppress = RuntimeException.class, suppressHandler = PrintExceptionSuppressHandler.class)
  public static void atEnter(@Binding.This Object object,
                @Binding.Class Object clazz,
                @Binding.Args Object[] args,
                @Binding.MethodName String methodName,
                @Binding.MethodDesc String methodDesc) {
    System.out.println("atEnter, args[0]: " + args[0]);
   }
​
  @AtExit(inline = true)
  public static void atExit(@Binding.Return Object returnObject) {
    System.out.println("atExit, returnObject: " + returnObject);
   }
​
  @AtExceptionExit(inline = true, onException = RuntimeException.class)
  public static void atExceptionExit(@Binding.Throwable RuntimeException ex,
                    @Binding.Field(name = "exceptionCount") int exceptionCount) {
    System.out.println("atExceptionExit, ex: " + ex.getMessage() + ", field exceptionCount: " + exceptionCount);
   }
​
}
  • 上文说过,bytekit的主旨是提供简介的API让开发能够轻松地完结字节码增强,从注解名咱们就能够知道@AtEnter是在办法进入时刺进,@AtExit是在办法退出时刺进,@AtExceptionExit时在产生反常退出时刺进
  • inline = true表明办法中的代码直接刺进增强办法中,inline = false表明是调用这个办法,有点难了解,咱们等下看反编译后的代码
  • 装备了 suppress = RuntimeException.classsuppressHandler = PrintExceptionSuppressHandler.class,阐明刺进的代码会被 try/catch 包围
  • @AtExceptionExit在原办法体规模try-catch指定反常进行处理

这是咱们要进行增强的办法

public class Sample {
​
  private int exceptionCount = 0;
​
  public String hello(String str, boolean exception) {
    if (exception) {
      exceptionCount++;
      throw new RuntimeException("test exception, str: " + str);
     }
    return "hello " + str;
   }
​
}
public class SampleMain {
​
  public static void main(String[] args) throws Exception {
​
    // 解析定义的 Interceptor类 和相关的注解
    DefaultInterceptorClassParser interceptorClassParser = new DefaultInterceptorClassParser();
    List<InterceptorProcessor> processors = interceptorClassParser.parse(SampleInterceptor.class);
​
    // 加载字节码
    ClassNode classNode = AsmUtils.loadClass(Sample.class);
​
    // 对加载到的字节码做增强处理
    for (MethodNode methodNode : classNode.methods) {
      if (methodNode.name.equals("hello")) {
        MethodProcessor methodProcessor = new MethodProcessor(classNode, methodNode);
        for (InterceptorProcessor interceptor : processors) {
          interceptor.process(methodProcessor);
         }
       }
     }
​
    // 获取增强后的字节码
    byte[] bytes = AsmUtils.toBytes(classNode);
​
    // 查看反编译结果
    System.out.println(Decompiler.decompile(bytes));
​
    // 修正Sample
    AgentUtils.reTransform(Sample.class, bytes);
​
    // 履行sample的办法
    try {
      Sample sample = new Sample();
      sample.hello("3", false);
      sample.hello("4", true);
     } catch (Exception e) {
      e.printStackTrace();
     }
​
   }
​
}

这是Sample反编译后的结果,代码量剧增

public class Sample {
  private int exceptionCount = 0;
​
  /*
   * WARNING - void declaration
   */
  public String hello(String string, boolean bl) {
    try {
      String string2;
      void str;
      void exception;
      try {
          // @AtEnter 直接调用,inline为false的效果
        SampleInterceptor.atEnter((Object)this, Sample.class, (Object[])new Object[]{string, new Boolean(bl)}, (String)"hello", (String)"(Ljava/lang/String;Z)Ljava/lang/String;");
       }
      catch (RuntimeException runtimeException) {
        Class<Sample> clazz = Sample.class;
        RuntimeException runtimeException2 = runtimeException;
        System.out.println("exception handler: " + clazz);
        runtimeException2.printStackTrace();
       }
      if (exception != false) {
        ++this.exceptionCount;
        throw new RuntimeException("test exception, str: " + (String)str);
       }
      String string3 = string2 = "hello " + (String)str;
        // @AtExit 代码直接刺进
      System.out.println("atExit, returnObject: " + string3);
      return string2;
     }
    catch (RuntimeException runtimeException) {
      int n = this.exceptionCount;
      RuntimeException runtimeException3 = runtimeException;
        // @AtExceptionExit 代码直接刺进
      System.out.println("atExceptionExit, ex: " + runtimeException3.getMessage() + ", field exceptionCount: " + n);
      throw runtimeException;
     }
   }
}
​

有了这个前置常识,咱们来看看trace指令

trace

Java Agent

Arthas指令许多,假如是exit、logout、quit、jobs、fg、bg、kill等简略的指令,就会直接履行,假如是trace这种杂乱的指令,会专门用一个类写处理的逻辑,如上图,依据名字就能够猜到这个类是处理什么指令的,这么多类的组织形式是模版模式,进口在com.taobao.arthas.core.shell.command.AnnotatedCommand#process,

public abstract class AnnotatedCommand {
  public abstract void process(CommandProcess process);
}
​
public class TraceCommand extends EnhancerCommand {
}
public abstract class EnhancerCommand extends AnnotatedCommand {
  @Override
  public void process(final CommandProcess process) {
    // ctrl-C support
    process.interruptHandler(new CommandInterruptHandler(process));
    // q exit support
    process.stdinHandler(new QExitHandler(process));
​
    // start to enhance
    enhance(process);
   }
}

有一些指令都有字节码增强的逻辑,这些逻辑共同封装在了EnhancerCommand这个类中,TraceCommand继承了EnhancerCommand,当trace指令履行的时分,增强的逻辑在EnhancerCommand,咱们只看核心代码

com.taobao.arthas.core.command.monitor200.EnhancerCommand#enhance

com.taobao.arthas.core.advisor.Enhancer#enhance(java.lang.instrument.Instrumentation)

public synchronized EnhancerAffect enhance(final Instrumentation inst) throws UnmodifiableClassException {
     ......
    try {
      // 很明显,这儿添加了一个文件转化器,注意,此处的转化器为本类
      ArthasBootstrap.getInstance().getTransformerManager().addTransformer(this, isTracing);
​
       ......
     } catch (Throwable e) {
      logger.error("Enhancer error, matchingClasses: {}", matchingClasses, e);
      affect.setThrowable(e);
     }
​
    return affect;
   }

依据办法名就能够在本类搜索到,详细代码如下

@Override
public byte[] transform(final ClassLoader inClassLoader, String className, Class<?> classBeingRedefined,
            ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
  try {
   // 检查classloader能否加载到 SpyAPI,假如不能,则放弃增强
   try {
    if (inClassLoader != null) {
     inClassLoader.loadClass(SpyAPI.class.getName());
     }
    } catch (Throwable e) {
    logger.error("the classloader can not load SpyAPI, ignore it. classloader: {}, className: {}",
           inClassLoader.getClass().getName(), className, e);
    return null;
    }
​
   // 这儿要再次过滤一次,为啥?由于在transform的过程中,有或许还会再诞生新的类
   // 所以需求将之前需求转化的类调集传递下来,再次进行判别
   if (matchingClasses != null && !matchingClasses.contains(classBeingRedefined)) {
    return null;
    }
      // ClassNode中有各种特点,对应Class文件结构
   // keep origin class reader for bytecode optimizations, avoiding JVM metaspace OOM.
   ClassNode classNode = new ClassNode(Opcodes.ASM9);
   ClassReader classReader = AsmUtils.toClassNode(classfileBuffer, classNode);
   // remove JSR https://github.com/alibaba/arthas/issues/1304
   classNode = AsmUtils.removeJSRInstructions(classNode);
​
   // 重要代码,生成增强字节码的拦截器
   DefaultInterceptorClassParser defaultInterceptorClassParser = new DefaultInterceptorClassParser();
​
   final List<InterceptorProcessor> interceptorProcessors = new ArrayList<InterceptorProcessor>();
​
   interceptorProcessors.addAll(defaultInterceptorClassParser.parse(SpyInterceptor1.class));
   interceptorProcessors.addAll(defaultInterceptorClassParser.parse(SpyInterceptor2.class));
   interceptorProcessors.addAll(defaultInterceptorClassParser.parse(SpyInterceptor3.class));
​
   if (this.isTracing) {
    // 依据装备判别trace指令是否要越过核算Java类库的代码的耗时
    if (!this.skipJDKTrace) {
     interceptorProcessors.addAll(defaultInterceptorClassParser.parse(SpyTraceInterceptor1.class));
     interceptorProcessors.addAll(defaultInterceptorClassParser.parse(SpyTraceInterceptor2.class));
     interceptorProcessors.addAll(defaultInterceptorClassParser.parse(SpyTraceInterceptor3.class));
     } else {
     interceptorProcessors.addAll(defaultInterceptorClassParser.parse(SpyTraceExcludeJDKInterceptor1.class));
     interceptorProcessors.addAll(defaultInterceptorClassParser.parse(SpyTraceExcludeJDKInterceptor2.class));
     interceptorProcessors.addAll(defaultInterceptorClassParser.parse(SpyTraceExcludeJDKInterceptor3.class));
     }
    }
​
   List<MethodNode> matchedMethods = new ArrayList<MethodNode>();
   for (MethodNode methodNode : classNode.methods) {
    if (!isIgnore(methodNode, methodNameMatcher)) {
     matchedMethods.add(methodNode);
     }
    }
​
   // https://github.com/alibaba/arthas/issues/1690
   if (AsmUtils.isEnhancerByCGLIB(className)) {
    for (MethodNode methodNode : matchedMethods) {
     if (AsmUtils.isConstructor(methodNode)) {
      AsmUtils.fixConstructorExceptionTable(methodNode);
      }
     }
    }
​
    .......
​
   for (MethodNode methodNode : matchedMethods) {
    if (AsmUtils.isNative(methodNode)) {
     logger.info("ignore native method: {}",
           AsmUtils.methodDeclaration(Type.getObjectType(classNode.name), methodNode));
     continue;
     }
    // 先查找是否有 atBeforeInvoke 函数,假如有,则阐明现已有trace了,则直接不再尝试增强,直接刺进 listener
    if(AsmUtils.containsMethodInsnNode(methodNode, Type.getInternalName(SpyAPI.class), "atBeforeInvoke")) {
     for (AbstractInsnNode insnNode = methodNode.instructions.getFirst(); insnNode != null; insnNode = insnNode
        .getNext()) {
      if (insnNode instanceof MethodInsnNode) {
       final MethodInsnNode methodInsnNode = (MethodInsnNode) insnNode;
       if(this.skipJDKTrace) {
        if(methodInsnNode.owner.startsWith("java/")) {
         continue;
         }
        }
       // 原始类型的box类型相关的都越过
       if(AsmOpUtils.isBoxType(Type.getObjectType(methodInsnNode.owner))) {
        continue;
        }
       AdviceListenerManager.registerTraceAdviceListener(inClassLoader, className,
                                methodInsnNode.owner, methodInsnNode.name, methodInsnNode.desc, listener);
       }
      }
     }else {
     // 要点代码,增强动作便是在这儿完结的
     MethodProcessor methodProcessor = new MethodProcessor(classNode, methodNode, groupLocationFilter);
     for (InterceptorProcessor interceptor : interceptorProcessors) {
      try {
       List<Location> locations = interceptor.process(methodProcessor);
       for (Location location : locations) {
        if (location instanceof MethodInsnNodeWare) {
         MethodInsnNodeWare methodInsnNodeWare = (MethodInsnNodeWare) location;
         MethodInsnNode methodInsnNode = methodInsnNodeWare.methodInsnNode();
​
         AdviceListenerManager.registerTraceAdviceListener(inClassLoader, className,
                                  methodInsnNode.owner, methodInsnNode.name, methodInsnNode.desc, listener);
         }
        }
​
       } catch (Throwable e) {
       logger.error("enhancer error, class: {}, method: {}, interceptor: {}", classNode.name, methodNode.name, interceptor.getClass().getName(), e);
       }
      }
     }
​
    // enter/exist 总是要刺进 listener
    AdviceListenerManager.registerAdviceListener(inClassLoader, className, methodNode.name, methodNode.desc,
                           listener);
    affect.addMethodAndCount(inClassLoader, className, methodNode.name, methodNode.desc);
    }
​
   // https://github.com/alibaba/arthas/issues/1223 , V1_5 的major version是49
   if (AsmUtils.getMajorVersion(classNode.version) < 49) {
    classNode.version = AsmUtils.setMajorVersion(classNode.version, 49);
    }
​
   byte[] enhanceClassByteArray = AsmUtils.toBytes(classNode, inClassLoader, classReader);
​
   // 增强成功,记录类
   classBytesCache.put(classBeingRedefined, new Object());
​
   // dump the class
   dumpClassIfNecessary(className, enhanceClassByteArray, affect);
​
   // 成功计数
   affect.cCnt(1);
​
   return enhanceClassByteArray;
   } catch (Throwable t) {
   logger.warn("transform loader[{}]:class[{}] failed.", inClassLoader, className, t);
   affect.setThrowable(t);
   }
​
  return null;
}

这段代码很长,其实主要逻辑就两个

  • 解析Interceptor Class的@AtXxx,@Binding等注解,生成InterceptorProcessor目标调集
  • 遍历InterceptorProcessor调集,修正原办法的字节码

整体的流程如下图

Java Agent

那这些拦截器长什么姿态呢?咱们随意找一个比如来看看

public static class SpyInterceptor1 {
​
  @AtEnter(inline = true)
  public static void atEnter(@Binding.This Object target, @Binding.Class Class<?> clazz,
                @Binding.MethodInfo String methodInfo, @Binding.Args Object[] args) {
   SpyAPI.atEnter(clazz, methodInfo, target, args);
   }
}

看到这儿,就很熟悉了,跟上面bytekit的比如很像,是在办法进入时刺进的,当然,这儿仅仅浅讲一下trace的原理,bytekit背后的原理,需求更底层的常识储藏,我还需求持续学习

参考资料

developer.aliyun.com/article/768…

arthas.aliyun.com/doc/trace.h…

blog.csdn.net/tianjindong…