Wicket1.3中Class熱加載--揭秘篇
在文章《Wicket1.3中Class熱加載--使用篇》中,展示了如何使用Wicket1.3提供的ReloadingWicketFilter來動態(tài)加載修改后的類(包括修改了簽名的類),從而實現(xiàn)高效開發(fā)。但在該文章中,只是給出了如何使用該功能的說明,而本篇文章將明確解析Wicket1.3類熱加載魔法的奧秘所在。
在上一篇文章中,是通過修改Wicket項目中web.xml文件,將其中的
org.apache.wicket.protocol.http.WicketFilter
全部替換成
org.apache.wicket.protocol.http.ReloadingWicketFilter
從而開啟了Wicket類熱加載的功能。那么為了探究其魔法奧秘,入手點自然就選擇ReloadingWicketFilter這個類了。
先來看一下ReloadingWicketFilter的代碼,驚人的少:
public class ReloadingWicketFilter extends WicketFilter { private ReloadingClassLoader reloadingClassLoader; /** * Instantiate the reloading class loader */ public ReloadingWicketFilter() { // Create a reloading classloader reloadingClassLoader = new ReloadingClassLoader(getClass().getClassLoader()); } /** * @see org.apache.wicket.protocol.http.WicketFilter#getClassLoader() */ protected ClassLoader getClassLoader() { return reloadingClassLoader; } /** * @see org.apache.wicket.protocol.http.WicketFilter#init(javax.servlet.FilterConfig) */ public void init(final FilterConfig filterConfig) throws ServletException { reloadingClassLoader.setListener(new IChangeListener() { public void onChange() { // Remove the ModificationWatcher from the current reloading class loader reloadingClassLoader.destroy(); /* * Create a new classloader, as there is no way to clear a ClassLoader's cache. This * supposes that we don't share objects across application instances, this is almost * true, except for Wicket's Session object. */ reloadingClassLoader = new ReloadingClassLoader(getClass().getClassLoader()); try { init(filterConfig); } catch (ServletException e) { Throw new RuntimeException(e); } } }); super.init(filterConfig); } } |
其中最引人注目的就是那個ReloadingClassLoader,再打開WicketFilter類中,很容易找到以下代碼,它表示使用自定義的ClassLoader來加載類。
final ClassLoader newClassLoader = getClassLoader(); Thread.currentThread().setContextClassLoader(newClassLoader); |
而ReloadingWicketFilter則是重載了getClassLoader方法,以返回了自定義的ReloadingClassLoader。也就是說Wicket的魔法其實是使用了一個自定義的ReloadingClassLoader來實現(xiàn)類的熱加載(包括對簽名被修改的類)。為了讓大家更清楚理解Wicket這一方法以,在分析ReloadingClassLoader之前,先來簡單的過一下JVM的類加載機制。
在JDK1.2以后,JVM在加載類時默認采用的是雙親委托機制(早期的類加載機制存在安全漏洞)。通俗的講,就是某個特定的類加載器在接到加載類的請求時,首先將加載任務委托給父類加載器,依次遞歸,所有 ClassLoaders 的根都是系統(tǒng) ClassLoader,它會以缺省方式裝入類,即從本地文件系統(tǒng)加載(可能是Jar包,也可能是Class文件),如果父類加載器可以完成類加載任務,就成功返回加載后的類;但如果父類加載器無法完成此加載任務時,那么這個特定的類加載才自己去加載指定名稱的類。這樣的雙親委托機制可以保證象java.io.*這種基礎類庫的內容一定是被系統(tǒng)ClassLoader加載,從而保證類加載的安全性。
下面是雙親委派機制的示意圖:
實例分析Web應用下的類加載順序:
為了更好的方便大家理解類的加載機制,并說明Wicket如何使用自定義的ClassLoader來加載更改后的類,下面將有一個簡單的實例來說明。
首先將前一篇文章中的HelloWorld代碼修改為:
public class HelloWorld extends WicketExamplePage { /** * Constructor */ public HelloWorld() { ClassLoader classLoader = this.getClass().getClassLoader(); while (null != classLoader) { System.err.println("loader " + classLoader.hashCode()+" "+classLoader.getClass()); classLoader = classLoader.getParent(); } add(new Label("message", "New Hello World!")); } } |
修改以后的代碼,可以在對象被創(chuàng)建時,輸出HelloWorld類的ClassLoader及其父ClassLoader,接下來恢復web.xml文件中的filter為WicketFilter,不使用RelodingWicketFilter,從而觀察原先的Class加載順序,得到的結果為:
loader 2011334 class org.apache.catalina.loader.WebappClassLoader loader 19608393 class org.apache.catalina.loader.StandardClassLoader loader 13756574 class org.apache.catalina.loader.StandardClassLoader loader 26726999 class sun.misc.Launcher$AppClassLoader loader 7494106 class sun.misc.Launcher$ExtClassLoader |
接下來仍然按照上一篇文章中的操作修改web.xml,使用RelodingWicketFilter,開啟Wicket類的熱加載功能。再看一下輸出結果:
loader 8310913 class org.apache.wicket.application.ReloadingClassLoader loader 2011334 class org.apache.catalina.loader.WebappClassLoader loader 19608393 class org.apache.catalina.loader.StandardClassLoader loader 13756574 class org.apache.catalina.loader.StandardClassLoader loader 26726999 class sun.misc.Launcher$AppClassLoader loader 7494106 class sun.misc.Launcher$ExtClassLoader |
可見通過代碼
final ClassLoader newClassLoader = getClassLoader(); Thread.currentThread().setContextClassLoader(newClassLoader); |
ReloadingWicketFilter使用ReloadingClassLoader作為當前類的ClassLoader,也就是說它接管了所有WEB-INF/classes目錄下面類文件的加載。這樣它就可以根據(jù)實際情況來加載一個類。但有經驗的程序員都有知道,一般來說Class一旦被加載,就表示在整個JVM生命周期的過程中,不會自動釋放,而是放置在內存中。那么Wicket又是怎么釋放已經加載的類,同時加載修改后的類呢?看一段ReloadingWicketFilter中init方法的代碼:
/** * @see org.apache.wicket.protocol.http.WicketFilter#init(javax.servlet.FilterConfig) */ public void init(final FilterConfig filterConfig) throws ServletException { reloadingClassLoader.setListener(new IChangeListener() { public void onChange() { // Remove the ModificationWatcher from the current reloading class loader reloadingClassLoader.destroy(); /* * Create a new classloader, as there is no way to clear a ClassLoader's cache. This * supposes that we don't share objects across application instances, this is almost * true, except for Wicket's Session object. */ reloadingClassLoader = new ReloadingClassLoader(getClass().getClassLoader()); try { init(filterConfig); } catch (ServletException e) { Throw new RuntimeException(e); } } }); super.init(filterConfig); } |
這段代碼表示,一旦發(fā)現(xiàn)類文件的改變,將會銷毀當前的reloadingClassLoader ,同時新建一個ReloadingClassLoader的實例,因為ClassLoader被銷毀了,所以由該ClassLoader加載的類都將被銷毀,然后再由新的ReloadingClassLoader進行加載,因此即使是修改了簽名的類也是可以被正確加載成功的。當然這種做法,可能會引起Session中的數(shù)據(jù)不能正確識別和轉換。但相對于開發(fā)環(huán)境下,開發(fā)人員通過另起一個新的Session就可以開始正常的工作了,還是可以有效的提高開發(fā)效率。
Wicket類加載的一個潛在問題:
如果僅僅使用Wicket開發(fā)程序,那么Wicket1.3引入的熱加載機制,對開發(fā)人員來說,會是一件非常幸福的事情,但如果同時使用了jsp,也就是說同時在一個Web應用程序(不是Web應用服務器)中同時使用Wicket和JSP,而且在Wicket和JSP代碼分別向Session中寫入相同的對象,如用戶信息之類的數(shù)據(jù)對象,那么就會出現(xiàn)一些不必要的問題,最覺的莫過于ClassCastException。雖然共用Wicket+JSP的情況比較少,出問題的機率也比較少,但還是要額外提出來作為一個警示。
下面是一個簡單的類Person代碼,用來展示如何出現(xiàn)ClassCastException:
public class Person { /** * Default constructor */ public Person() { super(); ClassLoader classLoader = this.getClass().getClassLoader(); while (null != classLoader) { System.err.println("loader " + classLoader.hashCode()+" "+classLoader.getClass()); classLoader = classLoader.getParent(); } } } |
在構造函數(shù)中的那段代碼,可以輸出它的ClassLoader順序,接下來象先前一樣訪問那個HelloWorld應用,得到如下的ClassLoader順序:
loader 8310913 class org.apache.wicket.application.ReloadingClassLoader loader 3862294 class org.apache.catalina.loader.WebappClassLoader loader 19608393 class org.apache.catalina.loader.StandardClassLoader loader 13756574 class org.apache.catalina.loader.StandardClassLoader loader 26726999 class sun.misc.Launcher$AppClassLoader loader 7494106 class sun.misc.Launcher$ExtClassLoader |
然后再寫一個run.jsp,代碼如下:
<%@page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <%@page import="org.apache.wicket.examples.Person"%> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>Class Loader Demo</title> </head> <body> <% new Person(); %> </body> </html> |
在run.jsp中,初始化一個Person對象,同樣觀察它的輸出結果,得到另外一個不同的Class加載順序:
loader 3862294 class org.apache.catalina.loader.WebappClassLoader loader 19608393 class org.apache.catalina.loader.StandardClassLoader loader 13756574 class org.apache.catalina.loader.StandardClassLoader loader 26726999 class sun.misc.Launcher$AppClassLoader loader 7494106 class sun.misc.Launcher$ExtClassLoader |
可見在JSP中的Person與在HelloWorld中的Person是由不同的ClassLoader加載的(JSP編譯成Servlet執(zhí)行,在Tomcat中,org.apache.jasper.servlet.JasperLoader負責JSP編譯后的類加載),根據(jù)JVM規(guī)范,這兩個Class是不等價的。因此進行轉換的時候,會引起ClassCastException,這是特別需要注意的一點。
點擊這里下載Word格式
posted on 2008-11-24 11:44 豬兒笨笨 閱讀(1724) 評論(3) 編輯 收藏 所屬分類: Java開發(fā) 、組件設計 、開源軟件 、Wicket