前言

在上一篇Jvm-Sandbox源码分析--启动简析 简单介绍了一下jvm-sandbox启动流程,在这篇文章中我们来分析一下系统模块和用户的自定义模块在启动时,是怎么加载的。

在上一篇文章启动简析的最后,代码进入默认的模块管理类 DefaultCoreModuleManager.reset()方法

//DefaultCoreModuleManager// 初始化加载所有的模块public synchronized CoreModuleManager reset() throws ModuleException {        // 1. 强制卸载所有模块        unloadAll();        // 2. 加载所有模块        for (final File moduleLibDir : moduleLibDirArray) {            // 用户模块加载目录,加载用户模块目录下的所有模块            // 对模块访问权限进行校验            if (moduleLibDir.exists() && moduleLibDir.canRead()) {                //初始化模块目录加载器,传入模块lib目录和加载模式attach 默认加载模式就是attach                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;    }

可以看到这部分代码主要做了两件事:强制卸载所有模块和加载所有模块,但是启动时候其实是没有加载模块的,所有这部分逻辑其实是会跳过,我们后续到通过命令卸载模块到时候再分析。

加载模块

这里加载的模块有两种类型:

  • 1.路径/Users/zhengmaoshao/sandbox/bin/../module 下的系统模块sandbox-mgr-module.jar
  • 2.路径/Users/zhengmaoshao/.sandbox-module 下的用户自定义模块
/**     * 加载Module     *     * @param mjCb 模块文件加载回调     * @param mCb  模块加载回掉     */    void load(final ModuleJarLoadCallback mjCb,              final ModuleJarLoader.ModuleLoadCallback mCb) {        // 开始逐条加载        for (final File moduleJarFile : listModuleJarFileInLib()) {            try {                mjCb. (moduleJarFile);                new ModuleJarLoader(moduleJarFile, mode).load(mCb);            } catch (Throwable cause) {                logger.warn("loading module-jar occur error! module-jar={};", moduleJarFile, cause);            }        }    }

1.模块文件加载回调

/**     * 用户模块文件加载回调     */    final private class InnerModuleJarLoadCallback implements ModuleJarLoadCallback {        @Override        public void  (File moduleJarFile) throws Throwable {            providerManager.loading(moduleJarFile);        }    }

最终会通过模块Jar文件加载链ModuleJarLoadingChain去加载文件
不过目前来看实现类都是空的,没有起到什么作用。

image

2.模块加载回调

//ModuleJarLoader.loadvoid 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();            }        }    }

关键步骤:

  • 1.创建模块类加载器
  • 2.将当前线程的类加载器从沙箱类加载器设置成模块类加载器
  • 3.加载模块
  • 4.将当前线程的类加载器从模块类加载器设置成沙箱类加载器

3.加载模块过程

ModuleJarLoader的loadingModules方法中的关键步骤:

  • 1.通过ServiceLoader加载工具,从sandbox-mgr-module.jar加载沙箱环境模块接口Module的实现类。
    实际就是加载ControlModule,InfoModule,ModuleMgrModule 这三个用于内部操作的类。
    ServiceLoader<Module> moduleServiceLoader = ServiceLoader.load(Module.class, moduleClassLoader);
  • 2.调用模块加载回调 方法,进入到真正进行模块加载的DefaultCoreModuleManager load方法。
      // 这里进行真正的模块加载            load(uniqueId, module, moduleJarFile, moduleClassLoader);

DefaultCoreModuleManager load方法关键步骤:

  • 1.实例化模块业务对象,注入@resource资源,包括我们自定义Module中的@Resource资源都是在这个时候注入的,在ControlModule中即是沙箱配置信息ConfigInfo
    // 初始化模块信息        final CoreModule coreModule = new CoreModule(uniqueId, moduleJarFile, moduleClassLoader, module);        // 注入@Resource资源        injectResource IfNecessary(coreModule);
  • 2.设置生命周期
  callAndFireModuleLifeCycle(coreModule, MODULE_LOAD);
  • 3.因为注解@Information中isActive 表示是否在加载时候就激活模块,它的默认值是true, 所以会进入激活模块逻辑,这里需要注意,如果不希望启动时候就激活模块,则设置为false。模块只有在激活之后才能增强目标类。
//如果模块标记了加载时自动激活,则需要在加载完成之后激活模块 markActive IfNecessary(coreModule);

在启动过程中系统模块和自定义模块到加载过程就分析完了,ControlModule,InfoModule,ModuleMgrModule 这三个系统模块提供了一些通过shell命令可以操作的方法。

而在我们通过sh sandbox.sh -p pid语句执行启动脚本sandbox.sh 的时候,最后会执行一个默认命令

# default    sandbox_curl "sandbox-info/version"    exit

这个命令就在刚刚加载的InfoModule类中

@Command("version")public void version(final PrintWriter writer)

所以在我们完成加载之后,便会看到如下信息。

                    NAMESPACE : default                      VERSION : 1.2.1                         MODE : ATTACH                  SERVER_ADDR : 0.0.0.0                  SERVER_PORT : 60483               UNSAFE_SUPPORT : ENABLE                 SANDBOX_HOME : /Users/zhengmaoshao/sandbox/bin/..            SYSTEM_MODULE_LIB : /Users/zhengmaoshao/sandbox/bin/../module              USER_MODULE_LIB : /Users/zhengmaoshao/sandbox/sandbox-module;~/.sandbox-module;          SYSTEM_PROVIDER_LIB : /Users/zhengmaoshao/sandbox/bin/../provider           EVENT_POOL_SUPPORT : DISABLE
收藏 打印