`

超越reloadable=true, 在Tomcat运行时动态重载

阅读更多

超越reloadable=true, 在Tomcat运行时动态重载 收藏
为什么写这篇文档?

使用过hibernate,  spring或其他大型组件,写过50个类以上的网络应用程序(web  application)的开发者应该知道,当系统中有很多类时,如果开启了Tomcat的reloadable=true,那么每当相关文件改变时,Tomcat会停止web  app并释放内存,然后重新加载web  app.这实在是个浩大的工程。


所以我总是在想如果能有只重载某几个类的功能,将极大的满足我这个即时调试狂。

去年我在论坛上发帖,才发现已经有一些应用服务器具有了这个功能,比如WebLogic,  WebSphere,  等等。好像还有一个很酷的名字,叫开发模式。看来我还是孤陋寡闻了点。

当然很多人都是在Tomcat上开发,包括我。我很喜欢它的轻小,那些大内存和高CPU消耗的应用服务器不愧为硬件杀手,没理由不改进Tomcat  :)。

最终实现功能

我没有时间去研究Tomcat的文件监听机制,也没时间去把他写成”开发模式”这么完整的功能,我最终实现的是,实现重载功能的测试jsp--很抱歉我还是没办法写得更完整。当然,你可以在这个基础上进行改进。

阅读须知

阅读本文,你应该具备以下知识

jvm  规范有关类加载器的章节

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

Tomcat  类加载机制

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

java  反射机制

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

ant

http://ant.apache.org/

(好象该网址被不定时封锁,有时能上,有时不能)

最好在你的电脑上安装ant,因为Tomcat源码包使用ant从互联网获得依赖包。不过我也是修改了一个错误才使它完全编译通过。

当然,你也可以用其他IDE工具检查并添加依赖包,在IDE中,其实你只需要添加jar直到使org.apache.catalina.loader.WebappClassLoader无错即可。

修改过程

说明

新添加的代码请添加到java文件的末尾,因为我在说明行数的时候,尽量符合原始行数

web  app类加载器

在Tomcat中,org.apache.catalina.loader.WebappClassLoader是web  app的类加载器,所以需要修改它实现重载功能。

资源列表

在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;

}

是否重载标志

让WebappClassLoader需要知道加载一个类是否使用重载的方式。所以我建立一个boolean  类型的属性和实现它的getter/setter方法:

private  boolean  isReload  =  false;

 


           public  boolean  isReload()  {

                   return  isReload;

           }

 


           public  void  setReload(boolean  isReload)  {

                   this.isReload  =  isReload;

           }

动态类加载器

根据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);

       }

}

在webappClassLoader中添加DynamicClassLoader

添加属性

private  DynamicClassLoader  dynamicClassLoader  =  new  DynamicClassLoader(this);

添加重建方法,以便需要再次重载时替换掉上次的类加载器对象

public  void  reCreateDynamicClassLoader()  {

                               dynamicClassLoader  =  new  DynamicClassLoader(this);

                       }

修改调用点

第832行,公开findClass方法

public  Class  findClass(String  name)  throws  ClassNotFoundException  {

第1569行,添加如下一行代码。

if  (isReload)  removeResourceEntry(name);

第1577行,这里好像是一个bug,具体原因我忘了-_-||

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

改为

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

第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);

测试代码

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);

%>

org.AClass

package  org;

 


               public  class  AClass  {

                       public  AClass()  {

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

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

                       }

 


                       public  void  createBClass()  {

                               new  BClass();

                       }

               }

org.BClass

package  org;

 


               public  class  BClass  {

                       public  BClass()  {

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

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

                       }

               }

测试步骤

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

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

编译org.AClass和org.BClass

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

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

按下F5按键刷新浏览器。

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

Good  Luck!  :)))

本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/danning/archive/2006/04/14/663422.aspx

分享到:
评论

相关推荐

    让用户分别使用apple和cloud虚拟目录访问Web服务目录.docx

    &lt;Context path = "/apple" docBase = "D:\MyBook\zhang" debug = "0" reloadable = "true"/&gt; &lt;Context path = "/cloud" docBase = "D:\wang" debug = "0" reloadable = "true"/&gt; 注意:xml文件是区分大小写的,不...

    jsp+ExtJs+RMI 分布式宿舍管理系统 源码(内有数据库)

    &lt;Context path="/ourdorm-p1" reloadable="false" crossContext="true"&gt;&lt;/Context&gt; &lt;Context path="/ourdorm-p2" reloadable="false" crossContext="true"&gt;&lt;/Context&gt; &lt;Context path="/newourdorm-1" ...

    Tomcat源码研究

    &lt;Context path="" docBase="ROOT" debug="0" reloadable="true"&gt;&lt;/Context&gt; &lt;Context path="/jsp/a" reloadable="true" docBase="E:\workplace\www.java2000. net\WebContent" /&gt;   使用默认配置的tomcat,...

    修改Tomcat默认访问根目录的方法

    代码如下:&lt;Context path=”” docBase=”/usr/tomcat/apache-tomcat-8.5.11/webapps/guanwang” debug=”0″ reloadable=”true”/&gt; docBase即为默认访问的文件夹名称。Linux与Windows一样,只是文件夹地址有差别...

    Tomcat-7.0.30

    这个修改会对Tomcat的运行性能有影响,如果把Tomcat作为产品阶段所 使用的服务器,最好修改成 &lt;Context reloadable="false"&gt; 修改tomcat-users.xml &lt;?xml version='1.0' encoding='utf-8'?&gt; &lt;tomcat-...

    apache-tomcat-7.0.21-embed.zip

    (1) 确认“JDK+Tomcat”的运行环境已经搭建好。 (2) 从光盘中将shopping目录拷贝到电脑中,这里假定拷贝...&lt;Context path="/shop" docBase="D:/shopping" debug="0" reloadable="true" crossContext="true"&gt; &lt;/Context&gt;

    在Eclipse 中配置Tomcat项目

    &lt;Context path="" reloadable="true" docBase="E:\myworkspace\myproject\WebRoot" workDir="E:\myworkspace\myproject\work" &gt; &lt;Loader className="org.apache.catalina.loader.DevLoader" reloadable="true" ...

    tomcat-6.0.18去webapps

    tomcat-6.0.18.7z 去webapps和lib两个目录。 主要是用于比较其他的,看看虚拟目录...&lt;Context docBase="G:\\work\\flash\\flex\\EventDemo1\\bin-debug" reloadable="true" crossContext="true" debug="0"&gt; &lt;/Context&gt;

    在tomcat6.0里配置虚拟路径

    在tomcat6.0里配置虚拟路径,&lt;Context path="/虚拟路径名" docBase="应用程序实际地址" reloadable="true" &gt; &lt;/Context&gt;

    tomcat自定义Web部署文件中docBase和workDir的区别介绍

    本文主要介绍的是tomcat自定义Web部署文件中docBase和workDir的区别,下面话不多说,直接来看详细介绍。 首先看这段tomcat配置文件: ...jsp运行时都要先转换成servlet,使用tomcat时会在tomcat安装目录下

    详解tomcat热部署和热加载的方法

    热加载:在server.xml -&gt; context 属性中 设置 reloadable=”true” &lt;Context docBase=xxx path=/xxx reloadable=true/&gt; 2. 热部署:在server.xml -&gt; context 属性中 设置 autoDeploy=”true” &lt;Context ...

    Tomcat6.0连接池配置

    &lt;Context path="/hrms" docBase="hrms" debug="5" reloadable="true" crossContext="true"&gt; 1. &lt;Resource name="jdbc/sql" 2. auth="Container" 3. type="javax.sql.DataSource" 4. driverClassName=...

    修改java类而无需重启tomcat.rar

    发布项目时可设置:reloadable="false" \apache-tomcat-5.5.36\conf\Catalina\localhost 目录下.xml文件 测试看看。。。。。。 说明:对于只修改java类而不希望tomcat重启时挺有用的,如果用了spring加载的话,...

    移动小商城:基于node,包含前后台.zip

    1.将my 文件夹,移动至 D盘根目录下 2.在eclipse 引入的tomcat 的Host 中加入 &lt;Context crossContext="true" ... &lt;Context crossContext="true" docBase="D:\my\ueditor\" path="/ueditor" reloadable="true"/&gt;

    Apache2.2整合Tomcat6所用软件包

    &lt;Context path="/uambank" reloadable="false" docBase="D:\\jdk\\workspace\\uambank\\uambank\\deploy\\target\\uambank-webapp" workDir="D:/jdk/workspace/work" /&gt; tomcat_bbs的设定 Shutdown 端口 ...

    Tomcat实现热部署

    热部署概念 热部署是指在你对JSP或JAVA类进行了修改在不重启WEB服务器前提下能让修改... &lt;Context docBase=myPrj path=/demo1 reloadable=true/&gt; docBase: webapps下的你项目的包名 path:项目访问路径 reloadable: 是

    tomcat 配置数据源

    debug="5" reloadable="true" crossContext="true"&gt; className="org.apache.catalina.logger.FileLogger" prefix="localhost_MysqlTest_log." suffix=".txt" timestamp="true"/&gt; ...

    Tomcat数据源配置方法_JBuilder中

    很灵活,所以也容易相互整窜,个人认为比较简单的方法就是在tomcat下的conf文件夹下的server.xml增加 代码如下: &lt;Context path=”/test” docBase=”test” xss=removed reloadable=”true” crossContext=”true” ...

    apahce2.2.4整合tomcat6.0.016

    apache config conf/workers.properties workers.tomcat_home=... &lt;Context path="" docBase="\WebRoot" debug="0" reloadable="false"/&gt; 网名:冷漠大神 qq:361619004 phone:15948337260 mail:hackq@163.com

    jsp +SQL server用 连接池做的实验室设管理系统.rar

    &lt;Context path="/DevMg" docBase="F:\Program Files\Tomcat 5.5\webapps\DevMg\WebRoot" reloadable="true"/&gt; 4.把DevMg 中的WEB-INF中的lib 里面的jar包拷到Tomcat下面的Common里面的LIB文件夹里面 5. 打开...

Global site tag (gtag.js) - Google Analytics