codecamp

部署时决定-插件机制

什么是插件

如果我们的项目依赖了一个接口,但是我们在开发时,真的没办法确定,部署的时候,到底采用哪个实现。

比如 Nutz 的 Log。 它在运行时,会判读当前运行环境 log4j 是否可用(有 log4j 的org.apache.log4j.Logger类), 如果没有,那么它就把日志信息输出到控制台上。它的实现,就是依靠的方式。

但是,同复杂强大的 OSGI 插件体系不同,这里的插件只是强调,在部署时决定采用什么实现。在运行时,它是没 办法更改的。因此 Nutz 虽然在编译时依赖了 Log4j,但是在运行时,没有 log4j 的 jar,依然能够工作的很好。

也正因为,这个插件简单的令人发指。有兴趣的同学可以参看: org.nutz.plugin 里面的源 代码,我想几分钟你就会全部看完。

简单使用插件

比如有一个接口:

public interface Said{
    String say();
}

你有两个实现类:

  • 实现类 A
    public class TomSaid implements Said{
        public String say(){
            return "I am Tom";
        }
    }
  • 实现类 B
    public class PeterSaid implements Said{
        public String say(){
            return "I am Peter";
        }
    }

这两个实现类分别放在两个 jar 包里,在你的工程部署时,负责部署的工程师很希望:

  • 将 tom.jar 放到项目里,整个工程就会使用 TomSaid,
  • 将 peter.jar 放到项目,整个工程就会使用 PeterSaid,
  • 将两个 jar 都放到项目,PeterSaid 有更高的优先级

怎样做到这一点呢?

首先我们需要在你的工程里为 TomSaid 实现一个插件:

public class TomSaidPlugin implements Plugin, Said{
    private Said said;
    public boolean canWork(){
        try {
            said = (Said)(Class.forName("com.you.app.TomSaid").newInstance());
            return true;
        } catch (Exception e) {}
        return false;
    }
    public String say(){
        return said.say();
    }
}

同理,为 PeterSaid 也实现一个插件:

public class PeterSaidPlugin implements Plugin, Said{
    private Said said;
    public boolean canWork(){
        try {
            said = (Said)(Class.forName("com.you.app.PeterSaid").newInstance());
            return true;
        } catch (Exception e) {}
        return false;
    }
    public String say(){
        return said.say();
    }
}

在调用代码里这样实现:

PluginManager<Said> plugins = new SimplePluginManager<Said>(
        "com.you.app.PeterSaidPlugin",
        "com.you.app.TomSaidPlugin");
Said said = plugins.get();
System.out.println(said.say());

上面的代码既不依赖 PeterSaid,也不依赖 TomSaid,完全能满足部署工程师的要求。

采用 SimplePluginManager 有几个注意事项:

  • 插件实现类必须有一个默认的构造函数
  • 插件实现类必须实现目标接口,在上例中就是 Said 接口
  • 插件实现类实际上就是一个被适配目标的一个代理 (在这里,你可以套套“代理模式”)
  • 构造函数参数的顺序,就是插件的优先级,第一个最优先

与 Ioc 容器一起工作

有些时候,你的 Plugin 实现类需要一些配置信息,某些配置信息可能相当复杂。 我们可以将插件同 Ioc 容器 联用(通过IocPlugManager<T>)。

比如我们修改一下上面的两个插件,让它们都需要被配置一个字段:

public class PeterSaidPlugin implements Plugin, Said {
    private String prefix;
    
    private Said said;

    public boolean canWork() {
        try {
            said = (Said) (Class.forName("com.you.app.PeterSaid").newInstance());
            return true;
        } catch (Exception e) {}
        return false;
    }

    public String say() {
        return prefix + said.say();
    }
}

这两个插件,都需要一个 "prefix" 的属性

public class TomSaidPlugin implements Plugin, Said {
    private String prefix;

    private Said said;

    public boolean canWork() {
        try {
            said = (Said) (Class.forName("com.you.app.TomSaid").newInstance());
            return true;
        } catch (Exception e) {}
        return false;
    }

    public String say() {
        return prefix + said.say();
    }
}

在 Ioc 容器的 Json 配置文件中:

// plugins.js
{
    peter : {
        type	: 'com.you.app.PeterSaidPlugin',
        fields	: {
            prefix : 'Peter: '
        }
    },
    tom : {
        type	: 'com.you.app.TomSaidPlugin',
        fields	: {
            prefix : 'Tom: '
        }
    }
}

调用代码改成:

Ioc ioc = new NutIoc(new JsonLoader("conf/plugins.js"));
PluginManager<Said> plugins = new IocPluginManager<Said>(ioc, "peter", "tom");
Said said = plugins.get();
System.out.println(said.say());

最后一点说明

我曾一度怀疑这个插件功能很无聊,因为它真的可以用“简陋”二字来形容(我们实现这个插件用的时间还没有我 写这篇文档所用时间的一半),但是它毕竟给了你一条很简明的途径, 让你的程序可以做到:

部署时才决定某一个接口的实现

你用微小的代价(实现一个接口函数)获得下面两个好处:

  • 你的应用很容易做到 模块化
  • 它几乎 没有侵入性

挺值得的,不是吗? 这也是为什么我们将它放到 Nutz 的核心发布包里原因: (超值的东西才会被放到 Nutz 的核心包里

如果你想做到运行时加载/卸载,这个“简陋”的小插件方案恐怕是帮不上你了。但是你真的需要吗? 我注意到一个事实:Eclipse 采用的是 “OSGI”,但是在安装了一个插件之后它还是会建议你重启应用,每次我看到这个 对话框,都觉得是对 “OSGI” 的一个讽刺。

文件池
日志
温馨提示
下载编程狮App,免费阅读超1000+编程语言教程
取消
确定
目录

表达式引擎

maplist结构

图像处理小军刀

关闭

MIP.setData({ 'pageTheme' : getCookie('pageTheme') || {'day':true, 'night':false}, 'pageFontSize' : getCookie('pageFontSize') || 20 }); MIP.watch('pageTheme', function(newValue){ setCookie('pageTheme', JSON.stringify(newValue)) }); MIP.watch('pageFontSize', function(newValue){ setCookie('pageFontSize', newValue) }); function setCookie(name, value){ var days = 1; var exp = new Date(); exp.setTime(exp.getTime() + days*24*60*60*1000); document.cookie = name + '=' + value + ';expires=' + exp.toUTCString(); } function getCookie(name){ var reg = new RegExp('(^| )' + name + '=([^;]*)(;|$)'); return document.cookie.match(reg) ? JSON.parse(document.cookie.match(reg)[2]) : null; }