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

[经验分享] tomcat热部署的实现原理

[复制链接]

尚未签到

发表于 2015-8-8 07:41:45 | 显示全部楼层 |阅读模式
  一.             概述
  名词解释:所谓热部署,就是在应用正在运行的时候升级软件,却不需要重新启动应用。
  对于Java应用程序来说,热部署就是在运行时更新Java类文件。在基于Java的应用服务器实现热部署的过程中,类装入器扮演着重要的角色。大多数基于Java的应用服务器,包括EJB服务器和Servlet容器,都支持热部署。类装入器不能重新装入一个已经装入的类,但只要使用一个新的类装入器实例,就可以将类再次装入一个正在运行的应用程序。
  我们知道,现在大多数的web服务器都支持热部署,而对于热部署的实现机制,网上讲的却不够完善,下面我们就tomcat的热部署实现机制,讲解一下它是如何实现的:
  Tomcat的容器实现热部署使用了两种机制:
  1.  Classloader重写,通过自定义classloader加载相应的jsp编译后的class到JVM中。
  2.  通过动态修改内存中的字节码,将修改过的class再次装载到JVM中。
  
  二.             Classloader实现jsp的重新加载
  Tomcat通过org.apache.jasper.servlet.JasperLoader实现了对jsp的加载,下面做个测试:
  1. 新建一个web工程,并编写一个jsp页面,在jsp页面中输出该页面的classloader,.
  2.  启动web服务器,打开jsp页面,我们可以看到后台输出,该jsp的classloader是JasperLoader的一个实例。
  3.  修改jsp,保存并刷新jsp页面,再次查看后台输出,此classloader实例已经不是刚才那个了,也就是说tomcat通过一个新的classloader再次装载了该jsp。
  4.  其实,对于每个jsp页面tomcat都使用了一个独立的classloader来装载,每次修改完jsp后,tomcat都将使用一个新的classloader来装载它。
  
  关于如何使用自定义classloader来装载一个class这里就不说了,相信网上都能找到,JSP属于一次性消费,每次调用容器将创建一个新的实例,属于用完就扔的那种,但是对于这种实现方式却很难用于其它情况下,如现在我们工程中很多都使用了单例,尤其是spring工程,在这种情况下使用新的classloader来加载修改后的类是不现实的,单例类将在内存中产生多个实例,而且这种方式无法改变当前内存中已有实例的行为,当然,tomcat也没通过该方式实现class文件的重新加载。
  
  三.             通过代理修改内存中class的字节码
  Tomcat中的class文件是通过org.apache.catalina.loader. WebappClassLoader装载的,同样我们可以做个测试,测试过程与jsp测试类似,测试步骤就不说了,只说一下结果:
  在热部署的情况下,对于被该classloader 加载的class文件,它的classloader始终是同一个WebappClassLoader,除非容器重启了,相信做完这个实验你就不会再认为tomcat是使用一个新的classloader来加载修改过的class了,而且对于有状态的实例,之前该实例拥有的属性和状态都将保存,并在下次执行时拥有了新的class的逻辑,这就是热部署的神秘之处(其实每个实例只是保存了该实例的状态属性,我们通过序列化对象就能看到对象中包含的状态,最终的逻辑还是存在于class文件中)。
  下面的class重定义是通过:java.lang.instrument实现的,具体可参考相关文档。
  下面我们看一下如何通过代理修改内存中的class字节码:
  以下是一个简单的热部署代理实现类(代码比较粗糙,也没什么判断):
package agent;


import java.lang.instrument.ClassFileTransformer;

import java.lang.instrument.Instrumentation;

import java.util.Set;

import java.util.Timer;

import java.util.TreeSet;



public  class  HotAgent {

    protected  static  Set  clsnames=new TreeSet();

   

    public  static  void  premain(String  agentArgs, Instrumentation  inst)  throws Exception {

       ClassFileTransformer  transformer =new ClassTransform(inst);

       inst.addTransformer(transformer);

       System.out.println("是否支持类的重定义:"+inst.isRedefineClassesSupported());

       Timer  timer=new  Timer();

      timer.schedule(new ReloadTask(inst),2000,2000);

    }

}

  
package agent;


import java.lang.instrument.ClassFileTransformer;

importjava.lang.instrument.IllegalClassFormatException;

import java.lang.instrument.Instrumentation;

import java.security.ProtectionDomain;


public  class  ClassTransform.  implements ClassFileTransformer {

    private  Instrumentation  inst;

    protected  ClassTransform(Instrumentation  inst){

       this.inst=inst;

    }

    /**

     * 此方法在redefineClasses时或者初次加载时会被调用,也就是说在class被再次加载时会被调用,

     * 并且我们通过此方法可以动态修改class字节码,实现类似代理之类的功能,具体方法可使用ASM或者javasist,

     * 如果对字节码很熟悉的话可以直接修改字节码。

     */

    public  byte[]  transform(ClassLoader  loader, String  className,

           Class  classBeingRedefined, ProtectionDomain  protectionDomain,

           byte[]  classfileBuffer)throws IllegalClassFormatException {

       byte[]  transformed = null;

       HotAgent.clsnames.add(className);

        return  null;

    }

}

  
package agent;


import java.io.InputStream;

import java.lang.instrument.ClassDefinition;

import java.lang.instrument.Instrumentation;

import java.util.TimerTask;


public  class  ReloadTask  extends  TimerTask {

   

    private  Instrumentation  inst;

    protected  ReloadTask(Instrumentation  inst){

       this.inst=inst;

    }

   

    @Override

    public  void  run() {

       try{

           ClassDefinition[]  cd=new ClassDefinition[1];

           Class[]  classes=inst.getAllLoadedClasses();

           for(Class  cls:classes){

          if(cls.getClassLoader()==null||!cls.getClassLoader().getClass().getName().equals("sun.misc.Launcher$AppClassLoader"))

                  continue;

              String  name=cls.getName().replaceAll("\\.","/");

             cd[0]=new ClassDefinition(cls,loadClassBytes(cls,name+".class"));

              inst.redefineClasses(cd);

           }

         

       }catch(Exception ex){

           ex.printStackTrace();

       }

    }

   

    private  byte[]  loadClassBytes(Class  cls,String  clsname) throws  Exception{

       System.out.println(clsname+":"+cls);

       InputStream  is=cls.getClassLoader().getSystemClassLoader().getResourceAsStream(clsname);

       if(is==null)return  null;

       byte[]  bt=new  byte[is.available()];

       is.read(bt);

       is.close();

       return  bt;

    }

}

  
  以上是基本实现代码,需要组件为:
  1.  HotAgent(预加载)
  2.  ClassTransform(在加载class的时候可以修改class的字节码),本例中没用到
  3.  ReloadTask(class定时加载器,以上代码仅供参考)
  4.  META-INF/MANIFEST.MF内容为:(参数一:支持class重定义;参数二:预加载类)
Can-Redefine-Classes: true

  Premain-Class: agent.HotAgent
  5.  将以上组件打包成jar文件(到此,组件已经完成,下面为编写测试类文件)。
  6.  新建一个java工程,编写一个java逻辑类,并编写一个Test类,在该测试类中调用逻辑类的方法,下面看下测试类代码:
package test.redefine;


public  class  Bean1 {

    public  void  test1(){

      System.out.println("============================");

    }

  }
  
package test.redefine;


public  class  Test {

    public  static  void  main(String[] args)throws  InterruptedException {

       Bean1  c1=new  Bean1();

       while(true){

           c1.test1();

           Thread.sleep(5000);

       }

    }

  }
  
  运行测试类:
  java –javaagent:agent.jar test.redefine.Test
  在测试类中,我们使用了一个死循环,定时调用逻辑类的方法。我们可以修改Bean1中的方法实现,将在不同时间看到不同的输出结果,关于技术细节也没什么好讲的了,相信大家都能明白。

运维网声明 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-95269-1-1.html 上篇帖子: Tomcat Configuration Using CATALINA_BASE 下篇帖子: 转载:tomcat部署项目的几种方式 .
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

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

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

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

扫描微信二维码查看详情

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


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


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


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



合作伙伴: 青云cloud

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