您的位置:首页 > 编程语言 > Java开发

java中插件机制和热升级的实现方案

2015-04-01 01:14 274 查看

引言

插件式的架构可以为系统带来极高的扩展性。典型的一个例子就是eclipse。我们可以下载各种各样的插件来不断丰富eclipse的功能,而eclipse本身却不需要作任何改动。那么在java中如何实现插件机制呢?

动态加载

实现插件式系统的基础是动态加载机制。所谓动态加载是指系统所要用到的字节码文件不需要添加classpath目录下,而在运行时由程序本身根据需要加载到jvm中。这种情况下相应的jar包可以放在任意位置,甚至从网络上获取。jdk中的ClassLoader类为我们提供了这一强大的特性。我们可以自定义一个子类来继承ClassLoader类,从而实现一些自定义的需求,但不要轻易就重写ClassLoader的方法,除非你对ClassLoader非常熟悉,并且有非这样做不可的需求。在本文,我们可以直接使用了jdk自带的URLClassLoader类来实现插件的动态加载。

URLClassLoader的构造函数原型为:

[code]public URLClassLoader(URL[] urls, ClassLoader parent)


第一个参数为class文件或jar包的URL列表,大致可以理解为class文件的地址,该地址可以是本地磁盘地址也可以是网络地址。第二个参数为父级ClassLoader,该参数我们一般都赋值为调用者本身的ClassLoader。此处需要非常注意的是java中的类都是由classloader加载的,如果同一个class文件由不同的classloader加载,则被认为是两个class类型,他们的实例间也不能强制转换。关于ClassLoader的更详尽的讲解可以参考这篇博文: 深入分析Java ClassLoader原理

动态加载类文件需要使用URLClassLoader类的loadClass方法,其原型为:

[code]public Class<?> loadClass(String name) throws ClassNotFoundException;


其参数的含义即为我们想要加载的类的全限定名称。

先上一段示例代码:

[code]
//播放器插件列表
 List<Player> audioPlayers = new ArrayList<>();

 //动态加载插件
 URL pluginJar = new File("D:/plugins/wma-player.jar").toURI().toURL();
 URLClassLoader classLoader = new URLClassLoader(new URL[]{pluginJar}, Thread.currentThread().getContextClassLoader());
 Class clazz = classLoader.loadClass("org.cbqin.player.audio.WmaPlayer");

 //生成插件实例
 Object obj = clazz.newInstance();

 if (obj instanceof Player) {
    //缓存到插件列表中,供之后播放时调用
    audioPlayers.add((Player) obj);
 }


该段代码的目的是在运行时动态加载WmaPlayer类,实例化后放入播放组件列表中,从而使得播放器可以播放wma格式的音频。

从中我们不难看出插件加载的一般步骤为:

构造URLClassloader实例:通过构造方法或newInstance方法将其所能加载的URL列表设置为我们需要扫描的jar包地址列表

加载目标类文件:通过URLClassloader的loadClass方法加载目标类文件。在上述代码中为了方便理解使用了硬编码的形式,而在实际工程中一般是在jar包中添加一个plugin.xml(名字和类型均可以自由设定)文件来描述该包中需要加载的类以及插件的一些其他信息。

生成类实例 : 通过反射调用相应方法生成目标类的实例。

热替换

现在,我们已经实现了插件的动态加载。那么能否再给力一点呢,就像tomcat中,我们修改了webapp下面工程的web.xml,则tomcat可以为我们重新部署修改后的工程。

答案是肯定的,热替换的基础是对类文件的实时监控。当然,同时也离不开动态加载。

目前java中的文件监控方案主要有:

jdk中的新增API WatchService API

通过JNI调用底层接口的jnotify

Apache Commons中的FileAlterationObserver

热替换的基本步骤为:

使用文件监控API监视插件目录的变化

当插件目录下的文件发生修改时,重新加载相应的插件

示例代码如下:

[code]//监控插件目录
FileAlterationObserver observer = new FileAlterationObserver("D:/plugins/");
observer.addListener(new FileAlterationListenerAdaptor() {
    @Override
    public void onFileChange(File file) {
        System.out.println(file.getName() + " has been changed");
        //重新加载相应的jar包
        try {
            reloadPlugin(file);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
});

//设置监控间隔
FileAlterationMonitor fileMonitor = new FileAlterationMonitor(1000, observer);

// 启动监控
fileMonitor.start();


结合动态加载和文件监控API,看起来“高大上”的插件机制便被轻松的实现了。后面有空的时候会考虑再写一篇关于插件沙箱机制实现的文章,完善下这个主题。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: