文档章节

Tomcat热部署的实现原理

浮躁的码农
 浮躁的码农
发布于 2015/07/17 23:04
字数 1586
阅读 44
收藏 0

Tomcat热部署机制

对于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字节码:

以下是一个简单的热部署代理实现类(代码比较粗糙,也没什么判断):

import java.lang.instrument.ClassFileTransformer; 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);
    }
}
import java.lang.instrument.ClassFileTransformer; 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;
    }
}
import java.lang.instrument.ClassDefinition; 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.eclipse.jst.j2ee.server:CPCWeb"/>
</Host>

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

本文转载自:

浮躁的码农

浮躁的码农

粉丝 71
博文 856
码字总数 156434
作品 0
松江
程序员
私信 提问
Tomcat中jsp热部署实现原理

我们知道在开发工程的时候jsp文件是即修改即生效的,由于比较好奇就研究了一下tomcat对于jsp热部署的实现原理,总结沉淀一下吧。 Tomcat jsp热部署的实现原理大体是这样的,每个JSP页面从上次...

heroShane
2014/02/11
0
0
JAVAEE——宜立方商城14:系统部署

1. 学习计划 1、系统部署 2. 项目部署 2.1. 项目架构讲解 2.2. 网络拓扑图 2.3. 系统部署 2.3.1. 部署分析 e3-manager e3-manager-web e3-portal-web e3-content e3-search e3-search-web e...

kent鹏
2018/08/09
0
0
关于Tomcat热部署配置问题?

关于Tomcat 热部署问题与开发者模式配置问题 tomcat怎样实现热部署? Tomcat怎么配置开发者模式?

N先生
2017/08/16
141
3
Eclipse Maven Tomcat的利用配置

1.事先准备 m2eclipse插件中其实内嵌了Maven,但个人更偏向于用独立的Maven(可以自由选择版本啦,偶尔可以在命令行中跑Maven命令啦)。 下载Apache Maven:http://maven.apache.org/,关于环...

躺着的S
2013/08/03
0
4
Tomcat Maven Plugin

maven-tomcat-plugin让maven与tomcat配合得很好。它可以把应用部署到Tomcat服务器,也可以把 tomcat作为内嵌服务器启动,就像jetty一样。 使用JPDA启动tomcat的远程调试功能。这样就能与ecl...

匿名
2011/09/07
5.2K
0

没有更多内容

加载失败,请刷新页面

加载更多

跨域的理解,以及解决方案!

/*什么是跨域? * 跨域的主要原因是浏览器的同源策略。 * =>>所谓的同源策略就是A网页设置的 Cookie,B网页不能打开,除非这两个网页"同源"。 * 什么是同源? * =>>同源就是协议相同、域名相同...

流年那么伤
7分钟前
1
0
Nginx配置try_fiels,php无法获取$_GET参数

平时开发都是用LNMP,新安装的虚拟机在配置nginx的rewrite的时候使用try_files命令。但是在写的时候配置成“try_files $uri $uri/ /index.php?q=args;”, 在PHP的web程序中,打印$_GET为空。...

叫我哀木涕
8分钟前
0
0
【原创】Microsoft Edge可以用localhost访问但无法用IP访问

Microsoft Edge可以用localhost和127.0.0.1访问但无法用本机IP访问, chrome ie都可以推测是edge的问题,网络是专用网络,防火墙也关了: 在edge里 按F12 以在控制台里看到这句 CONSOLE21301...

shzwork
8分钟前
0
0
Python利用数学方程式画4种不一样的心型图案

前言 下面这四个心型图案,是通过科学地计算,根据数学方程式生成的,虽然做的不是特别完美,但是基本的还是能实现的 第一个心型 结果图 第二心型 结果图 学习从来不是一个人的事情,要有个相互监...

A_裙232550246
9分钟前
0
0
微信带场景参数的二维码生成与使用?

微信公众号推广时,用户通过扫二维码关注公众号,需要统计用户是通过谁的二维码进行关注。 在用户扫码关注公众号时,二维码带上推广者的参数,在关注公众号后,获取到该推广者的参数。 目前有...

wxgzhgncj
9分钟前
0
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部