自定义classloader并实现热部署-使用findClass

需求如下:

  1. 可在前端新增、修改java代码,并可实现服务不重启的前提下进行代码的部署运行。
  2. 相当于:可实现java代码的热部署。

代码如下:

  1. MyClassLoaderTest.java:测试类
  2. MyCompiler.java:编译类,可将java代码编译为字节码byte数组
  3. MyClassLoaderHelp.java:自定义classloader封装类
  4. MyClassLoader.java:自定义classloader(不要直接使用此类中的方法,应通过使用MyClassLoaderHelp来实现功能)

MyClassLoaderTest.java

import java.lang.reflect.Method;

public class MyClassLoaderTest {

    /**
     * 包路径
     */
    private static final String pack = "com.code";
    /**
     * 类名称
     */
    private static final String className = "TestCode";

    private static String sourceCode = "package " + pack + ";" +
            "public class " + className + " {" +
            "    public String invoke(String msg) throws Exception {" +
            "        return msg;" +
            "    }" +
            "}";

    /**
     * cache :
     * paramCode、paramLogic、time(时间戳)
     * localMap :
     * paramCode、time(时间戳)
     */
    public static void main(String[] args) throws Exception {
        /**
         * 使用自定义classloader加载
         */
        // 1. 加载class文件到自定义classloader
        Class<?> clazz = MyClassLoaderHelp.findClass(pack + "." + className, 1L, sourceCode);
        Object obj = clazz.newInstance();
        Method method = clazz.getDeclaredMethod("invoke", String.class);
        method.setAccessible(true);
        System.err.println(method.invoke(obj, "hello world!"));

        /**
         * 模拟类信息发生变化,则可以实现热部署并直接执行新代码
         * 1. 当nonaTime变化时,会进行类的热部署,这里考虑使用时间是考虑到分布式服务部署的话,用更新标志位会产生问题,故使用代码的最新时间与已加载的类的时间来做比对
         * 2. 修改代码以达到效果展示
         */
        long nanoTime = 2L;
        sourceCode = "package " + pack + ";" +
                "public class " + className + " {" +
                "    public String invoke(String msg) throws Exception {" +
                "        return msg + \"代码改动,执行新的代码;\";" +
                "    }" +
                "}";
        for (int i = 0; i < 2; i++) {
            // 当i=0,由于需要重新加载,则需要通过load加载
            // 当i=1,由于已加载过,则可直接从cache加载
            clazz = MyClassLoaderHelp.findClass(pack + "." + className, nanoTime, sourceCode);
            obj = clazz.newInstance();
            method = clazz.getDeclaredMethod("invoke", String.class);
            method.setAccessible(true);
            System.err.println(method.invoke(obj, "hello world!"));
        }

    }

}

在这里插入图片描述

MyCompiler.java

import javax.tools.*;
import javax.tools.JavaFileObject.Kind;
import java.io.*;
import java.net.URI;
import java.nio.CharBuffer;
import java.util.*;

/**
 * 自定义编译器
 */
public class MyCompiler {

    private static JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();

    /**
     * 将java代码编译为class字节码并返回byte数组
     */
    public static Map<String, byte[]> compiler(String className, String sourceCode) throws Exception {
        try (
                StandardJavaFileManager stdManager = compiler.getStandardFileManager(null, null, null);
                MemoryJavaFileManager manager = new MemoryJavaFileManager(stdManager)
        ) {
            JavaFileObject javaFileObject = manager.makeStringSource(className, sourceCode);
            JavaCompiler.CompilationTask task = compiler.getTask(null, manager, null, null, null, Arrays.asList(javaFileObject));
            if (task.call()) {
                return manager.getClassBytes();
            }
        }
        return null;
    }

    /**
     * 内存Java文件管理器
     */
    static class MemoryJavaFileManager extends ForwardingJavaFileManager<JavaFileManager> {
        final Map<String, byte[]> classBytes = new HashMap<String, byte[]>();
        final Map<String, List<JavaFileObject>> classObjectPackageMap = new HashMap<>();
        MemoryJavaFileManager(JavaFileManager fileManager) {
            super(fileManager);
        }
        public Map<String, byte[]> getClassBytes() {
            return new HashMap(this.classBytes);
        }
        @Override
        public void flush() {

        }
        @Override
        public void close() {
            classBytes.clear();
        }
        @Override
        public Iterable<JavaFileObject> list(Location location, String packageName, Set<Kind> kinds, boolean recurse) throws IOException {
            Iterable<JavaFileObject> it = super.list(location, packageName, kinds, recurse);
            if (kinds.contains(Kind.CLASS)) {
                final List<JavaFileObject> javaFileObjectList = classObjectPackageMap.get(packageName);
                if (javaFileObjectList != null) {
                    if (it != null) {
                        for (JavaFileObject javaFileObject : it) {
                            javaFileObjectList.add(javaFileObject);
                        }
                    }
                    return javaFileObjectList;
                } else {
                    return it;
                }
            } else {
                return it;
            }
        }

        @Override
        public String inferBinaryName(Location location, JavaFileObject file) {
            if (file instanceof MemoryInputJavaClassObject) {
                return ((MemoryInputJavaClassObject) file).inferBinaryName();
            }
            return super.inferBinaryName(location, file);
        }

        @Override
        public JavaFileObject getJavaFileForOutput(JavaFileManager.Location location, String className, JavaFileObject.Kind kind, FileObject sibling) throws IOException {
            if (kind == JavaFileObject.Kind.CLASS) {
                return new MemoryOutputJavaClassObject(className);
            } else {
                return super.getJavaFileForOutput(location, className, kind, sibling);
            }
        }

        JavaFileObject makeStringSource(String className, final String code) {
            String classPath = className.replace('.', '/') + JavaFileObject.Kind.SOURCE.extension;
            return new SimpleJavaFileObject(URI.create("string:///" + classPath), Kind.SOURCE) {
                @Override
                public CharBuffer getCharContent(boolean ignoreEncodingErrors) {
                    return CharBuffer.wrap(code);
                }
            };
        }

        void makeBinaryClass(String className, final byte[] bs) {
            JavaFileObject javaFileObject = new MemoryInputJavaClassObject(className, bs);
            String packageName = "";
            int pos = className.lastIndexOf('.');
            if (pos > 0) {
                packageName = className.substring(0, pos);
            }
            List<JavaFileObject> javaFileObjectList = classObjectPackageMap.get(packageName);
            if (javaFileObjectList == null) {
                javaFileObjectList = new LinkedList<>();
                javaFileObjectList.add(javaFileObject);
                classObjectPackageMap.put(packageName, javaFileObjectList);
            } else {
                javaFileObjectList.add(javaFileObject);
            }
        }

        class MemoryInputJavaClassObject extends SimpleJavaFileObject {
            final String className;
            final byte[] bs;

            MemoryInputJavaClassObject(String className, byte[] bs) {
                super(URI.create("string:///" + className.replace('.', '/') + Kind.CLASS.extension), Kind.CLASS);
                this.className = className;
                this.bs = bs;
            }

            @Override
            public InputStream openInputStream() {
                return new ByteArrayInputStream(bs);
            }

            public String inferBinaryName() {
                return className;
            }
        }

        class MemoryOutputJavaClassObject extends SimpleJavaFileObject {
            final String className;

            MemoryOutputJavaClassObject(String className) {
                super(URI.create("string:///" + className.replace('.', '/') + Kind.CLASS.extension), Kind.CLASS);
                this.className = className;
            }

            @Override
            public OutputStream openOutputStream() {
                return new FilterOutputStream(new ByteArrayOutputStream()) {
                    @Override
                    public void close() throws IOException {
                        out.close();
                        ByteArrayOutputStream bos = (ByteArrayOutputStream) out;
                        byte[] bs = bos.toByteArray();
                        classBytes.put(className, bs);
                        makeBinaryClass(className, bs);
                    }
                };
            }
        }
    }

}

MyClassLoaderHelp.java

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 自定义classloader包装类
 */
public class MyClassLoaderHelp {
    /**
     * 类信息缓存
     */
    private static Map<String, Class> clazzs = new ConcurrentHashMap<>();
    /**
     * 用于记录当前加载的类的对应时间
     * key : name
     * value : 装载类的时间戳
     */
    private static Map<String, Long> updateClazzs = new ConcurrentHashMap<>();

    /**
     * 重写findClass
     */
    public static Class<?> findClass(String name, Long timestampCurrent, String sourceCode) throws Exception {
        Long timeStampLastModify = updateClazzs.get(name);
        // 1. 如果缓存中没有,则说明为新增操作,则重新装载class
        if (timeStampLastModify == null) {
            clazzs.remove(name);
        }
        // 2. 如果缓存中存在,且时间不一致,则说明有更新操作,则使用新的classloader并重新装载class文件
        if (timeStampLastModify != null && timestampCurrent != timeStampLastModify) {
            clazzs.remove(name);
            MyClassLoader.createNewClassLoader();
        }
        // 1> 从缓存中获取
        Class cache = clazzs.get(name);
        if (null != cache) {
            // 2> 若缓存存在,则直接返回
            System.err.println(" 从缓存获取类信息! ");
            return cache;
        }
        // 1. 若缓存中获取失败,则说明需要重新装载class文件,findClass之前先进行编译java数据为字节码数组
        MyClassLoader.getInstance().compiler(name, sourceCode);
        // 2. 通过字节码数组加载class
        Class<?> aClass = MyClassLoader.getInstance().findClass(name);
        // 3. 将数据放入缓存
        clazzs.put(name, aClass);
        // 4. 更新缓存时间
        updateClazzs.put(name, timestampCurrent);
        return aClass;
    }

}

MyClassLoader.java

import java.util.HashMap;
import java.util.Map;

/**
 * 自定义classloader
 */
public class MyClassLoader extends ClassLoader {
    // 存放java文件对应的字节码,后期可以改存到redis,减少编译时间
    private static Map<String, byte[]> CLASS_BYTES = new HashMap();

    // MyClassLoader单例模式
    private static volatile MyClassLoader instance;
    private MyClassLoader() { }
    public static MyClassLoader getInstance() {
        if (null == instance) {
            synchronized (MyClassLoader.class) {
                if (null == instance) {
                    instance = new MyClassLoader();
                }
            }
        }
        return instance;
    }
    /**
     * 重置classloader,返回新的classLoader,并从缓存移除此装载的类
     */
    public synchronized static void createNewClassLoader() {
        instance = new MyClassLoader();
    }

    /**
     * 编译java文件并存入缓存中
     */
    public void compiler(String name, String sourceCode) throws Exception {
        CLASS_BYTES.put(name, MyCompiler.compiler(getClassName(name), sourceCode).get(name));
    }

    /**
     * 重写findClass:此方法不要使用
     */
    @Override
    public Class<?> findClass(String name) {
        System.err.println(" 通过findclass加载类信息! ");
        byte[] b = CLASS_BYTES.get(name);
        Class<?> aClass = defineClass(name, b, 0, b.length);
        CLASS_BYTES.remove(name);
        return aClass;
    }

    /**
     * 获取类名称
     * @param name : com.xx.ClassName
     */
    private static String getClassName(String name) {
        String[] packagePaths = name.split("\\.");
        return packagePaths[packagePaths.length - 1];
    }

}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小安灬

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值