天天看點

Jvm-Sandbox源碼分析--啟動時加載子產品

前言

在上一篇

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.onLoad(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 onLoad(File moduleJarFile) throws Throwable {
            providerManager.loading(moduleJarFile);
        }
    }           

最終會通過子產品Jar檔案加載鍊ModuleJarLoadingChain去加載檔案

不過目前來看實作類都是空的,沒有起到什麼作用。

Jvm-Sandbox源碼分析--啟動時加載子產品

2.子產品加載回調

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

    }           

關鍵步驟:

  • 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.調用子產品加載回調onLoad方法,進入到真正進行子產品加載的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資源
        injectResourceOnLoadIfNecessary(coreModule);           
  • 2.設定生命周期
callAndFireModuleLifeCycle(coreModule, MODULE_LOAD);           
  • 3.因為注解@Information中isActiveOnLoad表示是否在加載時候就激活子產品,它的預設值是true, 是以會進入激活子產品邏輯,這裡需要注意,如果不希望啟動時候就激活子產品,則設定為false。子產品隻有在激活之後才能增強目标類。
//如果子產品标記了加載時自動激活,則需要在加載完成之後激活子產品
 markActiveOnLoadIfNecessary(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