• Java代码实现热部署


    一.思路

    0. 监听java文件最后修改时间,如果发生变化,则表示文件已经修改,进行重新编译

    1. 编译java文件为 class文件

    2. 通过手写类加载器,加载 class文件 ,创建对象

    3. 反射创建对象 / 进行调用,(如果是web项目可以将创建的对象添加到spring容器中)

    4. 调用测试

    二.知识点

           1. 自定义类加载器 继承 URLClassLoader 或 ClassLoader 都可以,继承 URLClassLoader 重写findClass(String name)方法即可实现加载class文件;

             2. findClass方法核心语句 :return super.defineClass(String name, byte[] b, int off, int len)方法,b是class文件读取后得到的byte[]形式;

             3. cmd窗口 使用javac即可将java文件编译成 class文件,在代码里使用 JavaCompiler 类,调用run方法即可编译指定java文件为class文件;

             4. JavaCompiler不支持直接new,通过类ToolProvider.getSystemJavaCompiler()方法获取;

             5. 通过类加载器获取的 class文件有时不方便调用,所以可以采用反射调用;

             6. 对于一个java文件,可以通过File类的 lastModified获取最后修改时间,循环比较lastModified即可判断文件是否被修改;

             7. class文件可以生成在任意目录,通过路径读取即可;

             8. 选择合适的类加载器或自定义类加载器,对于电脑上任意位置的class文件完全都可以通过反射调用;

        9. @SneakyThrows可以理解成 try-catch,使用需要导入lombok

             10. 本demo是通过查阅资料和不断测试实现,如果有不足请指出;

    三.实现

    1. Demo概述

    目标: 实现对HotTestService类的热部署,通过测试(main)监控java文件,如果java文件变动调用自定义类加载器MyClassLoader得到HotTestService的class对象,反射调用

    2. 核心测试方法

    package com.ahd.springtest.utils;
    
    import lombok.SneakyThrows;
    
    import javax.tools.JavaCompiler;
    import javax.tools.ToolProvider;
    import java.io.*;
    import java.lang.reflect.InvocationTargetException;
    import java.lang.reflect.Method;
    import java.net.MalformedURLException;
    import java.net.URL;
    import java.util.concurrent.TimeUnit;
    
    public class AhdgTest {
    
        //sourcePath 是java文件存放,编辑的路径
        private static String sourcePath = "D:\workspace_ms\20210319\springtest\src\main\java";
        //targetPath 是class文件存放路径
    //    private static String targetPath = "D:\workspace_ms\20210319\springtest\target\classes";
        private static String targetPath = "D:\workspace_ms\20210319\springtest\src\main\java";
        private static String errPath = "D:\文件清单\hotlog.txt";//编译日志打印目录
        private static String basePath = "\com\ahd\springtest\service\HotTestService"; //包名 + 类名,路径形式
    
    
        public static void main(String[] args) throws InterruptedException, IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchMethodException, MalformedURLException, ClassNotFoundException {
            //测试热部署
            testHot();
        }
        /***
         * 目标 : main方法循环调用,实时监测 com.ahd.springtest.service.HotTestService 是否发生改变,如果发生改变,重新加载并调用
         *
         * 0. 监听java文件最后修改时间,如果发生变化,则表示文件已经修改,进行重新编译
         *
         * 1. 编译java文件为 class文件
         *
         * 2. 通过手写类加载器加载 class文件 ,创建对象
         *
         * 3. 将新创建的对象 放入spring容器中
         *
         * 4. 调用测试
         *
         */
        public static void testHot() throws MalformedURLException, IllegalAccessException, InstantiationException, ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InterruptedException {
            //        0. 监听java文件最后修改时间,(如果发生变化,则表示文件已经修改,需要进行重新编译)
            File file = new File(sourcePath + basePath + ".java");
    
    
            Thread thread = new Thread(new Runnable() {
                @SneakyThrows  //简化 try catch写法
                @Override
                public void run() {
                    Long lastModifiedTime = file.lastModified();
    
                    while (true) {
                        long timeEnd = file.lastModified();
                        if (timeEnd != lastModifiedTime) {
                            lastModifiedTime = timeEnd;
    
                            //        1. 编译java文件为 class文件
                            try (InputStream is = new FileInputStream(file.getAbsolutePath());
                                 OutputStream os = new FileOutputStream(targetPath + basePath + ".class");
                                 OutputStream err = new FileOutputStream(errPath)) {
    
                                JavaCompiler javaCompiler = ToolProvider.getSystemJavaCompiler();
                                javaCompiler.run(is, os, err, sourcePath + basePath + ".java");//前三参数传入null 默认是 System.in,System.out.System.err
    
                            } catch (FileNotFoundException e) {
                                e.printStackTrace();
                            } catch (IOException e) {
                                e.printStackTrace();
                            }
                            //        2. 通过手写类加载器加载 class文件 ,创建对象
                            MyClassLoader instance = MyClassLoader.getInstance(new File(targetPath).toURI().toURL());
                            Class<?> aClass = instance.findClass("com.ahd.springtest.service.HotTestService");
    
                            Object o = aClass.newInstance();
                            //        3. 将新创建的对象 反射调用
                            Method test = aClass.getMethod("test");
                            Object invoke = test.invoke(o);
                            System.out.println(invoke);
                        }
                        try {
                            Thread.sleep(20);//检测频率:100ms
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            });
            thread.setDaemon(true);//设置成守护线程
            thread.start();
    
    //让主main一直运行,可以查看结果
            while(true){
                Thread.sleep(1000);
            }
        }
    }
    核心代码

    3. 自定义类加载器MyClassLoader

    package com.ahd.springtest.utils;
    
    import com.sun.xml.internal.ws.util.ByteArrayBuffer;
    import lombok.SneakyThrows;
    
    import java.io.IOException;
    import java.io.InputStream;
    import java.net.MalformedURLException;
    import java.net.URL;
    import java.net.URLClassLoader;
    
    public class MyClassLoader extends URLClassLoader {
        private static MyClassLoader myClassLoader;//可以直接通过getInstance方法获取
        private URL[] urls;
        private URL url;
    
        public MyClassLoader(URL url) {
            super(new URL[]{url});
            this.url = url;
        }
    
        public MyClassLoader(URL[] urls) {
            super(urls);
            this.urls = urls;
        }
        /***
         *         1. name 是 类的 全限命名,通过全限名命 + 路径 获取 绝对路径
         *
         *         2. io获取字节码
         *
         *         3. 调用父类方法创建并返回class对象
         * @param name
         * @return
         * @throws ClassNotFoundException
         */
        @SneakyThrows //简化的 try catch写法
        @Override
        protected Class<?> findClass(String name) throws ClassNotFoundException {
            //1. name 是 类的 全限命名,通过全限名命 + 路径 获取 绝对路径
            String path = url.getPath();
    //        System.out.println("yangdc log:  " + path);
            String classPath = path + name.replaceAll("\.","/").concat(".class");
    
            //2. io获取字节码
            InputStream is = null;
            URL url = null;
            int b = 0;
            ByteArrayBuffer bab = new ByteArrayBuffer();
            try {
                url = new URL("file:" + classPath);
    //            url = new URL("jar:" + classPath);
                is = url.openStream();
            } catch (MalformedURLException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            }
            while((b = is.read())!=-1){
                bab.write(b);
            }
            is.close();
    
            //3. 调用父类方法创建并返回class对象
            return super.defineClass(name,bab.toByteArray(),0,bab.size());
        }
    
        public static MyClassLoader getInstance(URL url){
            if (myClassLoader == null){
                return new MyClassLoader(url);
            }
            return myClassLoader;
        }
    
        public static MyClassLoader getInstance(URL[] url){
            if (myClassLoader == null){
                return new MyClassLoader(url);
            }
            return myClassLoader;
        }
    
    
        public URL[] getUrls() {
            return urls;
        }
    
        public void setUrls(URL[] urls) {
            this.urls = urls;
        }
    
        public URL getUrl() {
            return url;
        }
    
        public void setUrl(URL url) {
            this.url = url;
        }
    }
    自定义类加载器

    4. 被测试的类HotTestService

    package com.ahd.springtest.service;
    import org.springframework.stereotype.Service;
    
    @Service
    public class HotTestService {
        public HotTestService() {
        }
    
        public String test() {
            return "第39696633次测试hot";
        }
    }

    测试结果来张图

  • 相关阅读:
    从服务器上下载下来的代码,部署到本地时,Url自动带www前缀
    个人说明
    名词解释
    Bandizip-解压缩软件
    uTools-工具插件集
    Geek-软件卸载工具
    Microsoft商店软件推荐
    Docker入门第九章
    Docker入门第八章
    IDM-下载工具
  • 原文地址:https://www.cnblogs.com/aihuadung/p/14611069.html
Copyright © 2020-2023  润新知