http://blog.csdn.net/totodo/archive/2005/01/28/271798.aspx
也許你認為Class Load是一個高級話題,不管怎樣,作為開發者你還是要了解它。
理解CLassLoader
也許你欣然以為這樣寫沒問題,但實際上你錯了。?
java.lang.Class klass = Myclass.class;
而實例化一個類,可以是:Myclass myclass = new Myclass() 也可以是: myclass.newInstance();
JDK中除了ClassLoader可以加載類之外,還有以下這些也可以。
-
java.net.URLClassLoader
-
java.security.SecureClassLoader
-
java.rmi.server.RMIClassLoader
-
sun.applet.AppletClassLoader
??? (String name, boolean resolve)
??? throws ClassNotFoundException{
??? // First check if the class is already loaded
??? Class c = findLoadedClass(name);
??? if (c == null) {
??????? try {
??????????? if (parent != null) {
??????????????? c = parent.loadClass(name, false);
??????????? } else {
??????????????? c = findBootstrapClass0(name);
??????????? }
??????? } catch (ClassNotFoundException e) {
??????????? // If still not found, then invoke
??????????? // findClass to find the class.
??????????? c = findClass(name);
??????? }
??? }
??? if (resolve) {
??? resolveClass(c);
??? }
??? return c;
}
?
??? public MyClassLoader(){
??????? super(MyClassLoader.class.getClassLoader());
??? }
}
??? public MyClassLoader(){
??????? super(getClass().getClassLoader());
??? }
}
??? protected Class<?> findClass(String name)
??????? throws ClassNotFoundException {
??????? throw new ClassNotFoundException(name);
??? }
?
每個ClassLoader出來的類都是不同的, 如果有兩個ClassLoader載入兩各相同的程序,defineClass()定義得兩個類也是不同得。詳細請看( Java language specification )
根據上面得解析,既然由兩個classLoader()載入Target.class得字節碼 ,那defineClass()就會產生兩個class的定義。
?
Target target1 = (Target) target2;?是不正確的?。target1 和 target2是由兩個不同的classloader定義的。
??
?? 上面說一個class標識是由于package+classname組成得。 對于所有實現java.io. Serializable 接口的類,都是由 serialVersionUID 管理這些類得版本( RMI,JNDI,Security里都有這樣一個ID) 。它用64位的Hash來表示 (這個Hash由classname,filed,method組成)。從技術上講如果classname,field,mehtod所構成的Hash都一樣,那就會認為是同一個版本。
??????? log("this = " + this + "; Version.fx(1).");
??? }
??????? log("this = " + this + "; Version.fx(2).");
??? }
%JAVA_HOME%\bin\java Test 結果如下圖
%JAVA_HOME%\bin\java Test
結果如下圖:
?
很明顯,上面的例子中能從classpath中找到先后次序。如果我們把v1,v2的version.Version。都刪調。而把他們打成一個 myextension.jar包,放到java.ext.dirs目錄下。。這時候就通過 ExtClassLoader 來裝載了,而不是AppClassLoader.
結果會是如下:
圖?5.
AppClassLoader
and
ExtClassLoader
注意看 sun.misc.Launcher$ExtClassLoader@a9c85c 這說明是ExtClassLoader 加載了。
?
繼續往下看,另外一個例子。?在 differentversions 目錄下的例子,里面包含了RMI的ServerImpl這樣一個執行引擎。Client實現了 common.TaskIntf接口。 兩個 client.TaskImpl分別如下:
??? static{
??????? log("client.TaskImpl.class.getClassLoader
??????? (v1) : " + TaskImpl.class.getClassLoader());
??? }
??? public void execute(){
??????? log("this = " + this + "; execute(1)");
??? }
?另一個則是:
????
?static{
??????? log("client.TaskImpl.class.getClassLoader
??????? (v1) : " + TaskImpl.class.getClassLoader());
??? }
??? public void execute(){
??????? log("this = " + this + "; execute(2)");
??? }
這樣子來執行(順序隨便,這里把 %CURRENT_ROOT%\client2放在前面 ):
CLASSPATH=%CURRENT_ROOT%\common;%CURRENT_ROOT%\server;
??? %CURRENT_ROOT%\client2;%CURRENT_ROOT%\client1
%JAVA_HOME%\bin\java server.Server
先啟動Server..
? 分別把兩個client提交給服務器執行, (即便執行程序中得client1.bat 和 client2.bat server監控屏幕如圖6所示。)?
圖?6. Execution Engine Server console
?
再來看下面兩個圖(圖7和圖8),分別是client端得執行顯示。
圖?7. Execution Engine Client 1 console
圖?8. Execution Engine Client 2 console
?
縱觀上面三次執行結果,發現由于服務器啟動得時候使用了AppClassLoader.所以無論怎么樣都是載入得是client2(因為client2的classpath次序比較在前),
這里client1 很郁悶,它在自己那執行明明是? execute(1) 通過 RMI 發送給服務器端執行就成了 execute(2)..
值得注意的是: 在client1,client2分別發送給服務器執行之后,服務器端顯示的記錄是:
client.TaskImpl.class.getClassLoader(v2):sun.misc.lancuher@AppClassLoader@xxxx zhiz只執行了一次。而
this=client.TaskImpl@xxxxx
execute(2);執行了兩次
上面已經講到過了,對于一個ClassLoader來講 同樣的page+className 只能定義一個 class,而不同的ClassLoader即便加載同一個page.className 也會定義不同的class
?
到這里,我才發現,解決上面提出得那個問題似乎并不容易。:(。
那如何解決呢?答案就是---使用自定義得classLoader ..
如果各位等不急的話, 先請看(目錄中 differentversionspush 里面的代碼)
很顯然,我們很有必要寫自定義的classloader.
如何構造使用自定義的ClassLoader
既然自定義的ClassLoader,能解決上述問題,那接下去看看,我們如何來使用自定義的ClassLoader。
結合本文種的原碼---(在differentversionspush的目錄里),有個FileSystemClassLoader,類圖描述如下:
?
看看他的方法 findClassBytes(String className);
??? public byte[] findClassBytes(String className){
??????? try{
??????????? String pathName = currentRoot +
??????????????? File.separatorChar + className.
??????????????? replace('.', File.separatorChar)
??????????????? + ".class";
??????????? FileInputStream inFile = new
??????????????? FileInputStream(pathName);
??????????? byte[] classBytes = new
??????????????? byte[inFile.available()];
??????????? inFile.read(classBytes);
??????????? return classBytes;
??????? }
??????? catch (java.io.IOException ioEx){
??????????? return null;
??????? }
??? }
??? public Class findClass(String name)throws
??????? ClassNotFoundException{
??????? byte[] classBytes = findClassBytes(name);
??????? if (classBytes==null){
??????????? throw new ClassNotFoundException();
??????? }
??????? else{
??????????? return defineClass(name, classBytes,
??????????????? 0, classBytes.length);
??????? }
??? }
??? public Class findClass(String name, byte[]
??????? classBytes)throws ClassNotFoundException{
??????? if (classBytes==null){
??????????? throw new ClassNotFoundException(
??????????????? "(classBytes==null)");
??????? }
??????? else{
??????????? return defineClass(name, classBytes,
??????????????? 0, classBytes.length);
??????? }
??? }
??? public void execute(String codeName,
??????? byte[] code){
??????? Class klass = null;
??????? try{
??????????? klass = findClass(codeName, code);
??????????? TaskIntf task = (TaskIntf)
??????????????? klass.newInstance();
??????????? task.execute();
??????? }
??????? catch(Exception exception){
??????????? exception.printStackTrace();
??????? }
??? }
這 個類FileSystemClassLoader 被client使用了,用來定義class, 并且把它把client.TaskImpl(v1)轉化為 byte[], 然后 byte[]發送到RMI Server執行。(上面講了defineClass()能夠執行任何字節碼,來自編譯后的文件,網絡甚至是BCEL 字節碼引擎庫),?? 在Server端 ,又可以通過FileSystemClassLoader 以為byte[]的形式定義出 client.TaskImpl。
?
請看Client端的代碼:
public class Client{
??? public static void main (String[] args){
??????? try{
??????????? byte[] code = getClassDefinition
??????????????? ("client.TaskImpl");
??????????? serverIntf.execute("client.TaskImpl",
??????????????? code);
??????????? }
??????????? catch(RemoteException remoteException){
??????????????? remoteException.printStackTrace();
??????????? }
??????? }
??? private static byte[] getClassDefinition
??????? (String codeName){
??????? String userDir = System.getProperties().
??????????? getProperty("BytePath");
??????? FileSystemClassLoader fscl1 = null;
??????? try{
??????????? fscl1 = new FileSystemClassLoader
??????????????? (userDir);
??????? }
??????? catch(FileNotFoundException
??????????? fileNotFoundException){
??????????? fileNotFoundException.printStackTrace();
??????? }
??????? return fscl1.findClassBytes(codeName);
??? }
}
在RMI服務器端ServerImpl?程序里,?接受到來自client的字節碼(byte[]),于是FileSystemClassLoader 會從byte[]構造出一個class, 實例話,并且執行。
有一點要注意:每次接收到一個client的請求,FileSystemClassLoader都會重新實例化(執行結果中可以看出來),這就意 味著,client.Impl不在是在classpath中被找到的,而是通過FileSystemClassLoader 的findClass() 來執行deFineClass(),這樣每次 FileSystemClassLoader?都是創建新的實例,,自然 deFine出來的class也是不同的。 這樣,我們就能在RMI的執行中區分出 這兩個class來。(client.TaskImpl != client.TaskImp??在上篇就已經得出結論了。?)
看看服務器端的執行代碼:
public void execute(String codeName, byte[] code)throws RemoteException{
??????? FileSystemClassLoader fileSystemClassLoader = null;
??????? try{
??????????? fileSystemClassLoader = new FileSystemClassLoader();
??????????? fileSystemClassLoader.execute(codeName, code);
??????? }
??????? catch(Exception exception){
??????????? throw new RemoteException(exception.getMessage());
??????? }
??? }
?
服務器端的執行結果:
下面兩圖分別是客戶端顯示的。
?
哈,上面洋洋灑灑那么多,總算是一步一步的教會了大家 如何在同一個VM虛擬機中,執行“不同版本”的代碼 。(這些代碼有同樣的類名和包名)。
?
Class Loaders 在 J2EE 中應用。
????? 我的一個A_war.war的web項目中 代碼是 com.mycom.Test 而我在另外一個B_war.war的wenb項目中的 代碼也是com.mycom.Test 而他們照樣工作的好好的。
????? 當一個大型的 EJB項目,一臺服務器上部署了多個 EJB,War工程時候,他們也不會互相影響。AppServer還會有自己的裝載策略,比如你web中用的jar包,會優先于AppServer本身所帶有的。
??? 另外,J2EE的ClassLoader機制更詳細的能容你可以參考Tss上的這篇文章。 Understanding J2EE Application Server Class Loading Architectures "?
資源文件:
原文:
http://www.onjava.com/pub/a/onjava/2005/01/26/classloading.htm
更多文章、技術交流、商務合作、聯系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號聯系: 360901061
您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對您有幫助就好】元
