欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

从JMS标准看接口编程、模块整合及相关技术jndi,spi等

程序员文章站 2022-07-14 07:54:59
...
    最近看了一篇微信上的文章:是 【码农翻身】公众号的刘欣作者写的Java帝国之JMS的诞生。最近恰好关注到支付宝的开源分布式消息中间件–Metamorphosis(MetaQ)(现在这个项目改名为RocketMQ)非常想看一下源码,但我知道阿里的一般比较复杂,所以从简单的activeMQ开始,当然这个JMS标准产生过程看看更好。
    http://mp.weixin.qq.com/s/3b66QhyPl9sEkCnbgedF2g
一、文章的内容简介:
    简述一下:JMS就是java消息服务,用于不同中模块对象之间的信息交互。比如其中之一P2P,就是一个发送消息,一个接收消息,就结束了,常用在异步处理时,不需要立即由接收方返回结果时(当然异步转同步也是可以的,发送方如果wait(),线程等待结果时,比如DUBBO的同步调用);还有一个主题订阅模式,就是一个人发消息,所以订阅过的人都可以收到消息。
    但是很多厂商已经设计并开发了不同的消息中间件,那么,如果要对不同的产品进行统一标准,要需要对模块接口进行编程,厂商只是实现具体的功能。而模块接口正如文章中村长说:“不要被纷繁的现象迷住了双眼, 要看透背后的本质, 做出适当的抽象才可以。”。最后只剩下了一些:工厂queueConnFactory、队列queue、连接connect、交互session、生产者producer、消费者consumer、消息msg这些基本的概念产生的接口对象。这样基本的JMS标准就有了。这些接口只是一个架构,真正的实现是不同提供商提供的。
    众多产品上升到标准的过程中,如果兼顾这么多产品呢?文章中提到的是JNDI(Java命名和目录接口)技术。这个技术我们一般会在配置数据库的时候会用到,比如配置JNDI数据源。但一个技术产生一定是有原因的,如果知道了原因,就知道如何在恰当的时候使用,以及处理问题的时候想到。

二、JNDI的例子:
    JNDI简单的说可以有一个配置文件,那么就可以按名字从库中找到相应的对象。例子也是网上找的。比如我希望传一个String的地址,返回一个URL对象(或者实现某接口的对象),可以这样。
    1.一个配置文件。有一个name,用来根据名称找对象。有一个type,表示找到的对象是什么接口的对象。还有一个factory后面要实现的,用来产生所要的对象。最后有一个url就是factory所要的相关参数。
<?xml version='1.0' encoding='utf-8'?>
<Context>
    <Resource name="test/url"
    auth="Container"
    type="java.net.URL"
    factory="com.tomcat.jndi.MyURLFactory"
    url="file:///c:/test.properties"/>
</Context>

2.生产所要的对象的类的代码。
public Object getObjectInstance(Object obj, Name name, Context nameCtx,Hashtable environment) throws Exception {
    URL url = null;
    Reference ref = (Reference) obj;
    Enumeration<RefAddr> cfgAttrs = ref.getAll();//这个是取配置文件的所有属性。
    while (cfgAttrs.hasMoreElements()) {//循环查看属性
        RefAddr cfgAttr = cfgAttrs.nextElement();
        String attrName = cfgAttr.getType();//属性的名称
        String attrValue = (String) cfgAttr.getContent();//属性的内容
        if ("url".equals(attrName)) {
            url = new URL(attrValue);//如果找到url属性,就用这个地址产生一个URL对象。当然实际使用中,可以new出最后的对象,设置相关的属性。
        }
    }
    return url;//最终返回产生的对象。
}

3.使用
    Context ctxt = new InitialContext();
    Context envCtxt = (Context) ctxt.lookup("java:comp/env/");
    URL url = (URL) envCtxt.lookup("test/url");//配置中的对象的名字,找到需要的对象。

4. 例子很简单,我们常用的JNDI数据源也是这样,从中可以看出,jndi就是配置后,不用使用基于Class.forName("xxx")的方式来装载类了,把装载过程从硬编码中移到配置中,这样就可以动态整合不同的模块功能了。
    在我之前做过的项目中,也有对接口编程的例子,看到过使用数据库记录相关接口的实现类,并有名称,到时按条件查到后用Class.forName,也是一种动态。

三、目前见的比较多的SPI(Service Provider Interface)机制
    除了上面说过JDNI外,我在DUBBO中看到了很多使用SPI的地方,DUBBO就是微核心设计,所以关注与接口整合功能模块,而且spi用的很复杂,加载的不一定是实际类,而是动态代理类。
    当产生接口框架后,厂商提供了服务接口的一种实现之后,在jar包的META-INF/services/目录里同时创建一个以服务接口命名的文件。该文件里就是实现该服务接口的具体实现类。而当外部程序装配这个模块的时候,就能通过该jar包META-INF/services/里的配置文件找到具体的实现类名,并装载实例化,完成模块的注入。
    同样基于这样一个约定就能很好的找到服务接口的实现类,而不需要再代码里制定。
    简单的例子可以看:http://singleant.iteye.com/blog/1497259
    阿里的DUBBO中全部用的是SPI。

四、总结
    从上面的jms标准到JNDI与SPI的机制可以看到,对于一个产品来说,模块之间的功能关联应该通过接口来实现,而模块内部不一定都要写在接口。而我们想设计一个产品的时候,通过也是抽象的概念,用接口分离出整合关系,这样的产品才是一个微核心,可插拔的产品,更具灵活性。模块之间是通过JNDI或者SPI机制产生具体的实现类。
    平时写代码,通常照猫画虎,ACTION/SERVICE/DAO都写成interface与impl方式。通过上面的介绍,更能理解接口的好处以及通过接口整合模块功能的方式,在开发中也不一定都弄成接口与实现分离的方式吧。

    如果要区分一下什么时候用JNDI,什么时候用SPI。不妨考虑一下熟悉的JNDI的数据源是不是可以用SPI来实现?而SPI的东西是不是可以用JNDI来实现呢?初步感觉JNDI多了配置文件,而且数据源中的很多内容是标准中定义的接口,比如datasource,connection,recordset等等,所以说这种方式已经深入到很多内部的协作了,文章开关讲的JMS用JDNI也是浸入到内部的类的协作了。而用SPI,很多时候只是对外的一个接口的实现,并不是很浸入到实现的模块内部的关系。至于其它的方面还需要以后体会...

五、预告
     前阵了研究了一下Apache commons fileupload的源码,特别是处理文件上传时写的非常好,一边读取流,一边产生内部类的iterator对象,并按boundery分段把输入流写到不同的输出流。下次再写...