天天看點

Java代碼實作熱部署

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. 核心測試方法

Java代碼實作熱部署
Java代碼實作熱部署
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

Java代碼實作熱部署
Java代碼實作熱部署
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";
    }
}      

l  測試結果來張圖

Java代碼實作熱部署