JSP热部署的实现原理[通俗易懂]

JSP热部署的实现原理[通俗易懂]一.             概述名词解释:所谓热部署,就是在应用正在运行的时候升级软件,却不需要重新启动应用。对于Java应用程序来说,热部署就是在运行时更新Java类文件。在基于Java的应用服务器实现热部署的过程中,类装入器扮演着重要的角色。大多数基于Java的应用服务器,包括EJB服务器和Servlet容器,都支持热部署。类装入器不能重新装入一个已经装入的类,但只要使用一个

大家好,又见面了,我是你们的朋友全栈君。

一.             概述

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

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

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

原文地址:https://my.oschina.net/xianggao/blog/364068

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/143559.html原文链接:https://javaforall.net

(0)
全栈程序员-站长的头像全栈程序员-站长


相关推荐

  • 小鹤双拼入门和小鹤音形的搜狗输入法配置方法[通俗易懂]

    小鹤双拼入门和小鹤音形的搜狗输入法配置方法[通俗易懂]记忆口诀秋闱皒软月,韵书迟落撇。阿宋穷带份羹,航岸快赢良况。邹霞夸草追鱼滨,鸟眠小鹤双拼。iueieuanue,unui_oie图月,书痴aongaieneng,anganing_ang建安,快赢,良况ou_aaouiin,iaoian瞎夸,追鱼记忆口诀①(官方版):QiuWeiRuanT_ue_veYunU_shuI_chiSong_iongDaiFenGengHangJ_an秋闱软月云梳翅,松拥

    2022年6月29日
    201
  • 简单线性回归-最小二乘法推导过程

    简单线性回归-最小二乘法推导过程最近学习线性回归,自己推导了一下最小二乘法。 其他参考文章:https://blog.csdn.net/chasdmeng/article/details/38869941?utm_source=blogxgwz0https://blog.csdn.net/iterate7/article/details/78992015要是你在西安,感兴趣一起学习AIOPS,欢迎加入QQ群…

    2022年5月13日
    38
  • Linux时间戳转换_时间戳转换软件

    Linux时间戳转换_时间戳转换软件在大多数UNIX系统中,当前时间存储为自特定时刻以来经过的时间以简化,将时间保持为长整数。所有UNIX系统普遍接受的时刻是1970年1月1日凌晨12:00:00。这称为UNIX时间戳,并被所有现代UNIX/Linux系统识别。Linux时间戳date命令例如,如果我们希望找到2022年1月1日的UNIX时间戳,我们可以使用date命令。date尝试将字符串解析为格式化的日期和时间(或者,如果未指定时间戳,则假定时间为00:00AM),然后打印出给定

    2022年10月2日
    0
  • 配置文件的选择_Cisco备份配置文件

    配置文件的选择_Cisco备份配置文件不同模式打开文件的完全列表:模式描述r以只读方式打开文件。文件的指针将会放在文件的开头。这是默认模式。rb以二进制格式打开一个文件用于只读。文件指针将会放在文件的开头。这是默认模式。r+

    2022年8月4日
    4
  • django详解_java cookie

    django详解_java cookie前言cookie:在网站中,http请求是无状态的。也就是说即使第一次和服务器连接后并且登录成功后,第二次请求服务器依然不能知道当前请求是哪个用户。cookie的出现就是为了解决这个问题,第一次登录

    2022年8月7日
    4
  • 最炫民族风原创_manjaro deepin

    最炫民族风原创_manjaro deepin最近有一首最具有民族风的歌曲很流行,它就是《最炫民族风》。中华民族的儿女都有这种情节,对于民族风的事物都非常感兴趣。与之相关,有一款最具民族风的Linux桌面操作系统也正在引起大家的关注。2012年7月17日,国人最喜爱的一款Linux发行版——DeepinLinux12.06正式发布了。LinuxDeepin也就是我们所说的深度Linux,这是一款基于Ubuntu11.04的发行

    2022年9月15日
    0

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号