JvmSandbox原理分析04-可扩展模块与类隔离设计

这一篇我们将聚焦在CoreModuleManager是如何加载图中的各个module,module是体现jvm-sandbox强大扩展能力的组件,我们可以通过自定义各种功能的组件实现不同功能,其中阿里开源的jvm-sandbox-repeater正是由此扩展而来,能够实现流量的录制回放。基于jvm-sandbox,我们还可以做许多好玩的东西,正如作者所说的:“JVM-SANDBOX还能帮助你做很多很多,取决于你的脑洞有多大了。”。

模块加载的入口如下所示,接下来,我们将由该入口出发,介绍Sandbox是如何卸载、加载模块,以及模块的生命周期管理。

 // JettyCoreServer.java
 jvmSandbox.getCoreModuleManager().reset();
复制代码

1、模块重置

上一篇我们介绍了沙箱(JvmSandbox)的初始化,了解了在其初始化时会创建一个模块管理器,默认实现为DefaultCoreModuleManager,模块加载的大部分逻辑都封装在该默认实现中,当然也包括下面所要介绍的重置方法。

重置的逻辑非常简单,首先会卸载所有的模块,这是为了防止再次启动时,模块未清理干净。随后会遍历所有的模块路径,创建ModuleLibLoader,然后调用其load()方法进行模块加载。

 // DefaultCoreModuleManager.java
 @Override
 public synchronized CoreModuleManager reset() throws ModuleException {
     logger.info("resetting all loaded modules:{}", loadedModuleBOMap.keySet());
     // 1. 强制卸载所有模块
     unloadAll();
     // 2. 遍历moduleLibDirArray,加载目录中所有模块。moduleLibDirArray是从全局配置CoreConfigure解析获得
     for (final File moduleLibDir : moduleLibDirArray) {
         // 用户模块加载目录,加载用户模块目录下的所有模块
         // 对模块访问权限进行校验
         if (moduleLibDir.exists() && moduleLibDir.canRead()) {
             new ModuleLibLoader(moduleLibDir, cfg.getLaunchMode())
                     .load(new InnerModuleJarLoadCallback(), new InnerModuleLoadCallback());
         } else {
             logger.warn("module-lib not access, ignore flush load this lib. path={}", moduleLibDir);
         }
     }
     return this;
 }
复制代码

2、模块卸载

卸载所有模块的流程如下,遍历已经加载的模块注册表,随后调用unload()进行单个模块的卸载。

单个模块的冻结逻辑如下:

源码分析如下所示:

 // DefaultCoreModuleManager.java
 @Override
 public void unloadAll() {
     logger.info("force unloading all loaded modules:{}", loadedModuleBOMap.keySet());
     // 遍历已经加载的模块注册表,强制卸载所有模块
     for (final CoreModule coreModule : new ArrayList<CoreModule>(loadedModuleBOMap.values())) {
         try {
             unload(coreModule, true);
         } catch (ModuleException cause) {
             // ... 一些异常处理
         }
     }
 }
 // DefaultCoreModuleManager.java
 @Override
 public synchronized CoreModule unload(final CoreModule coreModule, final boolean isIgnoreModuleException) throws ModuleException {
     // 如果模块已经被卸载,则直接返回
     if (!coreModule.isLoaded()) {
         logger.debug("module already unLoaded. module={};", coreModule.getUniqueId());
         return coreModule;
     }
     logger.info("unloading module, module={};class={};", coreModule.getUniqueId(), coreModule.getModule().getClass().getName());
     // 尝试冻结模块
     frozen(coreModule, isIgnoreModuleException);
     try {
         // 通知生命周期
         callAndFireModuleLifeCycle(coreModule, MODULE_UNLOAD);
     } catch (ModuleException meCause) {
         // ...一些异常处理
     }
     // 从模块注册表中删除
     loadedModuleBOMap.remove(coreModule.getUniqueId());
     // 标记模块为:已卸载
     coreModule.markLoaded(false);
     // 释放所有可释放资源
     coreModule.releaseAll();
     // 尝试关闭ClassLoader
     closeModuleJarClassLoaderIfNecessary(coreModule.getLoader());
     return coreModule;
 }
 // DefaultCoreModuleManager.java
 @Override
 public synchronized void frozen(final CoreModule coreModule, final boolean isIgnoreModuleException) throws ModuleException {
     // 如果模块已经被冻结(尚未被激活),则直接幂等返回
     if (!coreModule.isActivated()) {
         logger.debug("module already frozen. module={};", coreModule.getUniqueId());
         return;
     }
     // ...日志
     // 通知生命周期
     try {
         callAndFireModuleLifeCycle(coreModule, MODULE_FROZEN);
     } catch (ModuleException meCause) {
         // ...一些异常处理
     }
     // 冻结所有监听器
     for (final SandboxClassFileTransformer sandboxClassFileTransformer : coreModule.getSandboxClassFileTransformers()) {
         EventListenerHandler.getSingleton().frozen(sandboxClassFileTransformer.getListenerId());
     }
     // 标记模块为:已冻结
     coreModule.markActivated(false);
 }
 ​
复制代码

从源码中可以看到,作者将卸载的每一步封装成了一个方法,因此,模块卸载的整体逻辑还是非常清晰的。大家可以先看流程图把握住整体,然后针对自己感兴趣的地方进行深入分析,因为卸载的每一个步骤都比较简单,大家可以自行深入源码了解。

2.1、冻结监听器

这里对冻结监听器稍微多说点,因为它涉及到后面要介绍的Spy与沙箱通信的内容,这里先提前说下。不然这里的冻结确实不太好理解。

 // 冻结所有监听器
 for (final SandboxClassFileTransformer sandboxClassFileTransformer : coreModule.getSandboxClassFileTransformers()) {
     EventListenerHandler.getSingleton().frozen(sandboxClassFileTransformer.getListenerId());
 }
复制代码

前面我们介绍过,SpyHandler最终会被埋在(增强)目标JVM的业务代码中,后期触发的所有事件(event)都会由它来分发给沙箱,随后交由沙箱的模块进行处理。而EventListenerHandler为SpyHandler的具体实现。

EventListenerHandler的内部会持有一个名为mappingOfEventProcessor的map,这个map可以理解为一个注册表,它注册了LISTENER_ID和EventProcessor的映射关系,每当EventListenerHandler分发事件时,它会判断该事件包含的LISTENER_ID是否存在注册表中。如果存在,则正常分发事件,如果不存在,则直接return。

我们目前可以认为LISTENER_ID与模块(CoreModule)是绑定的。EventListenerHandler分发事件,其实就是将事件分发给LISTENER_ID对应的模块而已,mappingOfEventProcessor其实就是EventListenerHandler中所有已经注册的模块信息。

有了上面的认识后,我们回过来说说冻结监听器的逻辑,代码如下所示,怎么样,是不是出人意料的简单。“冻结”其实就是把模块对应的LISTENER_ID从注册表中移除而已。这么做之后,每当事件分发时,发现事件包含的LISTENER_ID在注册表中并不存在,因此事件就永远不会分发到对应的模块进行处理,等同于模块永远不会处理任何事件,这个模块是不是就像被“冻结”住了?

 // EventListenerHandler.java
 public void frozen(int listenerId) {
     final EventProcessor processor = mappingOfEventProcessor.remove(listenerId);
     if (null == processor) {
         logger.debug("ignore frozen listener={}, because not found.", listenerId);
         return;
     }
     logger.info("frozen listener[id={};target={};]", listenerId, processor.listener );
     // processor.clean();
 }
复制代码

3、模块生命周期

在上面的小节中,我们发现了有两处地方都调用了同一个方法callAndFireModuleLifeCycle(),从名字中也很容器推断出来,这是调用模块生命周期钩子的方法。它通过传递不同类型的枚举量来执行模块不同生命周期钩子,我们来看看这个方法。

这个方法的逻辑清晰易懂,首先会判断模块是否实现了ModuleLifecycle接口,如果实现了,说明这个模块具有生命周期的概念。随后会根据枚举类型调用该模块的生命周期钩子,让模块在指定的时候处理一些用户需要执行的逻辑。

 // DefaultCoreModuleManager.java
 private void callAndFireModuleLifeCycle(final CoreModule coreModule, final ModuleLifeCycleType type) throws ModuleException {
     if (coreModule.getModule() instanceof ModuleLifecycle) {
         final ModuleLifecycle moduleLifecycle = (ModuleLifecycle) coreModule.getModule();
         final String uniqueId = coreModule.getUniqueId();
         switch (type) {
             case MODULE_LOAD: {
                 try {
                     moduleLifecycle.onLoad();
                 } catch (Throwable throwable) {
                     throw new ModuleException(uniqueId, MODULE_LOAD_ERROR, throwable);
                 }
                 break;
             } case MODULE_UNLOAD: {
                 try {
                     moduleLifecycle.onUnload();
                 } catch (Throwable throwable) {
                     throw new ModuleException(coreModule.getUniqueId(), MODULE_UNLOAD_ERROR, throwable);
                 }
                 break;
             } case MODULE_ACTIVE: {
                 try {
                     moduleLifecycle.onActive();
                 } catch (Throwable throwable) {
                     throw new ModuleException(coreModule.getUniqueId(), MODULE_ACTIVE_ERROR, throwable);
                 }
                 break;
             } case MODULE_FROZEN: {
                 try {
                     moduleLifecycle.onFrozen();
                 } catch (Throwable throwable) {
                     throw new ModuleException(coreModule.getUniqueId(), MODULE_FROZEN_ERROR, throwable);
                 }
                 break;
             }
         }
     }
     // 这里要对LOAD_COMPLETED事件做特殊处理
     // 因为这个事件处理失败不会影响模块变更行为,只做简单的日志处理
     if (type == MODULE_LOAD_COMPLETED && coreModule.getModule() instanceof LoadCompleted) {
         try {
             ((LoadCompleted) coreModule.getModule()).loadCompleted();
         } catch (Throwable cause) {
             logger.warn("loading module occur error when load-completed. module={};", coreModule.getUniqueId(), cause);
         }
     }
 }
复制代码

目前Sandbox对模块定义了如下几种钩子:

  • onLoad:模块加载,模块开始加载之调用
  • onActive:模块激活,模块开始激活之调用
  • loadCompleted:模块加载完成,模块完成加载调用
  • onFrozen:模块冻结,模块开始冻结之调用
  • onUnload:模块卸载,模块开始卸载之调用

Sandbox还为用户提供了一个ModuleLifecycleAdapter,模块继承该类后,可以仅实现自己想要的钩子函数。ModuleLifecycleAdapter源码如下所示:

 // ModuleLifecycleAdapter.java
 public class ModuleLifecycleAdapter implements ModuleLifecycle {
     @Override
     public void onLoad() throws Throwable {}
     @Override
     public void onUnload() throws Throwable {}
     @Override
     public void onActive() throws Throwable {}
     @Override
     public void onFrozen() throws Throwable {}
     @Override
     public void loadCompleted() {}
 }
复制代码

4、模块加载

模块加载的源码如下所示:首先根据模块目录以及全局配置中的启动模式创建一个模块加载器,然后调用模块加载器中的load()方法加载模块。注意下,这里调用load()方法是传递了两个回调类进去。

 for (final File moduleLibDir : moduleLibDirArray) {
     // 用户模块加载目录,加载用户模块目录下的所有模块
     // 对模块访问权限进行校验
     if (moduleLibDir.exists() && moduleLibDir.canRead()) {
         new ModuleLibLoader(moduleLibDir, cfg.getLaunchMode())
                 .load(new InnerModuleJarLoadCallback(), new InnerModuleLoadCallback());
     } else {
         logger.warn("module-lib not access, ignore flush load this lib. path={}", moduleLibDir);
     }
 }
复制代码

4.1、模块加载器

模块加载器的构造方法非常简单,仅是一些赋值语句。

 // ModuleLibLoader.java
 ModuleLibLoader(final File moduleLibDir, final Information.Mode mode) {
     this.moduleLibDir = moduleLibDir;
     this.mode = mode;
 }
复制代码

4.2、模块加载器的load()

load()方法会依次加载目录下的所有jar包,在加载前会调用mjCb.onLoad方法,处理一些回调函数。

 // ModuleLibLoader.java
 void load(final ModuleJarLoadCallback mjCb, final ModuleJarLoader.ModuleLoadCallback mCb) {
     for (final File moduleJarFile : listModuleJarFileInLib()) {
         try {
             mjCb.onLoad(moduleJarFile);
             new ModuleJarLoader(moduleJarFile, mode).load(mCb);
         } catch (Throwable cause) {
             logger.warn("loading module-jar occur error! module-jar={};", moduleJarFile, cause);
         }
     }
 }
复制代码

还记得load()时传递的两个回调类吧,mjCb就是其中一个回调类,它的实现是InnerModuleJarLoadCallback,这个类定义在模块管理器DefaultCoreModuleManager中,它的onLoad()方法非常简单,就是委托给模块管理器的providerManager来实现,providerManager内部封装了一层责任链,该责任链是在模块管理器初始化时,通过SPI的方式加载,如果忘记了大家可以回顾下前面的文章。这个责任链目前并没有做任何事情,是作者留给用户的钩子,让用户能够在jar包加载过程中实现一些用户的逻辑,例如对Jar包进行解密,签名验证等操作。

 // DefaultCoreModuleManager.java
 final private class InnerModuleJarLoadCallback implements ModuleJarLoadCallback {
     @Override
     public void onLoad(File moduleJarFile) throws Throwable {
         providerManager.loading(moduleJarFile);
     }
 }
 // DefaultProviderManager.java
 @Override
 public void loading(final File moduleJarFile) throws Throwable {
     for (final ModuleJarLoadingChain chain : moduleJarLoadingChains) {
         chain.loading(moduleJarFile);
     }
 }
 ​
复制代码

完成了ModuleJarLoadCallback 的回调后,作者又创建了一个ModuleJarLoader去执行具体加载过程。这里的命名有点乱,大家一定要理清楚。我们就直接来看看ModuleJarLoader#load的实现吧:

 // ModuleJarLoader.java
 void load(final ModuleLoadCallback mCb) throws IOException {
     boolean hasModuleLoadedSuccessFlag = false;
     ModuleJarClassLoader moduleJarClassLoader = null;
     logger.info("prepare loading module-jar={};", moduleJarFile);
     try {
         moduleJarClassLoader = new ModuleJarClassLoader(moduleJarFile);
         final ClassLoader preTCL = Thread.currentThread().getContextClassLoader();
         Thread.currentThread().setContextClassLoader(moduleJarClassLoader);
         try {
             hasModuleLoadedSuccessFlag = loadingModules(moduleJarClassLoader, mCb);
         } finally {
             Thread.currentThread().setContextClassLoader(preTCL);
         }
     } finally {
         if (!hasModuleLoadedSuccessFlag && null != moduleJarClassLoader) {
             logger.warn("loading module-jar completed, but NONE module loaded, will be close ModuleJarClassLoader. module-jar={};", moduleJarFile);
             moduleJarClassLoader.closeIfPossible();
         }
     }
 }
复制代码

ModuleJarLoader#load逻辑并不重,它主要新创建了一个ModuleJarClassLoader类加加载器,然后委托给ModuleJarLoader#loadingModules方法加载单个模块。这也意味着每个模块都由一个新的类加载器去加载,这样也实现了模块间的类隔离。

讲到这里,我们便可以更新下之前的Classloader模型了,更新后的模型如下所示:

ModuleJarLoader#loadingModules的实现如下:

 // ModuleJarLoader.java
 private boolean loadingModules(final ModuleJarClassLoader moduleClassLoader, final ModuleLoadCallback mCb) {
     final Set<String> loadedModuleUniqueIds = new LinkedHashSet<String>();
     final ServiceLoader<Module> moduleServiceLoader = ServiceLoader.load(Module.class, moduleClassLoader);
     final Iterator<Module> moduleIt = moduleServiceLoader.iterator();
     while (moduleIt.hasNext()) {
         final Module module;
         try {
             module = moduleIt.next();
         } catch (Throwable cause) {
             logger.warn("loading module instance failed: instance occur error, will be ignored. module-jar={}", moduleJarFile, cause);
             continue;
         }
         final Class<?> classOfModule = module.getClass();
         // ... 一些模块校验,比如判断模块是否实现了@Information标记,判断模块ID是否合法,判断模块要求的启动模式和容器的启动模式是否匹配
         try {
             if (null != mCb) {
                 mCb.onLoad(uniqueId, classOfModule, module, moduleJarFile, moduleClassLoader);
             }
         } catch (Throwable cause) {
             // ... 日志处理
         }
         loadedModuleUniqueIds.add(uniqueId);
     }
     // ... 日志处理
     return !loadedModuleUniqueIds.isEmpty();
 }
复制代码

ModuleJarLoader#loadingModules主要通过JDK SPI加载Jar包中的所有模块,加载后会进行一系列的模块校验操作,如果校验错误,则continue跳过。校验成功则会调用mCb的回调。

还记得上面的两个回调类吧,这里mCb是第二个回调类,它的实现为InnerModuleLoadCallback。它判断模块注册表中是否已经加载,如果加载则直接返回,然后它将真正的模块加载逻辑委托给DefaultCoreModuleManager#load做了。

DefaultCoreModuleManager#load方法首先也是校验注册表,然后通过CoreModule的构造方法创建一个module,为module中被@Resource注解标注的属性注入对象(这里的注入代码有点多,但是逻辑比较单一,就是通过反射属性写入对象,后面介绍到模块的属性时再说),随后通知一些生命周期的函数。

这里的markActiveOnLoadIfNecessary方法不仅会通知模块的active生命周期钩子,还会激活所有的监听器。没有,这里所谓的激活监听器,其实就是向EventListenerHandler#mappingOfEventProcessor属性注册模块信息而已,与上面所述的冻结监听器是相反的过程。

 // DefaultCoreModuleManager.java
 final private class InnerModuleLoadCallback implements ModuleJarLoader.ModuleLoadCallback {
     @Override
     public void onLoad(final String uniqueId, final Class moduleClass, final Module module, final File moduleJarFile, final ModuleJarClassLoader moduleClassLoader) throws Throwable {
         // 如果之前已经加载过了相同ID的模块,则放弃当前模块的加载
         if (loadedModuleBOMap.containsKey(uniqueId)) {
             // ... 日志
             return;
         }
         // 需要经过ModuleLoadingChain的过滤,这个步骤上面说过了,暂时未做处理,留给用户按需实现
         providerManager.loading(uniqueId, moduleClass, module, moduleJarFile, moduleClassLoader);
         // ... 日志
         // 这里进行真正的模块加载
         load(uniqueId, module, moduleJarFile, moduleClassLoader);
     }
 }
 // DefaultCoreModuleManager.java
 private synchronized void load(final String uniqueId, final Module module, final File moduleJarFile, final ModuleJarClassLoader moduleClassLoader) throws ModuleException {
     if (loadedModuleBOMap.containsKey(uniqueId)) {
         logger.debug("module already loaded. module={};", uniqueId);
         return;
     }
     logger.info("loading module, module={};class={};module-jar={};",
             uniqueId,
             module.getClass().getName(),
             moduleJarFile
     );
     // 初始化模块信息
     final CoreModule coreModule = new CoreModule(uniqueId, moduleJarFile, moduleClassLoader, module);
     // 注入@Resource资源
     injectResourceOnLoadIfNecessary(coreModule);
     // 通知模块load生命周期
     callAndFireModuleLifeCycle(coreModule, MODULE_LOAD);
     // 设置为已经加载
     coreModule.markLoaded(true);
     // 如果模块标记了加载时自动激活,则需要在加载完成之后激活模块
     markActiveOnLoadIfNecessary(coreModule);
     // 注册到模块列表中
     loadedModuleBOMap.put(uniqueId, coreModule);
     // 通知生命周期,模块加载完成
     callAndFireModuleLifeCycle(coreModule, MODULE_LOAD_COMPLETED);
 }
 ​
复制代码

5、总结

本篇主要介绍了Sandbox是如何加载模块的整个流程,包括模块的重置、卸载、加载、生命周期等内容。模块是Sandbox中非常重要的概念,同时也是Sandbox扩展性的体现,我们可以基于Sandbox整体框架,定义自己的模块,实现自己的处理逻辑。后面的实战也会讲到如何扩展自己的模块。

模块加载完成后,其实Sandbox的启动也就结束了,启动流程图最终也就像下面这样:

接下来就是业务代码不断触发监听的事件,Spy将事件捕捉到,然后通过SpyHandler发送给Sandbox对应的模块进行处理的大循环流程

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值