使用JDK動態加載jar包和JavaBean反射機制實現基於接口代理的RPC分佈式服務調用

分佈式服務框架概述:

服務協調中間件:Zookeeper 負責服務註冊、發現、通知。

服務提供方:

服務啓動 使用 Spring  ApplicationListener 接口 當容器中bean 完成加載時,變量容器中的bean 將RPC bean 發佈到Zookeeper ,並保存到內存。

服務監聽 使用netty 啓動遠程服務請求監聽端口,接收到遠程服務請求數據 通過反射方式調用本地服務。

服務消費方:

服務發現  通過主動拉取Zookeeper 數據和watch 監聽服務變更。

服務調用 本地生成遠程接口代理,代理接口將請求數據打包通過netty 發送到服務提供方監聽端口。

服務消費方 基於以上流程實現遠程服務調用,本地代理接口向服務提供方發生請求數據時包含的三元組是 服務編碼(唯一標識一個服務bean)、方法名稱、參數類型(唯一標識一個javabean 方法)。這就需要在消費方引入服務接口jar包。

通常我們是在工程的POM 文件中添加maven 座標的方式依賴遠程服務的接口API。但是這種方式不易擴展,業務變更需要調用新接口時需要通過編寫新代碼重新發布系統。

於是便設計一套基於JDK動態加載jar包和JavaBean反射機制的服務調用方式,方案圖解:

 

主要實現類:

public class RMURLJarLoader extends URLClassLoader {

    Logger logger = LoggerFactory.getLogger(RMURLJarLoader.class);

    // 緩存jar數據.

    private JarURLConnection cacheJar = null;

    public RMURLJarLoader() {

        super(new URL[] {});

    }

    /**

     * 將指定的文件url添加到類加載器的classpath中去,並緩存jar connection,方便以後卸載jar

     * 一個可想類加載器的classpath中添加的文件url

     *

     * @param

     */

    public void addURLJar(URL file) {

        try {

            // 打開並緩存文件url連接

            URLConnection uc = file.openConnection();

            if (uc instanceof JarURLConnection) {

                uc.setUseCaches(true);

                ((JarURLConnection) uc).getManifest();

                cacheJar = (JarURLConnection) uc;

            }

        } catch (Exception e) {

            System.err.println("Failed to cache plugin JAR file: " + file.toExternalForm());

            logger.error("Failed to cache plugin JAR file: " + file.toExternalForm());

        }

        try {

            URLClassLoader classLoader = (URLClassLoader) ClassLoader.getSystemClassLoader();

            Method add = URLClassLoader.class.getDeclaredMethod("addURL", new Class[] { URL.class });

            add.setAccessible(true);

            add.invoke(classLoader, file);

        } catch (Exception exp) {

            logger.error("Failed to cache plugin JAR file: " + file.toExternalForm());

        }

    }

    public void unloadJarFile() {

        if (cacheJar == null) {

            return;

        }

        try {

            System.err.println("Unloading plugin JAR file " + cacheJar.getJarFile().getName());

            cacheJar.getJarFile().close();

            cacheJar = null;

        } catch (Exception e) {

            System.err.println("Failed to unload JAR file\n" + e);

            logger.error("Failed to unload JAR file\n" + e);

        } finally {

        }

    }

}
public class RMURLClassLoader implements DisposableBean {

    private final static Logger logger = LoggerFactory.getLogger(RMURLJarLoader.class);

    private final static ConcurrentHashMap<String, RMURLJarLoader> LOCAL_CACHE = new ConcurrentHashMap<>();

    private RMURLJarLoader loadJar(String jarPath) {

        RMURLJarLoader jar = LOCAL_CACHE.get(jarPath);

        if (jar != null) {

            return jar;

        }

        jar = new RMURLJarLoader();

        try {

            URI uri = new URI(jarPath);

            jar.addURLJar(uri.toURL());

            LOCAL_CACHE.put(jarPath, jar);

        } catch (URISyntaxException e) {

            logger.error("Laod jar:" + jarPath + " exception!");

        } catch (MalformedURLException e) {

            logger.error("Laod jar:" + jarPath + " exception!");

        }

        return jar;

    }

    public Class loadClass(String jarPath, String fullClassName) {

        if (StringUtils.isNullOrEmpty(jarPath) || StringUtils.isNullOrEmpty(fullClassName)) {

            return null;

        }

        RMURLJarLoader jar = LOCAL_CACHE.get(jarPath);

        if (jar == null) {

            jar = loadJar(jarPath);

        }

        try {

            return jar.loadClass(fullClassName);

        } catch (ClassNotFoundException e) {

            logger.error("Laod jar:" + jarPath + "class :" + fullClassName + " exception!");

        }

        return null;

    }

    public void unloadJarFile(String jarPath) {

        RMURLJarLoader jar = LOCAL_CACHE.get(jarPath);

        if (jar == null) {

            return;

        }

        jar.unloadJarFile();

        jar = null;

        LOCAL_CACHE.remove(jarPath);

    }

/**

 * Invoked by a BeanFactory on destruction of a singleton.

 *

 * @throws Exception in case of shutdown errors.

 * Exceptions will get logged but not rethrown to allow

 * other beans to release their resources too.

 */

@Override

public void destroy() throws Exception {

    for (RMURLJarLoader jar : LOCAL_CACHE.values()) {

        jar.unloadJarFile();

    }

}

}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章