名词解释:所谓热部署,就是在应用正在运行的时候升级软件
,却不需要重新启动应用。
对于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;
import
java.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中的方法实现,将在不同时间看到不同的输出结果,关于技术
细节也没什么好讲的了,相信大家都能明白。
分享到:
相关推荐
这是一个支持热部署的软件,在Java项目中修改Java代码不需在重启服务器。一个很实用的Java编程软件
java热部署
有没有在为修改java代码 反复重启tomcat而烦恼 下载热部署一切不成问题。内容:1.热部署 2.分给myeclipse 1g内存
springboot+mybatis+thymeleaf 热部署 java 热部署页面访问
实现Java热部署的几种解决方案.docx
热部署工具
在 Java 开发领域,热部署一直是一个难以解决的问题,目前的 Java 虚拟机只能实现方法体的修改热部署,对于整个类的结构修改,仍然需要重启虚拟机,对类重新加载才能完成更新操作。对于某些大型的应用来说,每次的...
美团IDEA插件实现Java应用的热部署实践
JRebel eclipse 热部署
Java 自定义ClassLoader 实现类的热替换核心代码
关于java热部署知识.doc
Java 热部署操作笔记。Spring Tool Suite.网上能看到的资源多事mvn 的,再这个文档里面,很详细的介绍了.yml元素的写法。和注意事项。
JAVA热部署安装及配置,实现IDEA无需频繁重启服务。通过自己手动配置,绝对能用!
myeclipse 插件热部署 jrebel6.5 亲测可用,资源下载到磁盘,然后打开myeclipse 路径为:Windows ---> Perferences --> MyEclipse -->Servers -->Tomcat --> Tomcat 7.x -->JDK 配置jvm参数: -...
Erlang 中的Module级别热部署,可以看看
对于Java应用程序来说,热部署就是在运行时更新Java类文件。在基于Java的应用服务器实现热部署的过程中,类装入器扮演着重要的角色。大多数基于Java的应用服务器,包括EJB服务器和Servlet容器,都支持热部署。,需要...
热部署插件、支持32位、64位系统,解压后,里面有文件说明,参考说明配置。开发必备,节省时间。注意:请根据系统来选择配置文件。此说明基于:windows10 64位 、jdk7
netbean6.7对java文件热部署工具有问题,你可以使用这个文件,然后再glassfish上作相关的配置之后轻松实现热部署
jrebel 热部署工具 破解版