Java项目热部署

类的热部署、卸载和替换

一、Java中classLoader的双亲委托机制(默认是system classLoader,也称为AppClassLoader,其双亲指的是Extend和BootTrap classLoader):
Java中ClassLoader的加载采用了双亲委托机制,采用双亲委托机制加载类的时候采用如下的几个步骤:
当前ClassLoader首先从自己已经加载的类中查询是否此类已经加载,如果已经加载则直接返回原来已经加载的类。
每个类加载器都有自己的加载缓存,当一个类被加载了以后就会放入缓存,等下次加载的时候就可以直接返回了。
当前classLoader的缓存中没有找到被加载的类的时候,委托父类加载器去加载,父类加载器采用同样的策略,首先查看自己的缓存,然后委托父类的父类去加载,一直到bootstrp ClassLoader.
当所有的父类加载器都没有加载的时候,再由当前的类加载器加载,并将其放入它自己的缓存中,以便下次有加载请求的时候直接返回。
还有一个特例是contextClassLoader,多用于框架中,相当于是前三种的后门。

二、 class的热替换(主要通过覆写ClassLoader中的loadClass方法,加入修改时间的判断,通过类的反射调用新类的方法实现热替换)
ClassLoader中重要的方法
loadClass
ClassLoader.loadClass(...) 是ClassLoader的入口点。当一个类没有指明用什么加载器加载的时候,JVM默认采用AppClassLoader加载器加载没有加载过的class,调用的方法的入口就是loadClass(...)。如果一个class被自定义的ClassLoader加载,那么JVM也会调用这个自定义的ClassLoader.loadClass(...)方法来加载class内部引用的一些别的class文件。重载这个方法,能实现自定义加载class的方式,抛弃双亲委托机制,但是即使不采用双亲委托机制,比如java.lang包中的相关类还是不能自定义一个同名的类来代替,主要因为JVM解析、验证class的时候,会进行相关判断。

defineClass
系统自带的ClassLoader,默认加载程序的是AppClassLoader,ClassLoader加载一个class,最终调用的是defineClass(...)方法,这时候就在想是否可以重复调用defineClass(...)方法加载同一个类(或者修改过),最后发现调用多次的话会有相关错误:
...
java.lang.LinkageError
attempted duplicate class definition
...
所以一个class被一个ClassLoader实例加载过的话,就不能再被这个ClassLoader实例再次加载(这里的加载指的是,调用了defileClass(...)放方法,重新加载字节码、解析、验证。)。而系统默认的AppClassLoader加载器,他们内部会缓存加载过的class,重新加载的话,就直接取缓存。所与对于热加载的话,只能重新创建一个ClassLoader,然后再去加载已经被加载过的class文件。

1. 测试时要修改内容的类对象
package testjvm.testclassloader;

public class Hot {
public void hot() {
System.out.println(" version 1 : " + this.getClass().getClassLoader());
}
}
2. 覆写classLoad的UrlClassLoader类
package testjvm.testclassloader;
import java.io.File;
import java.io.FileNotFoundException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.HashMap;
import java.util.Map;
/**
* 主要功能是重新加载更改过的.class文件,达到热替换的作用 1
*/
public class HotSwapURLClassLoader extends URLClassLoader {
// 缓存加载class文件的最后最新修改时间
public static Map<String, Long> cacheLastModifyTimeMap = new HashMap<String, Long>();
// 工程class类所在的路径
public static String projectClassPath = "D:/workspace/JavaFunction/bin/";
// 所有的测试的类都在同一个包下
public static String packagePath = "testjvm/testclassloader/";

private static HotSwapURLClassLoader hcl = new HotSwapURLClassLoader();

public HotSwapURLClassLoader() {
// 设置ClassLoader加载的路径
super(getMyURLs());
}

public static HotSwapURLClassLoader getClassLoader() {
return hcl;
}

private static URL[] getMyURLs() {
URL url = null;
try {
url = new File(projectClassPath).toURI().toURL();
} catch (MalformedURLException e) {
e.printStackTrace();
}
return new URL[] { url };
}
/**
* 重写loadClass,不采用双亲委托机制("java."开头的类还是会由系统默认ClassLoader加载)
*/
@Override
public Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
Class clazz = null;
// 查看HotSwapURLClassLoader实例缓存下,是否已经加载过class
// 不同的HotSwapURLClassLoader实例是不共享缓存的
clazz = findLoadedClass(name);
if (clazz != null) {
if (resolve) {
resolveClass(clazz);
}
// 如果class类被修改过,则重新加载
if (isModify(name)) {
hcl = new HotSwapURLClassLoader();
clazz = customLoad(name, hcl);
}
return (clazz);
}
// 如果类的包名为"java."开始,则有系统默认加载器AppClassLoader加载
// java.表示这是JDK原始的类,比如java.lang.String
// 原始的loadClass包为: java.net;
if (name.startsWith("java.")) {
try {
// 得到系统默认的加载cl,即AppClassLoader
ClassLoader system = ClassLoader.getSystemClassLoader();
clazz = system.loadClass(name);
if (clazz != null) {
if (resolve)
resolveClass(clazz);
return (clazz);
}
} catch (ClassNotFoundException e) {
// Ignore
}
}

return customLoad(name, this);
}
public Class load(String name) throws Exception {
return loadClass(name);
}

/**
* 自定义加载
*
* @param name
* @param cl
* @return
* @throws ClassNotFoundException
*/
public Class customLoad(String name, ClassLoader cl) throws ClassNotFoundException {
return customLoad(name, false, cl);
}
/**
* 自定义加载
*
* @param name
* @param resolve
* @return
* @throws ClassNotFoundException
*/
public Class customLoad(String name, boolean resolve, ClassLoader cl) throws ClassNotFoundException {
// findClass()调用的是URLClassLoader里面重载了ClassLoader的findClass()方法
Class clazz = ((HotSwapURLClassLoader) cl).findClass(name);
if (resolve)
((HotSwapURLClassLoader) cl).resolveClass(clazz);
// 缓存加载class文件的最后修改时间
long lastModifyTime = getClassLastModifyTime(name);
cacheLastModifyTimeMap.put(name, lastModifyTime);
return clazz;
}
public Class<?> loadClass(String name) throws ClassNotFoundException {
return loadClass(name, false);
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
// TODO Auto-generated method stub
return super.findClass(name);
}

/**
* @param name
* @return .class文件最新的修改时间
*/
private long getClassLastModifyTime(String name) {
String path = getClassCompletePath(name);
File file = new File(path);
if (!file.exists()) {
throw new RuntimeException(new FileNotFoundException(name));
}
return file.lastModified();
}

/**
* 判断这个文件跟上次比是否修改过
*
* @param name
* @return
*/
private boolean isModify(String name) {
long lastmodify = getClassLastModifyTime(name);
long previousModifyTime = cacheLastModifyTimeMap.get(name);
if (lastmodify > previousModifyTime) {
return true;
}
return false;
}

/**
* @param name
* @return .class文件的完整路径 (e.g. E:/A.class)
*/
private String getClassCompletePath(String name) {
String simpleName = name.substring(name.lastIndexOf(".") + 1);
return projectClassPath + packagePath + simpleName + ".class";
}
}

3. 测试类:
package testjvm.testclassloader;
import java.lang.reflect.Method;
public class TestHotSwap {
public static void main(String[] args) throws Exception {
// 开启线程,如果class文件有修改,就热替换
Thread t = new Thread(new MonitorHotSwap());
t.start();
}
}

class MonitorHotSwap implements Runnable {
// Hot就是用于修改,用来测试热加载
private String className = "testjvm.testclassloader.Hot";
private Class hotClazz = null;
private HotSwapURLClassLoader hotSwapCL = null;
@Override
public void run() {
try {
while (true) {
initLoad();
Object hot = hotClazz.newInstance();
Method m = hotClazz.getMethod("hot");
// 打印出相关信息
m.invoke(hot, null);
// 每隔10秒重新加载一次
Thread.sleep(10000);
}
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 加载class
*/
void initLoad() throws Exception {
hotSwapCL = HotSwapURLClassLoader.getClassLoader();
// 如果Hot类被修改了,那么会重新加载,hotClass也会返回新的
hotClazz = hotSwapCL.loadClass(className);
}
}

测试运行时修改“version 1”到“version 2”,会看到动态输出的修改内容变成了“version 2”。

三、jar包的热部署


四、Tomcat下热部署
工具:JavaRebel


类的热部署:
http://www.xuehuile.com/blog/abc337f9515449959ab6cebc19d70361.html
http://my.oschina.net/zhaoxj/blog/140266
http://www.jiancool.com/article/84343280279/
http://wenku.baidu.com/view/1c5559f4ba0d4a7302763ab8.html
http://www.blogjava.net/heavensay/archive/2015/11/06/389685.html 类的热部署和卸载
http://blog.csdn.net/mycomputerxiaomei/article/details/24470465 动态加载jar文件
http://www.cnblogs.com/xwdreamer/archive/2011/12/05/2296918.html
http://huangqiqing123.iteye.com/blog/1461624
classLoader:
http://my.oschina.net/aminqiao/blog/262601
http://www.cnblogs.com/zhanjindong/p/3952445.html
http://calmness.iteye.com/blog/83978
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值