侯体宗的博客
  • 首页
  • Hyperf版
  • beego仿版
  • 人生(杂谈)
  • 技术
  • 关于我
  • 更多分类
    • 文件下载
    • 文字修仙
    • 中国象棋ai
    • 群聊
    • 九宫格抽奖
    • 拼图
    • 消消乐
    • 相册

Tomcat 热部署的实现原理详解

技术  /  管理员 发布于 7年前   194

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,.

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 Cjavaagent: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” ― 自动加载

感谢阅读,希望能帮助到大家,谢谢大家对本站的支持!


  • 上一条:
    Tomcat怎么实现异步Servlet
    下一条:
    Eclipse添加新server时无法选择Tomcat7的解决方法
  • 昵称:

    邮箱:

    0条评论 (评论内容有缓存机制,请悉知!)
    最新最热
    • 分类目录
    • 人生(杂谈)
    • 技术
    • linux
    • Java
    • php
    • 框架(架构)
    • 前端
    • ThinkPHP
    • 数据库
    • 微信(小程序)
    • Laravel
    • Redis
    • Docker
    • Go
    • swoole
    • Windows
    • Python
    • 苹果(mac/ios)
    • 相关文章
    • gmail发邮件报错:534 5.7.9 Application-specific password required...解决方案(0个评论)
    • 2024.07.09日OpenAI将终止对中国等国家和地区API服务(0个评论)
    • 2024/6/9最新免费公益节点SSR/V2ray/Shadowrocket/Clash节点分享|科学上网|免费梯子(1个评论)
    • 国外服务器实现api.openai.com反代nginx配置(0个评论)
    • 2024/4/28最新免费公益节点SSR/V2ray/Shadowrocket/Clash节点分享|科学上网|免费梯子(1个评论)
    • 近期文章
    • 在go中实现一个常用的先进先出的缓存淘汰算法示例代码(0个评论)
    • 在go+gin中使用"github.com/skip2/go-qrcode"实现url转二维码功能(0个评论)
    • 在go语言中使用api.geonames.org接口实现根据国际邮政编码获取地址信息功能(1个评论)
    • 在go语言中使用github.com/signintech/gopdf实现生成pdf分页文件功能(0个评论)
    • gmail发邮件报错:534 5.7.9 Application-specific password required...解决方案(0个评论)
    • 欧盟关于强迫劳动的规定的官方举报渠道及官方举报网站(0个评论)
    • 在go语言中使用github.com/signintech/gopdf实现生成pdf文件功能(0个评论)
    • Laravel从Accel获得5700万美元A轮融资(0个评论)
    • 在go + gin中gorm实现指定搜索/区间搜索分页列表功能接口实例(0个评论)
    • 在go语言中实现IP/CIDR的ip和netmask互转及IP段形式互转及ip是否存在IP/CIDR(0个评论)
    • 近期评论
    • 122 在

      学历:一种延缓就业设计,生活需求下的权衡之选中评论 工作几年后,报名考研了,到现在还没认真学习备考,迷茫中。作为一名北漂互联网打工人..
    • 123 在

      Clash for Windows作者删库跑路了,github已404中评论 按理说只要你在国内,所有的流量进出都在监控范围内,不管你怎么隐藏也没用,想搞你分..
    • 原梓番博客 在

      在Laravel框架中使用模型Model分表最简单的方法中评论 好久好久都没看友情链接申请了,今天刚看,已经添加。..
    • 博主 在

      佛跳墙vpn软件不会用?上不了网?佛跳墙vpn常见问题以及解决办法中评论 @1111老铁这个不行了,可以看看近期评论的其他文章..
    • 1111 在

      佛跳墙vpn软件不会用?上不了网?佛跳墙vpn常见问题以及解决办法中评论 网站不能打开,博主百忙中能否发个APP下载链接,佛跳墙或极光..
    • 2016-10
    • 2016-11
    • 2017-07
    • 2017-08
    • 2017-09
    • 2018-01
    • 2018-07
    • 2018-08
    • 2018-09
    • 2018-12
    • 2019-01
    • 2019-02
    • 2019-03
    • 2019-04
    • 2019-05
    • 2019-06
    • 2019-07
    • 2019-08
    • 2019-09
    • 2019-10
    • 2019-11
    • 2019-12
    • 2020-01
    • 2020-03
    • 2020-04
    • 2020-05
    • 2020-06
    • 2020-07
    • 2020-08
    • 2020-09
    • 2020-10
    • 2020-11
    • 2021-04
    • 2021-05
    • 2021-06
    • 2021-07
    • 2021-08
    • 2021-09
    • 2021-10
    • 2021-12
    • 2022-01
    • 2022-02
    • 2022-03
    • 2022-04
    • 2022-05
    • 2022-06
    • 2022-07
    • 2022-08
    • 2022-09
    • 2022-10
    • 2022-11
    • 2022-12
    • 2023-01
    • 2023-02
    • 2023-03
    • 2023-04
    • 2023-05
    • 2023-06
    • 2023-07
    • 2023-08
    • 2023-09
    • 2023-10
    • 2023-12
    • 2024-02
    • 2024-04
    • 2024-05
    • 2024-06
    • 2025-02
    Top

    Copyright·© 2019 侯体宗版权所有· 粤ICP备20027696号 PHP交流群

    侯体宗的博客