[转帖]tomcat热部署的实现原理_Tomcat, WebLogic及J2EE讨论区_Weblogic技术|Tuxedo技术|中间件技术|Oracle论坛|JAVA论坛|Linux/Unix技术|hadoop论坛_联动北方技术论坛  
网站首页 | 关于我们 | 服务中心 | 经验交流 | 公司荣誉 | 成功案例 | 合作伙伴 | 联系我们 |
联动北方-国内领先的云技术服务提供商
»  游客             当前位置:  论坛首页 »  自由讨论区 »  Tomcat, WebLogic及J2EE讨论区 »
总帖数
2
每页帖数
101/1页1
返回列表
0
发起投票  发起投票 发新帖子
查看: 4558 | 回复: 1   主题: [转帖]tomcat热部署的实现原理        下一篇 
    本主题由 koei123 于 2015-2-6 5:24:56 移动
white
注册用户
等级:少校
经验:1327
发帖:305
精华:0
注册:2011-7-21
状态:离线
发送短消息息给white 加好友    发送短消息息给white 发消息
发表于: IP:您无权察看 2011-8-2 10:09:56 | [全部帖] [楼主帖] 楼主

一.             概述

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

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

该贴由koei123转至本版2015-2-6 5:24:56



赞(0)    操作        顶端 
匿名用户
发表于: IP:您无权察看 2011-8-25 13:54:19 | [全部帖] [楼主帖] 2  楼

北京联动北方科技有限公司北京联动北方科技有限公司北京联动北方科技有限公司


赞(0)    操作        顶端 
总帖数
2
每页帖数
101/1页1
返回列表
发新帖子
请输入验证码: 点击刷新验证码
您需要登录后才可以回帖 登录 | 注册
技术讨论