小P的专栏

入门资料,图文教程

原创 超越reloadable=true, 在Tomcat运行时动态重载类(version 5.0.28)收藏

新一篇: Tomcat资源管理器模块发布--动态重载资源 | 旧一篇: 用xmlhttp将html的数据打包成multipart/form-data格式,实现异步上传文件功能

  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.WebappClassLoaderweb app的类加载器,所以需要修改它实现重载功能。

    1. 资源列表

WebappClassLoader中,有一个Map类型属性resourceEntries,它记载了web appWEB-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 {

            public BClass() {

                //修改输出内容确认Tomcat重新加载了类

                System.out.println("BClass v1");

            }

        }

    1. 测试步骤

      1. 按照上述步骤修改Tomcat源码并编译。

      2. winzip/winrar/file-roller打开$CATALINA_HOME/server/lib/catalina.jar。把前面编译完成后的org.apache.catalina.loader目录下的class文件覆盖jar中同名文件。

      3. 编译org.AClassorg.BClass

      4. 启动Tomcat并在浏览器中打开测试页http://localhost:8080/test.jsp

      5. 修改org.AClass中的System.out.println();语句并重编译类。

      6. 按下F5按键刷新浏览器。

      7. 查看Tomcat控制台是否输出了不同的语句?

      8. Good Luck! :)))

发表于 @ 2006年03月06日 14:33:00|评论(loading...)|编辑

新一篇: Tomcat资源管理器模块发布--动态重载资源 | 旧一篇: 用xmlhttp将html的数据打包成multipart/form-data格式,实现异步上传文件功能

评论

#cm4ever 发表于2006-04-26 23:31:00  IP: 219.136.219.*
注意,在开发Apache Tomcat Resources Manage Module的过程中,发现本文的代码有冗余和错误,因此本文只可做为参考,最新代码,请到项目地址下载阅读:
http://sourceforge.net/projects/tomcat-res-mgr
#hama 发表于2006-07-28 00:05:00  IP: 221.221.156.*
楼主不厚道了,翻译的为什么说成是原创呢?看下帖!!!!!!
#hama 发表于2006-07-28 00:07:00  IP: 221.221.156.*
http://mail-archives.apache.org/mod_mbox/tomcat-dev/200603.mbox/%3C39cd3e220603090956y45a54db6m@mail.gmail.com%3E
#hama 发表于2006-07-28 00:11:00  IP: 221.221.156.*
http://mail-archives.apache.org/mod_mbox/tomcat-dev/200603.mbox/%3C39cd3e220603090956y45a54db6m@mail.gmail.com%3E
#cm4ever 发表于2006-07-29 12:08:00  IP: 219.137.139.*
那篇也是我写的啊,只有我才写得出这么蹩脚的中式英语。

而且这篇文章tomcat开发人员都没看。
后来我在sourceforge上发表了可运行的模块,才引起注意。这说明人都是懒的,没现成的东西都不想动。
那个gmail油箱也是我注册的,基本上每月去个两三次。呵呵。
#11 发表于2006-07-31 17:42:00  IP: 61.185.224.*
11
#diyinzi 发表于2007-05-13 11:09:36  IP: 125.91.175.*
小P真是厉害,俺虽然技术不行,还是顶一下。。。
#treamboy 发表于2007-05-27 15:59:47  IP: 124.42.72.*
hi,家伙,速速在MSN里出现。都以为你挂了呢。
#eminem1013 发表于2007-11-07 10:34:56  IP: unknown, 211.*
楼主果然是牛人,我现在有个问题,麻烦楼主或者跟贴得帮忙看一下,我启动一个线程去定时地察看jar文件,如果发现文件更新,则重新加载进来。程序启动的时候是没问题的,但是当我覆盖jar包的时候,监听线程发现更新以后,重新加载jar包。可是这时候却报找不到资源的错误。我初步的判断是文件的权限问题,可能是SystemManager的问题。
还有就是tomcat的webappclassloader为什么重写了urlclassloader那么多的方法,是不是就是为了避免2个记载器重新加载一个类文件出现的错误。
发表评论  


当前用户设置只有注册用户才能发表评论。如果你没有登录,请点击登录
Csdn Blog version 3.1a
Copyright © cm4ever