使用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();

    }

}

}

 

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