设为首页 收藏本站
查看: 429|回复: 0

[经验分享] 超越reloadable=true, 在Tomcat运行时动态重载

[复制链接]

尚未签到

发表于 2017-2-3 09:11:03 | 显示全部楼层 |阅读模式
  超越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

运维网声明 1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com

所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其承担任何法律责任,如涉及侵犯版权等问题,请您及时通知我们,我们将立即处理,联系人Email:kefu@iyunv.com,QQ:1061981298 本贴地址:https://www.yunweiku.com/thread-336808-1-1.html 上篇帖子: 花生壳+tomcat实现外网访问内网 下篇帖子: 关于eclips中tomcat发布失败的问题
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

扫码加入运维网微信交流群X

扫码加入运维网微信交流群

扫描二维码加入运维网微信交流群,最新一手资源尽在官方微信交流群!快快加入我们吧...

扫描微信二维码查看详情

客服E-mail:kefu@iyunv.com 客服QQ:1061981298


QQ群⑦:运维网交流群⑦ QQ群⑧:运维网交流群⑧ k8s群:运维网kubernetes交流群


提醒:禁止发布任何违反国家法律、法规的言论与图片等内容;本站内容均来自个人观点与网络等信息,非本站认同之观点.


本站大部分资源是网友从网上搜集分享而来,其版权均归原作者及其网站所有,我们尊重他人的合法权益,如有内容侵犯您的合法权益,请及时与我们联系进行核实删除!



合作伙伴: 青云cloud

快速回复 返回顶部 返回列表