热部署系统实现

热部署

是指在不关闭或重启服务的情况下,更新Java类文件或配置文件,实现修改内容生效;通过热部署,可提高开发效率,节省程序打包重启的时间,同时,可实现生产环境中需要不停机或重启的服务的升级。

1.热部署实现原理


对于Java应用程序,热部署就是程序运行时实现Java类文件更新。要实现程序在运行中进行程序更新,就需要让java虚拟机在检测到Java类文件发生变化时,把原来的类文件卸载,并重新加载新的类文件。总的来说,热部署的本质是让jvm重新加载新的class文件。程序运行时,类加载器只会加载一次Java类文件,切不能卸载,这很明显不符合热部署的需要。但是,因为类加载器是可以进行更换的,所以,我们采取的方式是自定义类加载器,在自定义的类加载器中,重写findClass方法,从而实现热部署。

热部署实现方式:

  1. 热部署前,销毁自定义的类加载器;
  2. 更新Java Class文件;
  3. 创建新的ClassLoader去加载更新后的Java Class文件。

2.热部署系统交互

热部署系统实现_第1张图片

ClassLoader 介绍

 热部署功能,主要使用ClassLoader 实现,下面介绍下 ClassLoader 类

BootStrap根类加载器

引导类加载器,它的实现依赖于底层操作系统,是用c编写的,不是由ClassLoader类继承的。 根加载器从由系统属性sun.boot.class.path指定的目录中加载类库。 缺省设置为没有父加载器的jre目录的lib目录的class文件。 负责加载虚拟机的核心类库,如java.lang.*。 Object类由根类加载器加载。

ExtClassLoader扩展类加载器 

标准扩展类加载器。 父加载器是根类加载器。 用java编写,是ClassLoader的子类。 从java.ext.dirs或JDK安装目录的jre\lib\ext子目录下加载类库。 将用户创建的jar文件放在此目录中时,扩展类加载器会自动加载。

AppClassLoader应用类加载器 

父加载器是扩展类加载器。 在用java编写的ClassLoader子类中,从由环境变量classpath或系统属性java.class.path指定的目录中加载类。 这是用户自定义的类加载器的默认父加载器。

父类委托机制

当JVM要加载一个类时,使用哪个类加载器加载呢?
首先当前线程类加载器去加载第一个类。
如果当前类引用了另一个类,那么会使用当前类的类加载器去加载另一个类。
或者使用指定类加载器加载。

当依据上述步骤加载类时,每个步骤又会依据类加载器的父类委托机制。
类加载机制的父类委托机制:先让父类加载器试图加载该Class,只有在父类加载器无法加载该类时才尝试从自己的类路径中加载该类。

热部署系统实现_第2张图片

package com.example;

public class Demo {
    public static void main(String[] args) {
        ClassLoader classLoader = Demo.class.getClassLoader();

        while (classLoader != null){
            System.out.println(classLoader);
            classLoader = classLoader.getParent();
        }

        System.out.println(classLoader);
    }
}

运行结果

sun.misc.Launcher$AppClassLoader@18b4aac2
sun.misc.Launcher$ExtClassLoader@23ab930d
null

自定义类加载器

JVM中除了根加载器之外的所有类加载器都是ClassLoader子类的实例,开发者可以通过扩展ClassLoader的子类,并重写该ClassLoader所包含的方法来实现自定义的类加载器。
ClassLoader类有如下三个关键方法:

loadClass(String name, boolean resolve):该方法为CLassLoader的入口点,根据指定的二进制名称来加载类,系统就是调用CLassLoader的该方法来获取指定类对应的Class对象。

findClass(String name):根据二进制名称来查找类。

defineClass(String name, byte[] b, int off, int len):将指定类的字节码文件读入字节数组byte[] b内,并把它转化为Class对象。

当我们自定义类加载器时,一般只要重写findClass方法即可。

package com.example;

import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;

public class MyClassLoader extends ClassLoader {

    private String dir;

    public MyClassLoader(String dir) {
        this.dir = dir;
    }

    public MyClassLoader(String dir, ClassLoader parent) {
        super(parent);
        this.dir = dir;
    }

    @Override
    protected Class findClass(String name) throws ClassNotFoundException {
        String classFileName = dir + "/" + name.replace(".", "/") + ".class";

        try {
            FileInputStream fis = new FileInputStream(classFileName);
            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            int content;

            while ((content = fis.read()) != -1) {
                bos.write(content);
                bos.flush();
            }

            byte[] buffer = bos.toByteArray();
            return defineClass(name, buffer, 0, buffer.length);
        } catch (Exception ex) {
            System.out.println(ex);
        }

        return null;
    }
}
  • 我们创建一个Person类,把Person类文件放在/Users/yangyanping/Downloads 目录下

 Person 类代码

package com.example;

public class Person {
    public Person() {
        System.out.println(getClass().getClassLoader());
    }
}

 使用javac 编译 Person.java 得到Person.class 文件

javac Person.java

  编写测试类Demo

package com.example;

public class Demo {
    public static void main(String[] args) throws Exception {
        MyClassLoader myClassLoader = new MyClassLoader("/Users/yangyanping/Downloads");
        System.out.println("myClassLoader parent = " + myClassLoader.getParent());
        Class clasz = myClassLoader.loadClass("com.example.Person");
        Object object = clasz.newInstance();

        System.out.println(object);
    }
}

 运行结果

myClassLoader parent = sun.misc.Launcher$AppClassLoader@18b4aac2
com.example.MyClassLoader@3fa77460
com.example.Person@1ed6993a
  • 在项目中创建同一个 Person 类,测试
package com.example;

public class Person {
    public Person() {
        System.out.println(getClass().getClassLoader());
    }
}

 运行Demo类,结果如下

myClassLoader parent = sun.misc.Launcher$AppClassLoader@18b4aac2
sun.misc.Launcher$AppClassLoader@18b4aac2
com.example.Person@3fa77460

  从运行结果知道,使用的是AppClassLoader 类来加载Person 类,而不是我们自己定义的

MyClassLoader类来加载。这是由于上面讲到的 父类委托机制 。
  •     使用MyClassLoader 加载 Person 类.
package com.example;

public class Demo {
    public static void main(String[] args) throws Exception {
        MyClassLoader myClassLoader = new MyClassLoader("/Users/yangyanping/Downloads", null);
        System.out.println("myClassLoader parent = " + myClassLoader.getParent());
        Class clasz = myClassLoader.loadClass("com.example.Person");
        Object object = clasz.newInstance();

        System.out.println(object);
    }
}

     运行结果:

myClassLoader parent = null
com.example.MyClassLoader@3fa77460
com.example.Person@1ed6993a

代码演示

SPI 接口定义

/**
 * 检查订单参数
 */
public interface OrderHandler {
    void checkParam(Object[] args);
}

业务方实现

/**
 * 业务方实现
 */
public class OrderHandlerImpl implements OrderHandler {
    @Override
    public void checkParam(Object[] args) {
        System.out.println("checkParam start .....");
    }
}

 单元测试

import com.example.handler.OrderHandler;

import java.io.File;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.*;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

public class ClassLoaderTest {
    public static void main(String[] args) throws Exception {
        // 业务方实现的SPI jar包
        String path = "/Users/yangyanping/Downloads/code/ql-er/demo/order-handler-test/target/order-handler-test-0.0.1-SNAPSHOT.jar";


        File moduleFile = new File(path);
        URL moduleURL = moduleFile.toURI().toURL();

        URLClassLoader classLoader = new URLClassLoader(new URL[]{moduleURL});

        Map, Object> objectMap = getClass(path, classLoader);

        CheckWrapper checkWrapper = new CheckWrapper();
        checkWrapper.setObjectMap(objectMap);

        OrderServiceImpl impl = new OrderServiceImpl();
        impl.setCheckWrapper(checkWrapper);

        impl.subimit();
    }

    public static Map, Object> getClass(String path,
                                                  ClassLoader loader) throws Exception {
        Map, Object> objectMap = new HashMap<>();
        List classes = getAllClasses(path);
        for (String className : classes) {
            Class claz = loader.loadClass(className);

            Object obj = claz.newInstance();
            if (obj instanceof OrderHandler) {
                objectMap.put(OrderHandler.class, obj);
            }

        }
        return objectMap;
    }

    private static List getAllClasses(String module) throws Exception {
        List result = new ArrayList();
        @SuppressWarnings("resource")
        JarFile jar = new JarFile(new File(module));
        Enumeration entries = jar.entries();
        while (entries.hasMoreElements()) {
            JarEntry entry = entries.nextElement();
            String className = getClassName(entry);
            if (className != null && className.length() > 0) {
                result.add(className);
            }
        }
        return result;
    }

    private static String getClassName(JarEntry jarEntry) {
        String jarName = jarEntry.getName();
        if (!jarName.endsWith(".class")) {
            return null;
        }
        if (jarName.charAt(0) == '/') {
            jarName = jarName.substring(1);
        }
        jarName = jarName.replace("/", ".");
        return jarName.substring(0, jarName.length() - 6);
    }
}

参考:

Java服务器热部署的实现原理_chenjie19891104的博客-CSDN博客_java中的热部署

热部署_Can96的博客-CSDN博客_热部署

你可能感兴趣的:(java基础和并发编程,大数据,java,jvm)