精华内容
下载资源
问答
  • Tomcat热部署原理

    2012-05-19 20:49:10
    对于Java应用程序来说,部署就是在运行时更新Java类文件。在基于Java的应用服务器实现部署的过程中,类装入器扮演着重要的角色。大多数基于Java的应用服务器,包括EJB服务器和Servlet容器,都支持部署。类装入...
    一.             概述

    名词解释:所谓热部署,就是在应用正在运行的时候升级软件,却不需要重新启动应用。

    对于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,<%System.out.print(this.getClass().getClassLoader());%>.

    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<String> clsnames=new TreeSet<String>();



    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中的方法实现,将在不同时间看到不同的输出结果,关于技术细节也没什么好讲的了,相信大家都能明白。
    展开全文
  • tomcat热部署的实现原理2011-07-19 10:58:43 分类:Linux 一.概述 名词解释:所谓热部署,就是在应用正在运行的时候升级软件,却不需要重新启动应用。 对于Java应用程序来说,热部署就是在运行时...
     tomcat热部署的实现原理 2011-07-19 10:58:43

    分类: Linux

     

     

    一.             概述

    名词解释:所谓热部署,就是在应用正在运行的时候升级软件,却不需要重新启动应用。

    对于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,<%System.out.print(this.getClass().getClassLoader());%>.

    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中的方法实现,将在不同时间看到不同的输出结果,关于技术细节也没什么好讲的了,相信大家都能明白。

     

     
     
     
     

    转载于:https://www.cnblogs.com/developer-ios/p/5410764.html

    展开全文
  • Tomcat热部署机制 对于Java应用程序来说,热部署就是在运行时更新Java类文件。在基于Java的应用服务器实现热部署的过程中,类装入器扮演着重要的角色。大多数基于Java的应用服务器,包括EJB服务器和Servlet容器,都...

    Tomcat热部署机制

    对于Java应用程序来说,热部署就是在运行时更新Java类文件。在基于Java的应用服务器实现热部署的过程中,类装入器扮演着重要的角色。大多数基于Java的应用服务器,包括EJB服务器和Servlet容器,都支持热部署。类装入器不能重新装入一个已经装入的类,但只要使用一个新的类装入器实例,就可以将类再次装入一个正在运行的应用程序。

    我们知道,现在大多数的web服务器都支持热部署,而对于热部署的实现机制,网上讲的却不够完善,下面我们就Tomcat的热部署实现机制,讲解一下它是如何实现的:

    Tomcat的容器实现热部署使用了两种机制:

    Classloader重写,通过自定义classloader加载相应的jsp编译后的class到JVM中。 通过动态修改内存中的字节码,将修改过的class再次装载到JVM中。

    Classloader实现jsp的重新加载

    Tomcat通过org.apache.jasper.servlet.JasperLoader实现了对jsp的加载,下面做个测试: 1. 新建一个web工程,并编写一个jsp页面,在jsp页面中输出该页面的classloader,<%System.out.print(this.getClass().getClassLoader());%>.

    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<String>  clsnames=new TreeSet<String>();
     
        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中的方法实现,将在不同时间看到不同的输出结果,关于技术细节也没什么好讲的了,相信大家都能明白。

    Tomcat 热部署配置

     

    复制代码
    <Host appBase="webapps" autoDeploy="true" name="localhost" unpackWARs="true" xmlNamespaceAware="false" xmlValidation="false">  
        <Context docBase="CPCWeb" path="/CPCWeb" reloadable="true" source="org.<span class="wp_keywordlink"><a href="http://res.importnew.com/eclipse" title="Eclipse ImportNew主页" target="_blank">Eclipse</a></span>.jst.j2ee.server:CPCWeb"/>
    </Host>
    
    
    
    复制代码

    autoDeploy=”true” — 自动部署 reloadable=”true” — 自动加载

     



    转载自:http://www.cnblogs.com/freemanabc/p/5618180.html

    展开全文
  • Tomcat热部署机制 对于Java应用程序来说,热部署就是在运行时更新Java类文件。在基于Java的应用服务器实现热部署的过程中,类装入器扮演着重要的角色。大多数基于Java的应用服务器,包括EJB服务器和Servlet容器,...

    Tomcat热部署机制

    对于Java应用程序来说,热部署就是在运行时更新Java类文件。在基于Java的应用服务器实现热部署的过程中,类装入器扮演着重要的角色。大多数基于Java的应用服务器,包括EJB服务器和Servlet容器,都支持热部署。类装入器不能重新装入一个已经装入的类,但只要使用一个新的类装入器实例,就可以将类再次装入一个正在运行的应用程序。

    我们知道,现在大多数的web服务器都支持热部署,而对于热部署的实现机制,网上讲的却不够完善,下面我们就Tomcat的热部署实现机制,讲解一下它是如何实现的:

    Tomcat的容器实现热部署使用了两种机制:

    Classloader重写,通过自定义classloader加载相应的jsp编译后的class到JVM中。 通过动态修改内存中的字节码,将修改过的class再次装载到JVM中。

    Classloader实现jsp的重新加载

    Tomcat通过org.apache.jasper.servlet.JasperLoader实现了对jsp的加载,下面做个测试: 1. 新建一个web工程,并编写一个jsp页面,在jsp页面中输出该页面的classloader,<%System.out.print(this.getClass().getClassLoader());%>.

    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<String>  clsnames=new TreeSet<String>();
     
        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中的方法实现,将在不同时间看到不同的输出结果,关于技术细节也没什么好讲的了,相信大家都能明白。

    Tomcat 热部署配置

     

    复制代码
    <Host appBase="webapps" autoDeploy="true" name="localhost" unpackWARs="true" xmlNamespaceAware="false" xmlValidation="false">  
        <Context docBase="CPCWeb" path="/CPCWeb" reloadable="true" source="org.<span class="wp_keywordlink"><a href="http://res.importnew.com/eclipse" title="Eclipse ImportNew主页" target="_blank">Eclipse</a></span>.jst.j2ee.server:CPCWeb"/>
    </Host>
    
    
    复制代码

    autoDeploy=”true” — 自动部署 reloadable=”true” — 自动加载

    展开全文
  • 首先要说明几个概念,不要混用,热部署,热加载; 热部署:就是已经运行了项目,更改...热加载:是基于字节码进行更改的,不释放内存,热加载也可以叫热更新。在运行时重新加载class; 一、Arthas热更新步骤 arth...
  • 前面分享了如何用mysql热更新文档,现在这篇用tomcat http的方式更新。 原理分析 上次说到他会有一个定时任务刷新,这个定时任务也会扫描http的方式拉取远程的词典,对源码做了以下分析: 监控流程: ①向词库...
  •  这篇文章主要是分析Tomcat中关于部署和JSP更新替换的原理,在此之前先介绍class的替换和class的卸载的原理。一 class的替换 ClassLoader中重要的方法 loadClass    ClassLoader.loadClass(...) 是...
  •  这篇文章主要是分析Tomcat中关于部署和JSP更新替换的原理,在此之前先介绍class的替换和class的卸载的原理。一 class的替换ClassLoader中重要的方法loadClass  ClassLoader.loadClass(...) 是ClassLoader...

空空如也

空空如也

1 2 3
收藏数 48
精华内容 19
关键字:

tomcat热更新原理