javascript
spring解密_在运行时更新代码(已Spring解密)
spring解密
當從編譯到部署再到測試的開發(fā)周期花費太長時間時,人們希望能夠及時替換正在運行的代碼,而無需重新啟動應用程序服務器并等待部署完成。 在這種情況下,像JRebel這樣的商業(yè)解決方案或像Grails這樣的開源框架就可以提供幫助。
JVM不支持開箱即用地替換代碼,例如您可以使用Class.forName()動態(tài)加載類。 基本上,您有以下選擇:
- HotSwap:Java 1.4引入的技術,可讓您在調(diào)試器會話中重新定義類。 這種方法非常有限,因為它僅允許您更改方法的主體,而不能添加新的方法或類。
- OSGi:此技術使您可以定義包。 在運行時,可用此軟件包的較新版本替換該軟件包。
- 一次性的類加載器:通過在模塊的所有類上包裝單獨的類加載器,可以在模塊的新版本可用時丟棄該類加載器并進行替換。
- 使用Java代理檢測類:Java代理可以在定義類之前進行檢測。 這樣,它可以將代碼注入到已加載的類中,該類將此類與類文件的一個版本連接起來。 一旦有新版本可用,將執(zhí)行新代碼。
Grails的技術稱為“ 彈簧加載”,并使用“ Java代理”方法來處理從文件系統(tǒng)而不是從jar文件加載的類。 但是,這在后臺如何工作?
為了了解彈簧負載,我們建立了一個小樣本項目,使我們可以更詳細地研究該技術。 該項目僅包含兩個類: Main類調(diào)用ToBeChanged類的print()方法,并Hibernate一會兒:
public static void main(String[] args) throws InterruptedException {while (true) {ToBeChanged toBeChanged = new ToBeChanged();toBeChanged.print();Thread.sleep(500);} }print()方法僅打印出一個版本,以便我們可以看到它已更改。 另外,我們還打印出堆棧跟蹤,以查看其隨時間的變化:
public void print() {System.out.println("V1");StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();for (StackTraceElement element : stackTrace) {System.out.println("\t" + element.getClassName() + "." + element.getMethodName() + ":" + element.getLineNumber());} }啟動應用程序時,我們必須使用選項javaagent提供包含Java Agent的jar文件。 當彈簧加載以驗證者不喜歡的方式修改字節(jié)碼時,我們必須通過將選項noverify傳遞給JVM來禁用字節(jié)碼的驗證。 最后,我們用cp傳遞包含類文件的文件夾,并告訴JVM包含main()方法的類:
java -javaagent:springloaded-1.2.4.BUILD-SNAPSHOT.jar -noverify -cp target/classes com.martinsdeveloperworld.springloaded.Main將ToBeChanged類的版本從V1更新到V2并使用mvn package重建項目后,我們將看到以下輸出:
... V1java.lang.Thread.getStackTrace:-1com.martinsdeveloperworld.springloaded.ToBeChanged.print:7com.martinsdeveloperworld.springloaded.Main.main:8 V2java.lang.Thread.getStackTrace:-1com.martinsdeveloperworld.springloaded.ToBeChanged$$EPBF0gVl.print:7com.martinsdeveloperworld.springloaded.ToBeChanged$$DPBF0gVl.print:-1com.martinsdeveloperworld.springloaded.ToBeChanged.print:-1com.martinsdeveloperworld.springloaded.Main.main:8 ...版本V1的stacktrace看起來像我們期望的那樣。 從Main.main()方法ToBeChanged.print()被調(diào)用。 對于版本V2這是不同的。 在這里,方法ToBeChanged.print現(xiàn)在調(diào)用方法ToBeChanged$$DPBF0gVl.print() 。 還請注意,調(diào)用ToBeChanged.print()的行號已從8更改為-1,表示該行未知。
新的行號-1強烈表明Java代理已經(jīng)以一種允許它調(diào)用新方法而不是執(zhí)行舊代碼的方式來檢測ToBeChanged.print()方法。 為了證明這一假設,我在spring-loaded的代碼中添加了一些日志記錄語句,并添加了一個功能,可將每個插入的文件轉(zhuǎn)儲到本地硬盤驅(qū)動器中。 通過這種方式,我們可以檢查ToBeChanged.print()后方法ToBeChanged.print()外觀:
0 getstatic #16 <com/martinsdeveloperworld/springloaded/ToBeChanged.r$type>3 ldc #72 <0>5 invokevirtual #85 <org/springsource/loaded/ReloadableType.changed>8 dup9 ifeq 42 (+33)12 iconst_113 if_icmpeq 26 (+13)16 new #87 <java/lang/NoSuchMethodError>19 dup20 ldc #89 <com.martinsdeveloperworld.springloaded.ToBeChanged.print()V>22 invokespecial #92 <java/lang/NoSuchMethodError.<init>>25 athrow26 getstatic #16 <com/martinsdeveloperworld/springloaded/ToBeChanged.r$type>29 invokevirtual #56 <org/springsource/loaded/ReloadableType.fetchLatest>32 checkcast #58 <com/martinsdeveloperworld/springloaded/ToBeChanged__I>35 aload_036 invokeinterface #94 <com/martinsdeveloperworld/springloaded/ToBeChanged__I.print> count 241 return42 pop43 getstatic #100 <java/lang/System.out>46 ldc #102 <V1>48 invokevirtual #107 <java/io/PrintStream.println>51 invokestatic #113 <java/lang/Thread.currentThread>54 invokevirtual #117 <java/lang/Thread.getStackTrace>57 astore_1 ... 152 returngetstatic操作碼檢索新字段r$type的值并將其壓入堆棧(操作碼ldc )。 然后,調(diào)用方法ReloadableType.changed()來調(diào)用之前已推入堆棧的對象引用。 顧名思義,方法ReloadableType.changed()檢查此類型的新版本是否存在。 如果方法未更改,則返回0;如果方法已更改,則返回1。 如果返回值為零,即方法未更改,則以下操作碼ifeq跳至第42行。 從第42行開始,我們看到了原始實現(xiàn),在這里我將其縮短了一點。
如果值為1,則if_icmpeq指令跳至第26行,在此再次讀取靜態(tài)字段r$type 。 此引用用于在其上調(diào)用方法ReloadableType.fetchLatest() 。 下面的checkcast指令驗證返回的引用的類型為ToBeChanged__I 。 在這里,我們第一次偶然發(fā)現(xiàn)了這種彈簧接口為每種類型生成的人工接口。 它反映了原始類在檢測時所具有的方法。 兩行之后,此接口用于在ReloadableType.fetchLatest()返回的引用上調(diào)用方法print() ReloadableType.fetchLatest() 。
該引用不是對該類的新版本的引用,而是對所謂的調(diào)度程序的引用。 調(diào)度程序?qū)崿F(xiàn)ToBeChanged__I接口,并通過以下指令實現(xiàn)方法print() :
0 aload_1 1 invokestatic #21 <com/martinsdeveloperworld/springloaded/ToBeChanged$$EPBF0gVl.print> 4 return動態(tài)生成的類ToBeChanged$$EPBF0gVl是所謂的執(zhí)行程序,體現(xiàn)了該類型的新版本。 對于每個新版本,都會創(chuàng)建一個新的調(diào)度程序和執(zhí)行程序,只有接口保持不變。 一旦有新版本可用,則在新調(diào)度程序上調(diào)用接口方法,并且在最簡單的情況下,該接口方法將轉(zhuǎn)發(fā)至執(zhí)行程序中包含的代碼的新版本。 不能直接在執(zhí)行器上調(diào)用接口方法的原因是,彈簧加載還可以處理在新版本的類中添加方法的情況。 由于此方法在舊版本中不存在,因此將通用方法__execute()添加到接口和調(diào)度程序。 然后,此動態(tài)方法可以將調(diào)用調(diào)度到新方法,如從生成的調(diào)度程序中獲取的以下指令集中所示:
0 aload_31 ldc #25 <newMethod()V>3 invokevirtual #31 <java/lang/String.equals>6 ifeq 18 (+12)9 aload_2 10 checkcast #33 <com/martinsdeveloperworld/springloaded/ToBeChanged> 13 invokestatic #36 <com/martinsdeveloperworld/springloaded/ToBeChanged$$EPBFaboY.newMethod> 16 aconst_null 17 areturn 18 aload_3 ... 68 areturn在這種情況下,我向類ToBeChanged添加了一個名為newMethod()的新方法。 __execute()方法的開頭比較調(diào)用的描述符是否與新方法匹配。 在這種情況下,它將調(diào)用轉(zhuǎn)發(fā)給新的執(zhí)行者。 為了使此工作正常進行,必須將新方法的所有調(diào)用都重寫為__execute()方法。 這也可以通過對原始類的檢測來完成,并且也可以進行反射。
結(jié)論
Spring加載演示了可以在運行時用較新版本“替換”一個類。 為了實現(xiàn)這一點,利用了一系列Java技術,例如Java Agent和字節(jié)碼檢測。 通過仔細研究實現(xiàn),可以大致了解有關JVM和Java的許多知識。
翻譯自: https://www.javacodegeeks.com/2015/05/updating-code-at-runtime-spring-loaded-demystified.html
spring解密
總結(jié)
以上是生活随笔為你收集整理的spring解密_在运行时更新代码(已Spring解密)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: web ua检测_UA Web挑战会议:
- 下一篇: 天心erp的快捷键(天心erp管理系统费