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” — 自动加载
感谢阅读,希望能帮助到大家,谢谢大家对本站的支持!
《魔兽世界》大逃杀!60人新游玩模式《强袭风暴》3月21日上线
暴雪近日发布了《魔兽世界》10.2.6 更新内容,新游玩模式《强袭风暴》即将于3月21 日在亚服上线,届时玩家将前往阿拉希高地展开一场 60 人大逃杀对战。
艾泽拉斯的冒险者已经征服了艾泽拉斯的大地及遥远的彼岸。他们在对抗世界上最致命的敌人时展现出过人的手腕,并且成功阻止终结宇宙等级的威胁。当他们在为即将于《魔兽世界》资料片《地心之战》中来袭的萨拉塔斯势力做战斗准备时,他们还需要在熟悉的阿拉希高地面对一个全新的敌人──那就是彼此。在《巨龙崛起》10.2.6 更新的《强袭风暴》中,玩家将会进入一个全新的海盗主题大逃杀式限时活动,其中包含极高的风险和史诗级的奖励。
《强袭风暴》不是普通的战场,作为一个独立于主游戏之外的活动,玩家可以用大逃杀的风格来体验《魔兽世界》,不分职业、不分装备(除了你在赛局中捡到的),光是技巧和战略的强弱之分就能决定出谁才是能坚持到最后的赢家。本次活动将会开放单人和双人模式,玩家在加入海盗主题的预赛大厅区域前,可以从强袭风暴角色画面新增好友。游玩游戏将可以累计名望轨迹,《巨龙崛起》和《魔兽世界:巫妖王之怒 经典版》的玩家都可以获得奖励。
更新动态
- 凤飞飞《我们的主题曲》飞跃制作[正版原抓WAV+CUE]
- 刘嘉亮《亮情歌2》[WAV+CUE][1G]
- 红馆40·谭咏麟《歌者恋歌浓情30年演唱会》3CD[低速原抓WAV+CUE][1.8G]
- 刘纬武《睡眠宝宝竖琴童谣 吉卜力工作室 白噪音安抚》[320K/MP3][193.25MB]
- 【轻音乐】曼托凡尼乐团《精选辑》2CD.1998[FLAC+CUE整轨]
- 邝美云《心中有爱》1989年香港DMIJP版1MTO东芝首版[WAV+CUE]
- 群星《情叹-发烧女声DSD》天籁女声发烧碟[WAV+CUE]
- 刘纬武《睡眠宝宝竖琴童谣 吉卜力工作室 白噪音安抚》[FLAC/分轨][748.03MB]
- 理想混蛋《Origin Sessions》[320K/MP3][37.47MB]
- 公馆青少年《我其实一点都不酷》[320K/MP3][78.78MB]
- 群星《情叹-发烧男声DSD》最值得珍藏的完美男声[WAV+CUE]
- 群星《国韵飘香·贵妃醉酒HQCD黑胶王》2CD[WAV]
- 卫兰《DAUGHTER》【低速原抓WAV+CUE】
- 公馆青少年《我其实一点都不酷》[FLAC/分轨][398.22MB]
- ZWEI《迟暮的花 (Explicit)》[320K/MP3][57.16MB]