JVM-SANDBOX(沙箱)實現了一種在不重啟、不侵入目標JVM應用的AOP解決方案。就複製這一段用來開頭吧,具體介紹可以看官方github,傳送門 https://github.com/alibaba/jvm-sandbox
sandbox源碼各模塊的結構如下:
sandbox安裝後的目錄結構如下:
按照官方的教程,啟動命令如下:
./sandbox.sh -p 2343
查看對應的腳本,函數定義如下:
翻譯過來就是執行如下命令
java -Xms128M -Xmx128M -Xnoclassgc -ea -jar /xxx/sandbox-core.jar 2342 /xxx/sandbox-agent.jar home=/xxx;token=xxx;server.ip=xxx;service.port=xxx;namespace=xxx
按順序將參數定義為
查看sandbox-core模塊的pom.xml
<archive> <manifest> <mainClass>com.alibaba.jvm.sandbox.core.CoreLauncher</mainClass> </manifest></archive>
找到啟動類 com.alibaba.jvm.sandbox.core.CoreLauncher
該類存在一個main方法,接收3個參數,並實例化一個CoreLauncher實例,它在構造方法中完成agent的attach動作.
public CoreLauncher(final String targetJvmPid, final String agentJarPath, final String token) throws Exception { attachAgent(targetJvmPid, agentJarPath, token);}
主要是以agent-main的方式執行。
去到sandbox-agent模塊的pom.xml,有如下配置
<archive> <manifestEntries> <Premain-Class>com.alibaba.jvm.sandbox.agent.AgentLauncher</Premain-Class> <Agent-Class>com.alibaba.jvm.sandbox.agent.AgentLauncher</Agent-Class> <Can-Redefine-Classes>true</Can-Redefine-Classes> <Can-Retransform-Classes>true</Can-Retransform-Classes> </manifestEntries></archive>
分別指定了premain和agent的啟動類com.alibaba.jvm.sandbox.agent.AgentLauncher
查看入口agentmain方法
處理agent傳入的cfg參數欄位,解析為k-v對,然後執行install方法,並將結果寫入到了${HOME}/.sandbox.token裡,該文件記錄的內容為
default;21988529;0.0.0.0;32165//命名空間;token;ip;port
sandbox-agent模塊裡AgentLauncher類的install方法將完成主要的加載過程,包括:
JettyCoreServer的bind方法主要完成Http服務的啟動以及各模塊的加載,主要動作為:
注,當前的ClassLoader為每個namespace對應的SandboxClassLoader,後續該包裡加載的類的類加載器都是這個,影響的類位於sandbox-core.jar中,對應源碼裡的sandbox-common-api、sandbox-provider-api、sandbox-api、sandbox-core模塊。
JvmSandbox表示一個沙箱對象,關係如下,依賴CoreConfigure和CoreModuleManager,分別表示系統配置信息和模塊管理。CoreModuleManager的實現類為DefaultCoreModuleManager,為主要功能。
同時在初始化的時候還會調用SpyUtils,為每個namespace分配公共的EventListenerHandler(SpyHandler實現)類
private void init() { SpyUtils.init(cfg.getNamespace());//初始化SpyUtils,分配公共的EventListenerHandler(SpyHandler實現)類}
初始化Http服務,啟動一個Jetty Server,並設置為daemon
初始化Jetty處理器,實現一個請求分發器,可以通過http的方式來觸發@Command註解的方法。
註冊的處理器有兩種:
該Servlet要求請求URL按照如下格式:
/sandbox/${namespace}/module/http/${uniqueId}/${command}?${k1}=${v1}&${k2}=${v2}
對於一個http請求,具體流程為:
3.1. HttpServletRequest
3.2. HttpServletResponse
3.3. Map<String,String[]>:使用http請求的參數
3.4. String:使用url中?後面的內容
3.5. PrintWriter:HttpServletResponse.getWriter()
3.6. OutputStream:HttpServletResponse.getOutputStream()
4. 反射執行Method
相當於自己實現了一個http請求分發器
啟動Jetty http服務
主要調用CoreModuleManager的reset方法,完成模塊的重置。先卸載各個模塊再進行加載。
在這之前先介紹模塊的生命周期
(來自官網github wiki,官網都有,懶的搬了)
上面少了一個loadCompleted事件,會在模塊加載完成,模塊完成加載後調用。作者的意思是方法比較常用,所以單獨出來成為一個接口。
模塊的卸載會先將模塊進行凍結,然後再將模塊卸載,期間會觸發onFrozen和onUnload事件。有點需要注意的是凍結和卸載的不同,凍結只是把sandbox的事件通知機制給屏蔽掉,模塊的插樁代碼還在。凍結後可以解凍,插樁代碼可以復用。而卸載則會移除模塊對應的ClassFileTransformer,並重新加載原始的class,重新處理class,達到去除模塊插樁的目的。
這裡先只關注模塊的加載流程,模塊的加載過程如下:
該方法會使用ModuleJarClassLoader通過SPI機制加載jar包裡的Module類。由於通過SPI機制,因而需要符合SPI規範:
sandbox裡的模塊都是通過sandbox-module-starter插件完成,只要實現Module接口然後再類上加上org.kohsuke.MetaInfServices註解,為插件點讚。
加載完Module類後會讀取類似的com.alibaba.jvm.sandbox.api.Information註解,獲得模塊id,如果沒有該註解,則不會加載該模塊。
加載完後會通知InnerModuleLoadCallback進行回調,完成後續動作。然後將該模塊id加入已加載模塊列表中。
InnerModuleLoadCallback經過過濾鏈處理後,會委託給DefaultCoreModuleManager的load方法完成實際的處理動作。
該方法主要完成如下動作:
2.1. LoadedClassDataSource,已加載類數據源,注入已有的DefaultCoreLoadedClass
2.2. DataSourceModuleEventWatcher,事件觀察者,注入新建的DefaultModuleEventWatcher.該對象被標記為一個可釋放資源,由ReleaseResource引用,會在模塊卸載的時候進行回收。具體到這裡是觸發ModuleEventWatcher的delete動作,在釋放模塊資源時,清除模塊上的事件,該動作會觸發class重新加載,去除該模塊的插樁代碼。
2.3. ModuleController,模塊控制接口,注入新建的DefaultModuleController
2.4. ModuleManager,模塊管理器,注入新建的DefaultModuleManager
2.5. ConfigInfo,沙箱配置信息,注入新建的DefaultConfigInfo
回到3.5.2的第2步,這裡新建了很多對象注入到自定義Module實現類中,這些類都位於sandbox-core.jar包中。前面說過,這些類由SandboxClassLoader來加載,而ModuleJarClassLoader只複製加載各自模塊jar包的類,因而ModuleJarClassLoader需要繼承SandboxClassLoader,看下它的定義
RoutingURLClassLoader預先給指定的包路徑正則表達式指定ClassLoader,命中後將加載動作委託給指定的ClassLoader,沒命中才委託給父類加載器。
而ModuleJarClassLoader繼承自RoutingURLClassLoader,構造方法如下
指定了sandbox-core.jar裡類都由ModuleJarClassLoader的父類加載器即SandboxClassLoader來處理。
綜上,得到jvm-sandbox的類加載關係如下:
(圖片來自官網github)
除了sandbox-spy.jar包外,其他都包跟業務包進行了分離,各個模塊的包也互不影響。而sandbox-spy.jar裡的類由於s在完成插樁後會出現在業務代理裡,所以需要全局加載,故由Bootstrap加載器加載。
Ok,用一張圖總結jvm-sandbox的的啟動過程: