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

猜你喜欢

转载自yys19781104.iteye.com/blog/2256027