Java不停程序动态加载变更的类

笔者最近在研究如何在不停止已经运行程序的情况下,只要类发生一旦变更就能够自动重新加载新修改的类。之所以研究这个主要原因有以下几个:

  1. 不用停止程序就能够完成新逻辑变更
  2. 通用程序启动,启动类永远是一个,具体逻辑通过继承相应的接口实现

实现方案

Redis + 哈希(Hash)

将写好的Java代码编译成class文件,然后将字节码文件存到redis中,主程序启动的时候通过自定义classLoader加载相关的类。优点是存取快,缺点是缺少监听机制,需要定时监控redis值的变化,检测到变更触发重新加载类操作。

Redis + 发布订阅

实现方式同方式1,区别在于多了监听机制,redis的消息更新即可触发监听机制从而触发重新加载操作,缺点是发布订阅不能从头开始订阅,消息订阅只能消费一次,如果程序一旦崩溃是获取不到已经上传过的字节码文件。

kafka

kafka支持从头消费,也支持持久化,缺点同方式1,缺少监听机制,只能通过检测kafka值发生变化来重新加载类

zookeeper

zookeeper方式优于上述三种方案,主要是支持持久化,同样有相应的监听机制,节点信息变更能触发相应的回调函数,我们在回调函数里面重新执行加载类的操作即可,这也是笔者选择zookeeper实现的主要原因。

实现过程

  • 将编译好的class文件上传到zookeeper
  • 自定义类加载
  • 主程序根据配置信息动态加载相应的类
  • 修改回调函数重新执行加载操作

相关代码

  • 上传字节码文件到zookeeper
    public static void upload(ZkClient zk, String path, byte[] data){
    
            String[] paths = path.split("/");
            String realPath = "";
            for(String tmpPath : paths){
                if(tmpPath.isEmpty()) continue;
                realPath += "/" + tmpPath;
                if(!zk.exists(realPath)){
                    zk.create(path, "", CreateMode.PERSISTENT);
                    log.info("create node success : {}",path);
                }
            }
    
            if(!zk.exists(path)){
                zk.create(path, Base64.getEncoder().encodeToString(data), CreateMode.PERSISTENT);
                log.info("create node success : {}",path);
            }else{
                zk.writeData(path, Base64.getEncoder().encodeToString(data));
                log.info("writeData success : {}",path);
            }
        }

    注意:上传的字节码,笔者遇到使用zookeeper默认序列化的方式会出错,自定义序列化的方式同样也会出现问题,string跟byte转换的过程会有点问题,笔者尝试了Base64编码解码最后没有问题了,有兴趣的可以尝试一下

相关的单元测试类,涉及到路径相关的按照自己真实的修改即可

package com.monica.test;

import com.monica.zk.ZKUtils;
import org.I0Itec.zkclient.ZkClient;
import org.junit.Before;
import org.junit.Test;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.Channels;
import java.nio.channels.FileChannel;
import java.nio.channels.WritableByteChannel;


public class ZkTest {

    private ZkClient zkClient;

    @Before
    public void init(){
        String connStr = "localhost:2181";
        zkClient = new ZkClient(connStr);
    }


    @Test
    public void testCreateNode() throws IOException, InterruptedException {

        String userPath = "/code/java/test/firstProcess";
        byte[] data = getClassByte("/home/monica/study/dynamic-load-class-parent/dynamic-logic/target/classes/com/monica/dynamic/FirstProcess.class");
        ZKUtils.upload(zkClient,userPath,data);

    }


    @Test
    public void testDeleteNode() throws IOException, InterruptedException {

        String userPath = "/code/java/test/firstProcess";
        zkClient.delete(userPath);

    }
    

    private static byte[] getClassByte(String path) throws IOException {

        File file = new File(path);
        FileInputStream fis = new FileInputStream(file);
        FileChannel fc = fis.getChannel();
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        WritableByteChannel wbc = Channels.newChannel(baos);
        ByteBuffer by = ByteBuffer.allocate(1024);

        while (true) {
            int i = fc.read(by);
            if (i == 0 || i == -1) {
                break;
            }
            by.flip();
            wbc.write(by);
            by.clear();
        }
        fis.close();
        return  baos.toByteArray();

    }



}

主程序启动类

package com.monica.main.process;

import com.monica.dynamic.api.ProcessApi;
import org.I0Itec.zkclient.IZkDataListener;
import org.I0Itec.zkclient.ZkClient;

import java.util.Base64;

public class MainProcess {

    private static volatile ProcessApi process = null;

    public static void main(String[] args) throws Exception {


        String connStr = "localhost:2181";
        ZkClient zk = new ZkClient(connStr);

        String path = "/code/java/test/firstProcess";



        // 注册【数据】事件
        zk.subscribeDataChanges(path, new IZkDataListener() {

            @Override
            public void handleDataDeleted(String arg0) throws Exception {
                System.err.println("数据删除:" + path);
                System.exit(-1);

            }

            @Override
            public void handleDataChange(String arg0, Object arg1) throws Exception {
                System.err.println("数据修改:" + path );

                synchronized (process) {
                    String data = zk.readData(path);
                    MyClassLoader myClassLoader = new MyClassLoader(Base64.getDecoder().decode(data));
                    Class clazz = myClassLoader.loadClass("com.monica.dynamic.FirstProcess");
                    process = (ProcessApi) clazz.newInstance();
                    myClassLoader = null;
                }



            }
        });

        String data = zk.readData(path);
        MyClassLoader myClassLoader = new MyClassLoader(Base64.getDecoder().decode(data));

        Class clazz = myClassLoader.loadClass("com.monica.dynamic.FirstProcess");
        process = (ProcessApi) clazz.newInstance();
        myClassLoader = null;

        while (true) {

            process.doProcess();

            Thread.sleep(3000);
        }

    }

}

笔者这里将源码也上传方便用到的同学研究,下载不了可能得等待审核通过才行。可以尝试运行住程序,一边修改相关类重新上传验证一下,笔者这里也验证是可以正常加载的,如有表述不对的地方欢迎指正!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值