`

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
分享到:
评论

相关推荐

    支持Java热部署的插件

    这是一个支持热部署的软件,在Java项目中修改Java代码不需在重启服务器。一个很实用的Java编程软件

    myeclipse 插件热部署 jrebel6.5

    Drebel.dirs-------------热部署的项目 -Drebel.spring_plugin-----热部署spring相关jar -Drebel.struts2-plugin-----热部署struts2相关jar -Drebel.hibernate_plugin---热部署hibernate相关jar -Drebel.ibatis-...

    JAVA代码热部署,在线不停服动态更新

    本地debug的时候,可以实时编译并更新代码,线上也可以不停服来动态更新类,即所说的java热部署。  JDK代理的两种方式:  1.premain方式是Java SE5开始提供的代理方式,但其必须在命令行指定代理jar,并且代理类...

    热部署jrebel

    jrebel热部署,修改前端代码,Java代码,xml配文件,properties文件不用重新部署项目和重启服务器。真的好用,本人已验证使用。 jrebel热部署,修改前端代码,Java代码,xml配文件,properties文件不用重新部署项目...

    myeclipse热部署jrebel6.5.0

    Drebel.dirs-------------热部署的项目 -Drebel.spring_plugin-----热部署spring相关jar -Drebel.struts2-plugin-----热部署struts2相关jar -Drebel.hibernate_plugin---热部署hibernate相关jar -Drebel.ibatis-...

    IntelliJ+Maven+Jetty+Jrebel实现web项目java代码更改后热部署

    NULL 博文链接:https://wang-jia-sina-com.iteye.com/blog/1730307

    idea 热部署

    JRebel 是一款热部署插件。当你的 java-web 项目在 tomcat 中 run/debug 的时候 ,修改某一个 java 文件时,tomcat 并不能将此文件的修改 实时编译 并反映到运行的项目中去。JRebel 就可以解决在项目运行状态run/...

    myeclipse热部署

    myeclipse热部署,解压后就可以用,tomcat的JDK添加一段简单的代码就可以了。修改sql,java不需要重启项目了,好东西给大家分享,具体的写在文档里,放压缩包里了

    myplugins-热部署.zip

    myeclipse 热部署插件,myeclipse进行Java开发时,特别是JavaEE开发,对于web项目,调试阶段需频繁重启项目,这样不仅效率低下,而且容易打断开发人员思路,只要myeclipse引入myplugins-热部署插件,即无需频繁重启...

    Tomcat实现热部署

    热部署是指在你对JSP或JAVA类进行了修改在不重启WEB服务器前提下能让修改生效,配置文件的修改除外 热部署好处 每次打增量包的时候就不用重新启动tomcat了 实现方式 在tomcat\conf\server.xml中的&lt;host&gt;&lt;/host&gt;内部...

    Jrebel插件-热部署

    资源为破解版的Jrebel热部署插件,在eclipse中配置好了以后,修改java文件无需再重启项目

    springloaded-1.2.6实现spring热部署,最全的教程

    使用spring-loaded项目,确实可以实现java应用的热部署

    热部署配置包、使用文档

    配置的热部署使用idea等更改了后台java代码即可不用重新发布项目直接刷新浏览器更新,具体运行方式以及配置方式文件中有,百度也有

    IDEA JRebel热部署插件jar包

    IDEA的 JRebel热部署插件, 修改后无需重启项目

    详解tomcat热部署和热加载的方法

    我在项目开发过程中,经常要改动Java/JSP 文件,但是又不想从新启动服务器(服务器从新启动花时间),想直接获得(debug)结果.有两种方式热部署 和热加载:  1.热加载:在server.xml -&gt; context 属性中 设置 ...

    Springboot-Mybatis-Gradle:Gradle 构建SpringBoot项目,使用Mybatis来持久化,集成PageHelper分页插件,实现热部署

    Springboot-Mybatis-GradleGradle 构建SpringBoot项目,使用Mybatis来持久化,集成PageHelper分页插件,实现热部署相关博文:

    java attach 超屌工具

    本资源是用于生产系统无法停机的情况下修复bug或者debug信息,希望各位看官不要用来干坏事…………。里面readme会指导大伙怎么使用这个工具的

    Idea部署tomcat服务实现过程图解

    IDEA2020导入非maven项目并部署tomcat的方法在idea中将创建的java web项目部署到Tomcat中的过程图文详解idea打包成war包部署到tomcat及访问路径问题(图文详解)Idea配置maven-tomcat-plugin插件实现项目部署IDEA导入...

    SpringBoot(powernode)(教学视频+源代码)

    SpringBoot(powernode) 一、第一个SpringBoot项目 ...五、Spring Boot热部署 5.1 什么是热部署 5.2 添加依赖 5.3 配置idea的启动面板 六、Spring Boot的配置文件语法 6.1 首先引入依赖 6.2 创建Weapon类 6.3 pro

    J2EE开发全程实录(JAVA项目开发)

    7.4.4 元数据热部署... 121 7.4.5 元数据部署方式... 121 7.5 元数据引擎实现... 122 7.5.1 根据元数据路径加载 元数据... 122 7.5.2 元数据枚举器... 122 7.5.3 元数据缓存... 125 7.5.4 元数据加载器... 126 7.5.5...

Global site tag (gtag.js) - Google Analytics