Tomcat 热部署的实现原理详解

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 –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” — 自动加载

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

(0)

相关推荐

  • Tomcat实现热部署

    热部署概念 热部署是指在你对JSP或JAVA类进行了修改在不重启WEB服务器前提下能让修改生效,配置文件的修改除外 热部署好处 每次打增量包的时候就不用重新启动tomcat了 实现方式 在tomcat\conf\server.xml中的<host></host>内部添加<context/>标签 <!-- 实现tomcat热部署和自定义ContextPath--> <Context docBase="myPrj " path=&quo

  • Tomcat 热部署的实现原理详解

    Tomcat热部署机制 对于Java应用程序来说,热部署就是在运行时更新Java类文件.在基于Java的应用服务器实现热部署的过程中,类装入器扮演着重要的角色.大多数基于Java的应用服务器,包括EJB服务器和Servlet容器,都支持热部署.类装入器不能重新装入一个已经装入的类,但只要使用一个新的类装入器实例,就可以将类再次装入一个正在运行的应用程序. 我们知道,现在大多数的web服务器都支持热部署,而对于热部署的实现机制,网上讲的却不够完善,下面我们就Tomcat的热部署实现机制,讲解一下它

  • springBoot 插件工具热部署 Devtools的步骤详解

    第一步添加jar包: <!-- 这个依赖是热部署的(devtools)--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <scope>runtime</scope> <optional>true</optional> <

  • Java的Tomcat和Servlet的运行原理详解

    目录 一.客户端(浏览器)与服务器之间的交互 二.Tomcat启动过程 三.Tomcat处理请求过程 四.Servlet的service方法 总结 在从前的时候,想要运行一个Java代码,就必须要实现main方法,这是程序的入口,通过初识 Servlet 就会发现没有实现 main 方法,程序就被成功调用,并且还能够在浏览器除看见想要看见的结果,这是为什么呢? 事实就是,main 方法在Tomcat 中,Servlet 程序是配合Tomcat上运行的,Tomcat 就有了main 方法,就会拖着

  • 详解tomcat热部署和热加载的方法

    详解tomcat热部署和热加载的方法 我在项目开发过程中,经常要改动Java/JSP 文件,但是又不想从新启动服务器(服务器从新启动花时间),想直接获得(debug)结果.有两种方式热部署 和热加载: 1.热加载:在server.xml -> context 属性中 设置 reloadable="true" <Context docBase="xxx" path="/xxx" reloadable="true"/&

  • java进行远程部署与调试及原理详解

    这篇文章主要介绍了java进行远程部署与调试及原理详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 远程调试,特别是当你在本地开发的时候,你需要调试服务器上的程序时,远程调试就显得非常有用. JAVA 支持调试功能,本身提供了一个简单的调试工具JDB,支持设置断点及线程级的调试同时,不同的JVM通过接口的协议联系,本地的Java文件在远程JVM建立联系和通信.此篇是Intellij IDEA远程调试的教程汇总和原理解释,知其然而又知其所以然.

  • SpringBoot内置tomcat启动原理详解

    前言 不得不说SpringBoot的开发者是在为大众程序猿谋福利,把大家都惯成了懒汉,xml不配置了,连tomcat也懒的配置了,典型的一键启动系统,那么tomcat在springboot是怎么启动的呢? 内置tomcat 开发阶段对我们来说使用内置的tomcat是非常够用了,当然也可以使用jetty. <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-bo

  • idea打包成war包部署到tomcat及访问路径问题(图文详解)

    idea将web项目打包成war最重要的是配置atrificats. 首先打开file -> project structure 创建之后,output directory即为输出war包的路径.Name可以随意,之后点击绿色+,打开directory content 选择webapp目录,记得勾选include in project build 点击ok后,新建一个配置 选择artificats 这样就算配置完成,点击run执行之后,就到前面的output directory设置的文件夹里找到

  • 基于Tomcat安全配置与性能优化详解

    Tomcat 是 Apache软件基金会下的一个免费.开源的WEB应用服务器,它可以运行在 Linux 和 Windows 等多个平台上,由于其性能稳定.扩展性好.免费等特点深受广大用户喜爱.目前,很多互联网应用和企业应用都部署在 Tomcat 服务器上,比如我们公司,哈. 之前我们 tomcat 都采用的是默认的配置,因此在安全方面还是有所隐患的.上周对测试环境的所有服务器的tomcat都做了安全优化,其间也粗略做了一些性能优化,这里就简单记录分享下! 一.版本安全 升级当前的tomcat版本

  • keepalived对nginx进行高可用搭建及原理详解

    目录 一.Keepalived介绍 二.Keepalived的应用场景 三.Keepalived的工作原理 1 VRRP协议 2 核心组件 3 分层工作 4 工作状态 四.Keepalived使用 1 配置介绍 2 使用keepalived对nginx进行高可用搭建 2.1 环境准备 2.2 nginx软件安装和配置 2.3 Keepalived软件安装 2.4 监听存活脚本 2.5 最终配置文件 2.6 启动主从的Keepalived 2.7 查看VIP是否启动 2.8 测试 五.需要注意的问

  • 基于tomcat的连接数与线程池详解

    前言 在使用tomcat时,经常会遇到连接数.线程数之类的配置问题,要真正理解这些概念,必须先了解Tomcat的连接器(Connector). 在前面的文章 详解Tomcat配置文件server.xml 中写到过:Connector的主要功能,是接收连接请求,创建Request和Response对象用于和请求端交换数据:然后分配线程让Engine(也就是Servlet容器)来处理这个请求,并把产生的Request和Response对象传给Engine.当Engine处理完请求后,也会通过Conn

随机推荐