JvmSandbox原理分析05-动态字节码增强

上一篇我们聊完了Sandbox模块加载的流程,并在结尾说明Sandbox的启动流程也将在模块加载完成后结束,可以由如下流程进行描述:

但这时候就有人说了:“不对啊,代码增强呢?inst对象呢?前面铺垫了这么久,咋啥没说就结束了呢?“。哈哈哈,这不来了嘛。

这一篇算是整个启动流程的补充,因为代码增强的逻辑是写在module中的,即在module加载时进行业务代码增强。但Sandbox中的module不全具备增强逻辑,还有些仅是用作沙箱与操作系统通信的,比如第三篇介绍的InfoModule

因此,前面的文章在模块加载完成后就结束了。而这篇补充篇则是介绍module作为字节码增强器的功能与逻辑。

1、LogExceptionModule

Sandbox代码工程中内置了几个模块,我们挑选其中一个逻辑最轻的LogExceptionModule进行讲解,只要把握主其中的增强逻辑,我相信再复杂的处理,你都能迎刃而解。

LogExceptionModule的代码如下所示:

 // LogExceptionModule.java
 @MetaInfServices(Module.class)
 @Information(id = "debug-exception-logger", version = "0.0.2", author = "[email protected]")
 public class LogExceptionModule implements Module, LoadCompleted {
     private final Logger exLogger = LoggerFactory.getLogger("DEBUG-EXCEPTION-LOGGER");
     @Resource
     private ModuleEventWatcher moduleEventWatcher;
     @Override
     public void loadCompleted() {
         new EventWatchBuilder(moduleEventWatcher)
                 .onClass(Exception.class)
                 .includeBootstrap()
                 .onBehavior("<init>")
                 .onWatch(new EventListener() {
                     @Override
                     public void onEvent(Event event) throws Throwable {
                         final BeforeEvent bEvent = (BeforeEvent) event;
                         exLogger.info("{} occur an exception: {}", getJavaClassName(bEvent.target.getClass()), bEvent.target);
                     }
                 }, BEFORE);
     }
 }
复制代码

其中几个自定义的注解我们都有在前面介绍,这里再来回顾下:

  • @MetaInfServices:能够在打包时为该module添加JDK SPI需要的文件,不用在maven中手动写明或者打包后手动添加
  • @Information:标注模块的一些信息,会在模块加载时根据标注的信息进行校验
  • @Resource:需要注入的变量,会在模块加载时通过反射注入变量

loadCompleted()这个方法相信大家非常熟悉,这个是模块的生命周期钩子,模块加载完成后会调用这个方法。

这个方法中的EventWatchBuilder是构造监听配置的构造器,我们可以通过一系列方法构造我们想要监听的配置,比如想要增强哪个类的哪个方法,是JDK的类还是业务类等配置,这个大家自行看下就行了,这里就不多说了。

方法中最需要关注的就是moduleEventWatcher和EventListener的作用,下面也将对这两个类进行详细说明。

1.1、ModuleEventWatcher

我们先来看看它是如何被注入的,这里只节选了其中一段代码:

 // DefaultCoreModuleManager.java
 // 如果被@Resource注解标注的字段类型为ModuleEventWatcher,则会进入到这个if判断中
 else if (ModuleEventWatcher.class.isAssignableFrom(fieldType)) {
     final ModuleEventWatcher moduleEventWatcher = coreModule.append(
         // 构造ReleaseResource资源,即ModuleEventWatcher作为ReleaseResource是可释放的。在模块卸载时调用release方法释放资源
         new ReleaseResource<ModuleEventWatcher>(
             // 守护引用
             SandboxProtector.instance.protectProxy(
                 ModuleEventWatcher.class,
                 // DefaultModuleEventWatcher 为 ModuleEventWatcher 的默认实现
                 new DefaultModuleEventWatcher(inst, classDataSource, coreModule, cfg.isEnableUnsafe(), cfg.getNamespace())
             )
         ) {
             // 定义 ReleaseResource 的 release 方法,在模块卸载时调用
             @Override
             public void release() {
                 logger.info("release all SandboxClassFileTransformer for module={}", coreModule.getUniqueId());
                 final ModuleEventWatcher moduleEventWatcher = get();
                 if (null != moduleEventWatcher) {
                     for (final SandboxClassFileTransformer sandboxClassFileTransformer
                          : new ArrayList<SandboxClassFileTransformer>(coreModule.getSandboxClassFileTransformers())) {
                         moduleEventWatcher.delete(sandboxClassFileTransformer.getWatchId());
                     }
                 }
             }
         });
     // 这个是apache反射包中的方法,反射字段
     writeField(resourceField, module, moduleEventWatcher, true);
 }
复制代码

相应的功能在注释中表达的很明确了,在LogExceptionModule模块加载时,DefaultModuleEventWatcher类会实例化一个对象通过反射写到LogExceptionModule模块的moduleEventWatcher属性上,moduleEventWatcher这个属性也是一个可释放资源,在模块卸载时被释放。

ModuleEventWatcher是代码增强的核心,可以看到在构造DefaultModuleEventWatcher时,传递了inst对象进去,说明后期增强业务方的代码都需要依赖watcher这个对象。

1.2、EventListener

EventListener是非常重要的一个组件,官方称其事件监听器。

之前有说过,Sandbox会将Spy和SpyHandler(实现为EventListenerHandler)写入业务方的代码里面,每当事件触发时,handler便会把事件分发出来,交由Sandbox中的某个module处理。

当然,这是形象的说法,准确的说法应该是,在module初始化时,Sandbox会将module中的EventListener一起给写入业务方的代码里面,每当事件触发时,SpyHandler会委托给对应module的EventListener处理,这样就类似事件分发给这个module了。

详细的代码跟踪,我们在后面再说,我们先来看看这个EventListener做了什么,可以看到非常简单,只是打印了一条日志而已。

 exLogger.info("{} occur an exception: {}", getJavaClassName(bEvent.target.getClass()), bEvent.target);
复制代码

1.3、小结

由此,我们可以简单总结下LogExceptionModule功能:这个module会在业务方每次抛出异常(即Exception类初始化时),在Execption构造器的入口处打印一条日志。

2、增强逻辑

本节我们来看看Sandbox具体的增强逻辑吧!

增强的入口是EventWatchBuilder的onWatch()方法:

 // EventWatchBuilder.java
 @Override
 public EventWatcher onWatch(EventListener eventListener, Event.Type... eventTypeArray) {
     return build(eventListener, null, eventTypeArray);
 }
 ​
 // EventWatchBuilder.java
 private EventWatcher build(final EventListener listener, final Progress progress, final Event.Type... eventTypes) {
     final int watchId = moduleEventWatcher.watch(toEventWatchCondition(), listener, progress, eventTypes);
     return new EventWatcher() {
         // ...匿名类的一些方法实现,可以忽略
     };
 }
复制代码

最终干活的还是moduleEventWatcher,我们在到DefaultModuleEventWatcher这个默认实现里面看看watch()方法干了什么吧!记住这里的listener和eventTypes是LogExceptionModule里面的匿名内部类和Before类型。

 // DefaultModuleEventWatcher.java
 @Override
 public int watch(final EventWatchCondition condition, final EventListener listener, final Progress progress, final Event.Type... eventType) {
     return watch(toOrGroupMatcher(condition.getOrFilterArray()), listener, progress, eventType);
 }
 // DefaultModuleEventWatcher.java
 private int watch(final Matcher matcher, final EventListener listener, final Progress progress, final Event.Type... eventType) {
     final int watchId = watchIdSequencer.next();
     // [1] 给对应的模块追加ClassFileTransformer,这个SandboxClassFileTransformer可是异常重要,代码增强全靠它了
     final SandboxClassFileTransformer sandClassFileTransformer = new SandboxClassFileTransformer(
         watchId, coreModule.getUniqueId(), matcher, listener, isEnableUnsafe, eventType, namespace);
     // 注册到CoreModule中
     coreModule.getSandboxClassFileTransformers().add(sandClassFileTransformer);
     // [2] 这里addTransformer后,接下来引起的类加载都会经过sandClassFileTransformer
     inst.addTransformer(sandClassFileTransformer, true);
     // [3] 查找需要渲染的类集合
     final List<Class<?>> waitingReTransformClasses = classDataSource.findForReTransform(matcher);
     logger.info("watch={} in module={} found {} classes for watch(ing).", watchId, coreModule.getUniqueId(), waitingReTransformClasses.size());
     int cCnt = 0, mCnt = 0;
     // 进度通知启动
     beginProgress(progress, waitingReTransformClasses.size());
     try {
         // [4] 应用JVM
         reTransformClasses(watchId,waitingReTransformClasses, progress);
         // 计数
         cCnt += sandClassFileTransformer.getAffectStatistic().cCnt();
         mCnt += sandClassFileTransformer.getAffectStatistic().mCnt();
         // 激活增强类
         if (coreModule.isActivated()) {
             final int listenerId = sandClassFileTransformer.getListenerId();
             // [5] 添加EventProcessor注册信息
             EventListenerHandler.getSingleton().active(listenerId, listener, eventType);
         }
     } finally {
         finishProgress(progress, cCnt, mCnt);
     }
     return watchId;
 }
 // DefaultModuleEventWatcher.java
 private void reTransformClasses() {
     // ...
     for (final Class<?> waitingReTransformClass : waitingReTransformClasses) {
         // ...
         inst.retransformClasses(waitingReTransformClass);
     }
     // ...
 }
复制代码

watch()方法要做的事还是挺多的:

  • [1] 创建一个SandboxClassFileTransformer实例对象,如果对Java Instrumentation有过了解的话,就很清楚该类就是用来对JVM中的字节码做增强的(类形变、字节码改变)。注意,这里将matcher, listener, eventType都传递了进入,后面也会用到这些变量。
  • [2] 调用inst.addTransformer()方法,将上述创建的SandboxClassFileTransformer对象传递进去,表明之后增强的逻辑全部都会经过SandboxClassFileTransformer
  • [3] 查找渲染类集合,其实也是查找待增强的类集合。那在哪里去查找呢?相信大家肯定还记得,我们在创建DefaultCoreModuleManager对象时,该对象包含了一个classDataSource表示所有已加载类的数据池。查找待增强类就是通过matcher对象去classDataSource查找所有匹配的类,这些类就是待增强的类。那matcher对象又是怎么构造的呢?这就跟我们在LogExceptionModule中构造EventWatchBuilder时传递的参数有关了,感兴趣的同学可以研究下,这里就不过多叙述了。
  • [4] 将SandboxClassFileTransformer应用在当前attach的JVM上,该私有方法reTransformClasses()在也给出了,这里我们省略了许多不重要的逻辑,我们目前只需知道,reTransformClasses()会遍历查找出的待增强类,然后使用inst.retransformClasses()方法进行增强,这个方法执行完成,待增强的类中就会插入我们预先定义好的字节码,这就实现了字节码增强的功能。
  • [5] 又看到了这段熟悉的代码,这个在之前也讲过,激活的逻辑其实并不复杂,仅是在EventListenerHandler中的mappingOfEventProcessor注册表添加注册信息,之后每次分发事件时,会判断注册表中是否存在相关信息,存在则分发,否则不分发。

增强的逻辑其实讲到这里已经说完了,执行完这些步骤后,目标JVM里的相关类便实现了字节码增强。可有些人会问:”不对啊,到底增强了那些代码呢,又增强了什么逻辑呢?“

这就要讲到SandboxClassFileTransformer这个非常重要的类了。

3、SandboxClassFileTransformer实现字节码增强

声明:由于作者知识水平受限,对asm框架和字节码了解有限,也就没有对其核心执行原理进行探讨。因此本小节只做脉络梳理,对于增强细节,大家可以自行深入了解。待后续作者能力有所提高,再来完善本小节内容。

上一小节我们说到,在DefaultModuleEventWatcher中,会初始化SandboxClassFileTransformer的实例。那么我们就从它初始化开始说起吧:

 // DefaultModuleEventWatcher.java
 final SandboxClassFileTransformer sandClassFileTransformer = new SandboxClassFileTransformer(
         watchId, coreModule.getUniqueId(), matcher, listener, isEnableUnsafe, eventType, namespace);
 ​
 // SandboxClassFileTransformer.java
 public class SandboxClassFileTransformer implements ClassFileTransformer {
     SandboxClassFileTransformer(final int watchId, final String uniqueId, final Matcher matcher, final EventListener eventListener,
                                 final boolean isEnableUnsafe, final Event.Type[] eventTypeArray, final String namespace) {
         this.watchId = watchId;
         this.uniqueId = uniqueId;
         this.matcher = matcher;
         this.eventListener = eventListener;
         this.isEnableUnsafe = isEnableUnsafe;
         this.eventTypeArray = eventTypeArray;
         this.namespace = namespace;
         this.listenerId = ObjectIDs.instance.identity(eventListener);
     }
 }
复制代码

从源码中看,初始化过程好像也没啥,就是简单的赋值语句而已。但要注意,SandboxClassFileTransformer继承了ClassFileTransformer,因此它肯定要重写transform()方法,这个方法在增强字节码的时候会被调用。来看看这个方法做了什么吧,我们这里省略掉无关判断逻辑,只保留核心步骤:

 // SandboxClassFileTransformer.java
 @Override
 public byte[] transform(final ClassLoader loader, final String internalClassName, final Class<?> classBeingRedefined,
                         final ProtectionDomain protectionDomain, final byte[] srcByteCodeArray) {
     try {
         // ...
         // [1]
         return _transform(loader, internalClassName, classBeingRedefined, srcByteCodeArray);
     }
 }
 // SandboxClassFileTransformer.java
 private byte[] _transform(final ClassLoader loader, final String internalClassName, final Class<?> classBeingRedefined, final byte[] srcByteCodeArray) {
     // ...
     // [2] 获取当前类结构
     final ClassStructure classStructure = getClassStructure(loader, classBeingRedefined, srcByteCodeArray);
     // [3] 检查方法是否匹配
     final MatchingResult matchingResult = new UnsupportedMatcher(loader, isEnableUnsafe).and(matcher).matching(classStructure);
     // [4] 获取匹配方法的签名列表
     final Set<String> behaviorSignCodes = matchingResult.getBehaviorSignCodes();
     // ...
     try {
         // [5] 对指定方法进行增强
         final byte[] toByteCodeArray =
             new EventEnhancer().toByteCodeArray(loader, srcByteCodeArray, behaviorSignCodes, namespace, listenerId, eventTypeArray);
         // ...
         return toByteCodeArray;
     } catch (Throwable cause) {
         // ...
     }
 }
复制代码
  • [1] 这里经过了一些判断后调用私有的_transform()方法进行类转换
  • [2] 获取当前类结构,getClassStructure()方法一般返回ClassStructureImplByAsm类对象,表明使用ASM框架来获取当前的类结构
  • [3] 可以看到,这行代码同时使用了matcher和classStructure。后者是当前类的字节码结构,不用多说;前者包含了module中配置的一些信息,有类名和方法名等,如果忘记了可以看下本文最开始分析的LogExceptionModule这个module。知道了这两个变量,这行代码的作用也就不言而喻了,就是在当前字节码结构中筛选出与matcher匹配的方法(官方注释为行为)。
  • [4] 从匹配的方法(行为)中获取方法签名的集合
  • [5] 关键的地方就在这里了,这行代码会对源字节码中匹配的方法进行字节码增强

接下来增强的逻辑来到了EventEnhancer#toByteCodeArray()方法中了,我们看看这个方法接受了哪些重要变量吧:

  • srcByteCodeArray表示类的字节码二进制数据;
  • behaviorSignCodes表示匹配的方法签名,也就是后期需要增强的方法签名;
  • listenerId表示EventListener对应的唯一ID,它在SandboxClassFileTransformer初始化时创建,与EventListener一一对应;
  • eventTypeArray表示监听的事件类型。

EventEnhancer#toByteCodeArray()方法源码如下:

 // EventEnhancer.java
 @Override
 public byte[] toByteCodeArray(final ClassLoader targetClassLoader,
                               final byte[] byteCodeArray,
                               final Set<String> signCodes,
                               final String namespace,
                               final int listenerId,
                               final Event.Type[] eventTypeArray) {
     final ClassReader cr = new ClassReader(byteCodeArray);
     final ClassWriter cw = createClassWriter(targetClassLoader, cr);
     final int targetClassLoaderObjectID = ObjectIDs.instance.identity(targetClassLoader);
     cr.accept(new EventWeaver(ASM7, cw, namespace, listenerId, targetClassLoaderObjectID,
         cr.getClassName(), signCodes, eventTypeArray), EXPAND_FRAMES);
     // 将增强后的字节码以文件形式输出,仅用于调试使用
     return dumpClassIfNecessary(cr.getClassName(), cw.toByteArray());
 }
复制代码

想要理解上面的代码需要掌握ASM框架的核心执行原理,其中ClassReader、ClassWriter是ASM中核心类,EventWeaver也是继承了ASM框架中的核心类ClassVisitor才实现了字节码编织的功能。

这里我们忽略ASM框架的原理,只要知道这段代码是进行类增强的即可。并且核心增强逻辑由EventWeaver进行控制。

dumpClassIfNecessary()这个方法能够将增强后的字节码以文件的格式输出,只要在该方法中开启dump开关即可,大家可以自行查看。

下面我们来看看EventWeaver这个类,它的构造方法没啥可说的,是一些赋值语句。我们主要关心它的visitMethod()方法,这个方法也是重写了ClassVisitor#visitMethod(),当ASM去访问类中的method时,会调用这个方法获取MethodVisitor对象执行相关访问逻辑,重要性可想而知。

 // EventWeaver.java
 public class EventWeaver extends ClassVisitor implements Opcodes, AsmTypes, AsmMethods {
     @Override
     public MethodVisitor visitMethod(final int access, final String name, final String desc, final String signature, final String[] exceptions) {
         final MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions);
         // [1] 判断当前方法的方法签名是否在匹配的方法签名集合中
         final String signCode = getBehaviorSignCode(name, desc);
         if (!isMatchedBehavior(signCode)) {
             logger.debug("non-rewrite method {} for listener[id={}];", signCode, listenerId);
             return mv;
         }
         logger.info("rewrite method {} for listener[id={}];event={};", signCode, listenerId, join(eventTypeArray, ","));
         // [2] 返回一个自定义的 MethodVisitor 对象
         return new ReWriteMethod(api, new JSRInlinerAdapter(mv, access, name, desc, signature, exceptions), access, name, desc) {
             // [3]
         }
     }
 }
复制代码
  • [1] 首先会判断当前访问的方法的方法签名是否在匹配的方法签名集合中,isMatchedBehavior()方法会用到构造EventWeaver时传递的在SandboxClassFileTransformer中创建的匹配方法签名集合,只有在该集合中,才会使用后面的ReWriteMethod这个自定义的MethodVisitor。
  • [2] 返回一个自定义的 MethodVisitor 对象
  • [3] 这个自定义的 MethodVisitor 对象会重写许多方法,这些重写的方法定义了访问一个method时,字节码如何重新组织的逻辑,由于有许多重写的方法,我们仅挑选其中一个进行重点讲解。

我们这里挑选onMethodEnter()这个方法进行讲解,这个方法表明,进入一个method时,需要执行的字节码指令是什么。

 // EventWeaver.java
 @Override
 protected void onMethodEnter() {
     codeLockForTracing.lock(new CodeLock.Block() {
         @Override
         public void code() {
             mark(beginLabel);
             loadArgArray();
             dup();
             push(namespace);
             push(listenerId);
             loadClassLoader();
             push(targetJavaClassName);
             push(name);
             push(desc);
             loadThisOrPushNullIfIsStatic();
             // 这一行是重点
             invokeStatic(ASM_TYPE_SPY, ASM_METHOD_Spy$spyMethodOnBefore);
             swap();
             storeArgArray();
             pop();
             processControl();
             isMethodEnter = true;
         }
     });
 }
复制代码

如果大家对 Java 字节码熟悉的话,dup、push这些指令应该有所了解。但这不是今天的重点,我们重点是要关心invokeStatic(ASM_TYPE_SPY, ASM_METHOD_Spy$spyMethodOnBefore);。这行代码表示插入一段执行静态方法对应的字节码。

而要插入的静态方法则被定义在这个变量中ASM_METHOD_Spy$spyMethodOnBefore,而该变量又被定义在AsmMethods接口中。

 // AsmMethods.java
 // [1] 调用getAsmMethod()方法获取org.objectweb.asm.commons.Method对象
 Method ASM_METHOD_Spy$spyMethodOnBefore = getAsmMethod(
     Spy.class,
     "spyMethodOnBefore",
     Object[].class, String.class, int.class, int.class, String.class, String.class, String.class, Object.class
 );
 // AsmMethods.java
 class InnerHelper {
     private InnerHelper() {}
     // [2] 调用unCaughtGetClassDeclaredJavaMethod()方法获取java.lang.reflect.Method对象
     static Method getAsmMethod(final Class<?> clazz, final String methodName, final Class<?>... parameterClassArray) {
         return Method.getMethod(unCaughtGetClassDeclaredJavaMethod(clazz, methodName, parameterClassArray));
     }
 }
 // SandboxReflectUtils.java
 public static Method unCaughtGetClassDeclaredJavaMethod(final Class<?> clazz, final String name, final Class<?>... parameterClassArray) {
     try {
         // [3] 反射调用获取方法
         return clazz.getDeclaredMethod(name, parameterClassArray);
     } catch (NoSuchMethodException e) {
         throw new UnCaughtException(e);
     }
 }
复制代码
  • [1] AsmMethods接口中定义的ASM_METHOD_Spy$spyMethodOnBefore变量是通过getAsmMethod()方法获得,调用这个方法时传递了Spy.class"spyMethodOnBefore"两个参数,说明获取的method为Spy#spyMethodOnBefore。嗯,从这里开始就和Spy这个间谍类关联上了,它可是在前几篇中经常出现的类啊,终于在这里讲解了它的作用了。
  • [2] getAsmMethod方法首先会调用unCaughtGetClassDeclaredJavaMethod()方法反射获取到Spy#spyMethodOnBefore的java.lang.reflect.Method对象,然后调用Method.getMethod()对其包装,随即获得org.objectweb.asm.commons.Method对象,最后在invokeStatic()方法中写入编织的静态方法字节码。
  • [3] 反射调用获取java.lang.reflect.Method对象

字节码增强的过程其实到这里已经结束了。下面我们可以深入了解增强的静态方法字节码究竟是什么。这就要从Spy#spyMethodOnBefore方法入手了。

4、Spy增强的静态方法

Spy#spyMethodOnBefore方法源码如下,这里省略了一些不重要的细节:

 // Spy.java
 public static Ret spyMethodOnBefore(final Object[] argumentArray,
                                     final String namespace,
                                     final int listenerId,
                                     final int targetClassLoaderObjectID,
                                     final String javaClassName,
                                     final String javaMethodName,
                                     final String javaMethodDesc,
                                     final Object target) throws Throwable {
     // ...
     try {
         final SpyHandler spyHandler = namespaceSpyHandlerMap.get(namespace);
         if (null == spyHandler) {
             return Ret.RET_NONE;
         }
         return spyHandler.handleOnBefore(listenerId, targetClassLoaderObjectID, argumentArray, javaClassName, javaMethodName, javaMethodDesc, target);
     }
     // catch finally ...
 }
复制代码

方法逻辑非常简单,就是从namespaceSpyHandlerMap中获取指定名称空间的SpyHandler,然后利用该SpyHandler将方法所属类名、方法名称、方法签名、方法参数列表等信息传递出去。

这里的SpyHandler其实就是EventListenerHandler,下面来看看EventListenerHandler#handleOnBefore的执行逻辑吧,这里同样省略一些逻辑:

 // EventListenerHandler.java
 @Override
 public Spy.Ret handleOnBefore(int listenerId, int targetClassLoaderObjectID, Object[] argumentArray, String javaClassName, String javaMethodName, String javaMethodDesc, Object target) throws Throwable {
     // ...
     // [1] 获取事件处理器
     final EventProcessor processor = mappingOfEventProcessor.get(listenerId);
     // 如果尚未注册,则直接返回,不做任何处理
     if (null == processor) {
         logger.debug("listener={} is not activated, ignore processing before-event.", listenerId);
         return newInstanceForNone();
     }
     // ...
     // [2] 组装BeforeEvent
     final BeforeEvent event = process.getEventFactory().makeBeforeEvent(
         processId,
         invokeId,
         javaClassLoader,
         javaClassName,
         javaMethodName,
         javaMethodDesc,
         target,
         argumentArray
     );
     try {
         // [3] 分发event事件
         return handleEvent(listenerId, processId, invokeId, event, processor);
     } finally {
         process.getEventFactory().returnEvent(event);
     }
 }
 // EventListenerHandler.java
 private Spy.Ret handleEvent(final int listenerId,
                             final int processId,
                             final int invokeId,
                             final Event event,
                             final EventProcessor processor) throws Throwable {
     // 获取事件监听器
     final EventListener listener = processor.listener;
     // 如果当前事件不在事件监听器处理列表中,则直接返回,不处理事件
     if (!contains(processor.eventTypes, event.type)) {
         return newInstanceForNone();
     }
     // 调用事件处理
     try {
         // ...log
         // [4]
         listener.onEvent(event);
     }
     // ...catch
     // 默认返回不进行任何流程变更
     return newInstanceForNone();
 }
复制代码
  • [1] 从注册表中获取事件处理器,mappingOfEventProcessor这个注册表我们可是非常熟悉了,之前将模块冻结和激活时都讲到了它。在进行事件分发前,会从该注册表中判断是否存在module定义的EventListener所对应的listenerId,如果存在的话,则正常进行事件分发,否则不分发事件。怎么样,是不是和前面的内容对应上了。
  • [2] 将方法所属类名、方法名称、方法签名、方法参数列表等信息组装成BeforeEvent进行分发。
  • [3] 调用handleEvent()方法分发事件。
  • [4] 最终调用listener.onEvent()方法将事件分发出去,这个listener就是LogExceptionModule中构造EventWatchBuilder时定义的匿名内部类。
 public void loadCompleted() {
     new EventWatchBuilder(moduleEventWatcher)
         .onClass(Exception.class)
         .includeBootstrap()
         .onBehavior("<init>")
         .onWatch(new EventListener() {
             // 第四步[4]将回调到这个方法上来。
             @Override
             public void onEvent(Event event) throws Throwable {
                 // 最终,这个event就是上述第二步[2]封装的BeforeEvent,它包含了方法所属类名、方法名称、方法签名、方法参数列表等信息
                 final BeforeEvent bEvent = (BeforeEvent) event;
                 exLogger.info("{} occur an exception: {}", getJavaClassName(bEvent.target.getClass()), bEvent.target);
             }
         }, BEFORE);
 }
复制代码

嗯,讲到这里,字节码增强的所有逻辑已经完美闭环了,其中还有些事件类型(EventType)判断、EventProcessor的构造、处理等,大家可以根据这条主线脉络自行探索学习哈!

5、总结

本篇从内置的LogExceptionModule出发,介绍了Sandbox字节码增强功能的核心逻辑,包括增强逻辑、SandboxClassFileTransformer类形变器、Spy增强的静态方法等。当然,作为Jvm-Sandbox的核心功能,内容完全不止这些,本篇文章仅是抛砖,讲解了主要脉络,其中的各类细节,大家完全可以随着这条主脉络深入学习。

猜你喜欢

转载自juejin.im/post/7104240362813653005