超越reloadable=true, 在Tomcat運行時動態重載類(version 5.0.28)

來源:互聯網
上載者:User

  1. 為什麼寫這篇文檔?

使用過hibernate, spring或其他大型組件,寫過50個類以上的網路應用程式(web application)的開發人員應該知道,當系統中有很多類時,如果開啟了Tomcat的reloadable=true,那麼每當相關檔案改變時,Tomcat會停止web app並釋放記憶體,然後重新載入web app.這實在是個浩大的工程。

所以我總是在想如果能有只重載某幾個類的功能,將極大的滿足我這個即時調試狂。

去年我在論壇上發帖,才發現已經有一些應用伺服器具有了這個功能,比如WebLogic, WebSphere, 等等。好像還有一個很酷的名字,叫開發模式。看來我還是孤陋寡聞了點。

當然很多人都是在Tomcat上開發,包括我。我很喜歡它的輕小,那些大記憶體和高CPU消耗的應用伺服器不愧為硬體殺手,沒理由不改進Tomcat 。

  1. 最終實現功能

我沒有時間去研究Tomcat的檔案監聽機制,也沒時間去把他寫成”開發模式”這麼完整的功能,我最終實現的是,實現重載功能的測試jsp--很抱歉我還是沒辦法寫得更完整。當然,你可以在這個基礎上進行改進。

  1. 閱讀須知

閱讀本文,你應該具備以下知識

    1. jvm 規範有關類載入器的章節

      http://java.sun.com/docs/books/vmspec/2nd-edition/html/VMSpecTOC.doc.html

    2. Tomcat 類載入機制

      http://www.huihoo.org/apache/tomcat/

    3. java 反射機制

      http://java.sun.com/docs/books/tutorial/reflect/

    4. ant

      http://ant.apache.org/

      (好象該網址被不定時封鎖,有時能上,有時不能)

最好在你的電腦上安裝ant,因為Tomcat源碼包使用ant從互連網獲得依賴包。不過我也是修改了一個錯誤才使它完全編譯通過。

當然,你也可以用其他IDE工具檢查並添加依賴包,在IDE中,其實你只需要添加jar直到使org.apache.catalina.loader.WebappClassLoader無錯即可。

  1. 修改過程

    1. 說明

新添加的代碼請添加到java檔案的末尾,因為我在說明行數的時候,盡量符合原始行數

    1. web app類載入器

在Tomcat中,org.apache.catalina.loader.WebappClassLoader是web app的類載入器,所以需要修改它實現重載功能。

    1. 資源清單

在WebappClassLoader中,有一個Map類型屬性resourceEntries,它記載了web app中WEB-INF/classes目錄下所載入的類,因此當我們需要重載一個類時,我們需要先將它在resourceEntries裡刪除,我編寫了一個方法方便調用:

public boolean removeResourceEntry(String name) {

    if (resourceEntries.containsKey(name)) {

        resourceEntries.remove(name);

        return true;

    }

    return false;

}

    1. 是否重載標誌

讓WebappClassLoader需要知道載入一個類是否使用重載的方式。所以我建立一個boolean 類型的屬性和實現它的getter/setter方法:

private boolean isReload = false;

     public boolean isReload() {

         return isReload;

     }

     public void setReload(boolean isReload) {

         this.isReload = isReload;

     }

    1. 動態類載入器

根據jvm類載入器規範,一個類載入器對象只能載入一個類1次,所以重載實際上是建立出另一個類載入器對象來載入同一個類。當然,我們不需要再建立一個WebappClassLoader,他太大而且載入規則很複雜,不是我們想要的,所以我們建立一個簡單的類載入器類org.apache.catalina.loader.DynamicClassLoader:

package org.apache.catalina.loader;

import java.net.URL;

import java.net.URLClassLoader;

import java.security.CodeSource;

import java.util.*;

/**

* 動態類載入器

*

* @author peter

*

*/

public class DynamicClassLoader extends URLClassLoader {

    /* 父類載入器 */

    private ClassLoader parent = null;

    /* 已載入類名列表 */

    private List classNames = null;

    /**

    * 構造器

    *

    * @param parent

    * 父類載入器,這裡傳入的是WebappClassLoader

    */

    public DynamicClassLoader(ClassLoader parent) {

        super(new URL[0]);

        classNames = new ArrayList();

        this.parent = parent;

    }

    /**

    * 從類的位元據中載入類.

    *

    * @param name

    * 類名

    * @param classData

    * 類的位元據

    * @param codeSource

    * 資料來源

    * @return 成功載入的類

    * @throws ClassNotFoundException

    * 載入失敗拋出未找到此類異常

    */

    public Class loadClass(String name, byte[] classData, CodeSource codeSource) throws ClassNotFoundException {

        if (classNames.contains(name)) {

            // System.out.println("此類已存在,調用 loadClass 方法載入.");

            return loadClass(name);

        } else {

            // System.out.println("新類, 記錄到類名列表,並用類定義方法載入類");

            classNames.add(name);

            return defineClass(name, classData, 0, classData.length, codeSource);

        }

    }

    /* *

    * 重載此方法,當要載入的類不在類名列表中時,調用父類載入器方法載入.

    * @see java.lang.ClassLoader#loadClass(java.lang.String)

    */

    public Class loadClass(String name) throws ClassNotFoundException {

        if (!classNames.contains(name)) {

            //System.out.println("不在類名列表中,調用父類載入器方法載入");

            return parent.loadClass(name);

        }

        return super.loadClass(name);

    }

}

    1. 在webappClassLoader中添加DynamicClassLoader

      1. 添加屬性

private DynamicClassLoader dynamicClassLoader = new DynamicClassLoader(this);

      1. 添加重建方法,以便需要再次重載時替換掉上次的類載入器對象

public void reCreateDynamicClassLoader() {

                dynamicClassLoader = new DynamicClassLoader(this);

            }

    1. 修改調用點

      1. 第832行,公開findClass方法

public Class findClass(String name) throws ClassNotFoundException {

      1. 第1569行,添加如下一行代碼。

if (isReload) removeResourceEntry(name);

      1. 第1577行,這裡好像是一個bug,具體原因我忘了-_-||

if ((entry == null) || (entry.binaryContent == null))

改為

if ((entry == null) || (entry.loadedClass == null && entry.binaryContent == null))

      1. 第1633~1636行

if (entry.loadedClass == null) {

                clazz = defineClass(name, entry.binaryContent, 0, entry.binaryContent.length,

                    codeSource);

            改為

            byte[] classData = new byte[entry.binaryContent.length];

            System.arraycopy(entry.binaryContent, 0, classData, 0,

            classData.length);

            if (entry.loadedClass == null) {

                clazz = isReload ?

                    dynamicClassLoader.loadClass(name,

                    classData, codeSource) :

                    defineClass(name,

                    classData, 0, classData.length, codeSource);

    1. 測試代碼

      1. test.jsp

我測試用的jsp為$CATALINA_HOME/webapps/ROOT/test.jsp,由於webapp裡面並不會明確式載入tomcat的核心類,所以我們需要用反射代碼調用WebappClassLoader的方法。代碼如下:

<%

ClassLoader loader = (Thread.currentThread().getContextClassLoader());

Class clazz = loader.getClass();

java.lang.reflect.Method setReload = clazz.getMethod("setReload", new Class[]{boolean.class});

java.lang.reflect.Method reCreate = clazz.getMethod("reCreateDynamicClassLoader", null);

java.lang.reflect.Method findClass = clazz.getMethod("findClass", new Class[]{String.class});

reCreate.invoke(loader, null);

setReload.invoke(loader, new Object[]{true});

Class A = (Class)findClass.invoke(loader, new Object[]{"org.AClass"});

setReload.invoke(loader, new Object[]{false});

A.newInstance();

// 如果你使用下面這行代碼,當重編譯類時,請稍微修改一下調用它的jsp,讓jsp也重新編譯

//org.AClass a = (org.AClass)A.newInstance();

// 下面這些代碼是測試當一個類不在DynamicClassLoader類名列表時的反應

//a.test();

//java.lang.reflect.Method test = a.getClass().getMethod("test", null);

//test.invoke(a, null);

%>

      1. org.AClass

package org;

        public class AClass {

            public AClass() {

                // 修改輸出內容確認Tomcat重新載入了類

                System.out.println("AClass v3");

            }

            public void createBClass() {

                new BClass();

            }

        }

      1. org.BClass

package org;

        public class BClass {

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.