classloader實戰:如何不重啟校驗資料庫驅動連結

weixin_34291004發表於2018-06-06

場景介紹

使用過was的時候,我們常見的建立資料來源時有一個驗證資料庫資訊的正確性的按鈕。但是如果沒有相應的驅動包的時候,校驗是失敗的,如果想校驗成功,那就加入對應的資料庫驅動包即可,但是was本身並不是熱部署的,要想驗證那就必須重啟was。這個在伺服器還是可以接受的,因為你建立資料來源肯定是是先有規劃的,驅動包都是放入指定地點的,weblogic11g was本身還提供了很多資料庫的Jar包以備使用。但是換成一個配置系統的話,那這樣的操作就不能忍受了。因為要驗證一下資料庫連線是否正確還要去重啟遠端的機器,這聽起來就比較麻煩。

解決方案

java的熱部署方案可以解決這個問題。java利用classloader的雙親委託機制可以解決這個問題。思路就是用新的classloader去載入類,然後去做校驗,打破雙親委託。類載入的目錄就是我們指定的目錄,每次可以上傳jar包到固定目錄。然後用新的classloader去載入。

程式碼實現

首先要寫一個破壞雙親委託的classloader。

package com.xp.classloader;

import java.net.URL;
import java.net.URLClassLoader;
import java.util.HashMap;
import java.util.Map;

public class DriverLoader extends URLClassLoader {

    // class cache
    Map<String, Class<?>> loadedClasses = new HashMap<>();

    public DriverLoader(URL[] urls) {
        super(urls);
    }

    @Override
    public Class<?> loadClass(String name) throws ClassNotFoundException {
        if (loadedClasses.containsKey(name)) {
            return loadedClasses.get(name);
        }

        try {
            Class<?> findClass = this.findClass(name);
            loadedClasses.put(name, findClass);
            return findClass;
        } catch (ClassNotFoundException e) {
            // ignore it
        }
        return super.loadClass(name);
    }

    @Override
    protected Package getPackage(String name) {
        return super.getPackage(name);
    }

}

當有新的jar包上傳的時候,就需要重新載入類,這裡的類是可以配置的。然後根據配置讀取載入的類。

package com.xp.classloader;

import java.io.File;
import java.io.FilenameFilter;
import java.net.URL;

public class ClassManager {
    public static final String DIR = "d:/jarFile/";

    public static Class<?> getClass(String className) throws Exception {
        File jarDir = new File(DIR);
        File[] listFiles = jarDir.listFiles(new FilenameFilter() {

            @Override
            public boolean accept(File dir, String name) {
                // check jar file
                if (name.endsWith(".jar")) {
                    return true;
                }
                return false;
            }
        });
        URL[] urls;
        if (listFiles == null) {
            urls = new URL[0];
        } else {
            int count = 0;
            urls = new URL[listFiles.length];
            for (File listFile : listFiles) {
                urls[count++] = listFile.toURI().toURL();
            }
        }
        ClassLoader loader = new DriverLoader(urls);
        return loader.loadClass(className);
    }
}

為了發現檔案上傳就可以出發這個操作,所以此時需要使用java7的新特性---目錄監控。

package com.xp.dir;

import java.io.IOException;
import java.nio.file.FileSystems;
import java.nio.file.Paths;
import java.nio.file.StandardWatchEventKinds;
import java.nio.file.WatchEvent;
import java.nio.file.WatchEvent.Kind;
import java.nio.file.WatchKey;
import java.nio.file.WatchService;

public class DirWatcher {

    private WatchService service;

    public DirWatcher(String filePath) throws IOException {
        service = FileSystems.getDefault().newWatchService();
        // register create event
        Paths.get(filePath).register(service, StandardWatchEventKinds.ENTRY_CREATE);

    }

    public void watch(WatcherHandler handler) throws InterruptedException {
        while (true) {
            WatchKey key = service.take();
            for (WatchEvent<?> event : key.pollEvents()) {
                Kind<?> kind = event.kind();
                if (kind == StandardWatchEventKinds.ENTRY_CREATE) {
                    handler.handler();
                }
            }
            key.reset();
        }
    }

}

為了完成功能程式碼簡潔,這裡使用了回撥。增加一個介面。

package com.xp.dir;

public interface WatcherHandler {
    public void handler();
}

接下來就是呼叫的主體。

package com.xp;

import com.xp.classloader.ClassManager;
import com.xp.dir.DirWatcher;
import com.xp.dir.WatcherHandler;

public class Main {

    public static void main(String[] args) throws Exception {
        new DirWatcher(ClassManager.DIR).watch(new WatcherHandler() {

            @Override
            public void handler() {
                Class<?> class1;
                try {
                    class1 = ClassManager.getClass("com.mysql.jdbc.Driver");
                    if (class1 != null) {
                        System.out.println("success");
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }

            }
        });
    }

}

開啟目錄監控,然後直接使用回撥去載入mysql的驅動類。這裡已經獲取到驅動類了,再通過這個驅動類就能直接獲取連結。

總結

classloader讓Java的操作更加靈活,很多需要重啟應用的問題,都可以考慮加入classloader來做到熱更新。

相關文章