天天看點

openFire 源碼解讀



1.openfire的入口main檔案在src/java 檔案夾下的org.jivesoftware.openfire.starter包中。

2.運作main函數之後openfire會調用start方法,首先是擷取到ClassLoader對象。那麼什麼是ClassLoader對象呢?

下面具體學習ClassLoader的知識。

  1. 首先ClassLoader作用是加載Class檔案到jvm中,供程式使用,java程式可以動态加載類定義,這個動态加載的機制就是通過ClassLoader來實作的。
  2. ClassLoader 是加載Class檔案的(ExtClassLoader和AppClassLoader也在此時被加載),那麼ClassLoader又被誰加載呢?是一 個被不是java語言所編寫的ClassLoader來加載的,這個ClassLoader就是bootstrapClassLoader(啟動類加載 器)。這個加載器在jvm運作的時候加載java核心的api以滿足java程式最基本的需求。其中包括使用者定義的ClassLoader,使用者定義的 ClassLoader就是通過程式建立的ClassLoader,那麼也有非程式員建立的ClassLoader,就是jvm自己提供的吧(這句是自己 了解的)。使用者自定義的ClassLoader有ExtClassLoader,ExtClassLoader加載java的擴充的api,也就是 /lib/ext中的類。使用者自定義的ClassLoader還有AppClassLoader,AppClassLoader使用者機器上的 CLASSPATH設定目錄中的Class的,通常在沒有指定ClassLoader的情況下,程式自定義的類由AppClassLoader加載
  3. ClassLoader 的加載模式:雙親委托模式進行加載。該模式的原理是:某個自定義的ClassLoader加載Class的時候都會先委托他的parnet ClassLoader加載該Class,當parent ClassLoader加載失敗,再由目前的ClassLoader加載該Class,但是如果該ClassLoader的parent ClassLoader為null那麼該ClassLoader的parent就是bootstrapClassLoader。
  4. 使用雙親委托模式的優點是:

    第一:避免重複加載,當父親已經加載了該類,那麼子ClassLoader就沒有必要加載該class了。

    第二:安全因素。

3.擷取目前類的類類加載器的方法:

public ClassLoader findParentClassLoader(){ 
//擷取父類加載器 
    ClassLoader parent = Thread.currentThread().getContextClassLoader(); 
if(parent==null){ 
    parent = this.getClass().getClassLoader(); 
    if(parent==null){ 
        parent = ClassLoader.getSystemClassLoader(); 
} 
} 
return parent; 
}
           

4.類加載器的種類:

  •  bootstrap class Loader(引導類加載器) 用來加載java的核心類庫
  •  extensions class loader(擴充類加載器) 用來加載java的擴充庫Java 虛拟機的實作會提供一個擴充庫目錄。該類加載器在此目錄裡面查找并加載 Java 類[ExtClassLoader]
  • 系統類加載器(system class loader)Java 應用的類都是由它來完成加載的。可以通過 ClassLoader.getSystemClassLoader() 來擷取它[AppClassLoader]

上一節主要學習了jvm的類加載器,這節繼續進行,從org.jivesoftware.openfire.starter.ServerStarter檔案的第72行進行解讀。

System.getProperty("openfire.lib.dir");

上面這句話是什麼意思呢,根據字面意思了解應該是擷取到目前項目也就是openfire的lib路徑

繼續往下讀,如果存放lib的路徑不存在那麼就建立一個存放lib的檔案夾

同樣的通過這個方法可以擷取到其他的屬性 如下清單

java.version Java 運作時環境版本
java.vendor Java 運作時環境供應商
java.vendor.url Java 供應商的 URL
java.home Java 安裝目錄
java.vm.specification.version Java 虛拟機規範版本
java.vm.specification.vendor Java 虛拟機規範供應商
java.vm.specification.name Java 虛拟機規範名稱
java.vm.version Java 虛拟機實作版本
java.vm.vendor Java 虛拟機實作供應商
java.vm.name Java 虛拟機實作名稱
java.specification.version Java 運作時環境規範版本
java.specification.vendor Java 運作時環境規範供應商
java.specification.name Java 運作時環境規範名稱
java.class.version Java 類格式版本号
java.class.path Java 類路徑
java.library.path 加載庫時搜尋的路徑清單
java.io.tmpdir 預設的臨時檔案路徑
java.compiler 要使用的 JIT 編譯器的名稱
java.ext.dirs 一個或多個擴充目錄的路徑
os.name 作業系統的名稱
os.arch 作業系統的架構
os.version 作業系統的版本
file.separator 檔案分隔符(在 UNIX 系統中是“/”)
path.separator 路徑分隔符(在 UNIX 系統中是“:”)
line.separator 行分隔符(在 UNIX 系統中是“/n”)
user.name 使用者的賬戶名稱
user.home 使用者的主目錄
user.dir 使用者的目前工作目錄

上一節我們閱讀到了org.jivesoftware.openfire.starter.ServerStarter檔案中的第90行,這節繼續。

第90行調用unpackArchives(libDir, true);方法。

通過閱讀該方法的英文注釋大概意思是:轉換檔案夾中的一些封包件為一個标準的jar檔案,在轉換jar檔案的同時每個被轉的封包件就會被删除,如果封包件不存在,那麼就什麼都不做。

  • 過濾檔案

帶着這句話我們進行閱讀。該方法傳入了2個參數,第一個是一個lib檔案夾,第二個參數是個boolean值true。

File [] packedFiles = libDir.listFiles(new FilenameFilter() {
    public boolean accept(File dir, String name) {
        return name.endsWith(".pack");
    }
});
               
if (packedFiles == null) {
    // Do nothing since no .pack files were found
    return;
}
           

以上代碼是unpackArchives方法中的146到155行的代碼。

上面的第一句話我們大家應該很熟悉,沒錯,這句話我也經常用,但是我經常用不帶參數的方法也就是一般這樣用,

File [] packedFiles = libDir.listFiles();

這樣是擷取到一個檔案夾下的所有的檔案。

而帶參數的根據字面意思大概是過濾檔案名稱的意思,就是過濾一定規則的檔案,而不是顯示所有的檔案,

過濾用

FilenameFilter

這個接口,一般我們用接口都是通過繼承的方法來使用,但是我們現在通過new的方式來使用,其實這種用法還是蠻多的,比如很多注冊事件== 很多地方都大量運用了該方法。

但是new接口的時候我們會發現我們就要實作裡面所有的方法,少一個方法都不可以。因為

FilenameFilter

接口隻有一個方法

accept

是以我們在new的同時就會實作該方法,我們通過該方法直接過濾以某種字尾名的檔案就可以了現在我們要列出.pack類型的檔案是以我們應該寫

return

name.endsWith(

".pack"

);

就可以擷取到了。很友善吧。

通過以上代碼我們學習一個知識點,那就是擷取某個檔案夾下的某種格式的檔案清單應該用

FilenameFilter

來實作,實作方法是一下代碼

File [] packedFiles = libDir.listFiles(new FilenameFilter() {
    public boolean accept(File dir, String name) {
        return name.endsWith(".pack");
    }
});
           

如果沒有擷取到.pack類型的檔案那麼什麼都不做,直接傳回。

  • 具體實作把.pack的檔案轉換為jar檔案

    上面我們擷取到了.pack的檔案資料,然後開始周遊該數組,把每個.pack檔案轉為jar檔案。

    關鍵代碼如下

    InputStream in = new BufferedInputStream(new FileInputStream(packedFile));
    JarOutputStream out = new JarOutputStream(new BufferedOutputStream(
            new FileOutputStream(new File(libDir, jarName))));
    Pack200.Unpacker unpacker = Pack200.newUnpacker();
    // Print something so the user knows something is happening.
    if (printStatus) {
        System.out.print(".");
    }
    // Call the unpacker
    unpacker.unpack(in, out);
        
    in.close();
    out.close();
    packedFile.delete();
    unpacked = true;
               

以上的

packedFile

是周遊每個.pack的檔案。就這樣把.pack檔案轉為了jar檔案。

第三節中我們閱讀了org.jivesoftware.openfire.starter.ServerStarter檔案到91行,繼續吧!

這節我們跳過108行之前的從108行開始學習,91行到107行相對比較簡單。

從第108行到113行主要做了2件事情

第一:加載系統用到的jar包跟zip包到classpath中

第二:通過反射加載org.jivesoftware.openfire.XMPPServer類檔案。

一:那麼如何加載檔案到classpath中呢

  1. openfire用什麼加載檔案到classpath中:openfire中用org.jivesoftware.openfire.starter.JiveClassLoader加載檔案到classpath中(該類是繼承了URLClassLoader)
  2. openfire加載檔案到classpath的方法:

    首先找出jar跟zip類型的檔案,代碼如下

    1 2 3 4 5 6 7 8 9 10 11 12 13

    File[] jars = libDir.listFiles(

    new

    FilenameFilter() {

    public

    boolean

    accept(File dir, String name) {​

    boolean

    accept =

    false

    ;

    String smallName = name.toLowerCase();

    if

    (smallName.endsWith(

    ".jar"

    )) {

    accept =

    true

    ;

    }

    else

    if

    (smallName.endsWith(

    ".zip"

    )) {

    accept =

    true

    ;

    }

    return

    accept;

    }

    });

    然後調用父類URLClassLoader的addURL方法加載檔案到classpath中,代碼如下
    1 2 3 4 5

    for

    (

    int

    i =

    ; i < jars.length; i++) {

    if

    (jars[i].isFile()) {

    addURL(jars[i].toURI().toURL());

    }

    }

二:如何通過反射加載檔案​

1 2 3 4 5 6 7 8

//建立了一個自定義的類加載器,該類加載器同時加載了所需要的jar包          

ClassLoader loader =

new

JiveClassLoader(parent, libDir);

//設定自定義的類加載器為目前線程的類加載器

Thread.currentThread().setContextClassLoader(loader);

//利用目前線程的類加載器加載類檔案,注意:這裡需要帶包名寫全,不然項目中不通包中出現相同類檔案就可能出現找不到的情況

Class containerClass = loader.loadClass(

"org.jivesoftware.openfire.XMPPServer"

);

containerClass.newInstance();

到這裡org.jivesoftware.openfire.starter.ServerStarter檔案都閱讀完畢,ServerStarter中主要做了如下幾件事情:

  • 擷取classpath路徑
  • 把.pack檔案轉換為jar檔案
  • 擷取目前線程的類加載器
  • 建立自定義類加載器,并加載jar檔案跟zip檔案到classpath中
  • 利用自定義的類加載器啟動org.jivesoftware.openfire.XMPPServer類檔案

繼續閱讀