今天主要总结一下如何基于Spark 的插件架构来新增客户端的功能,这里列举出一个获取服务器端群组信息的实际例子,实现后的效果如下图所示: Spark 是一个基于XMPP 协议,用Java 实现的IM 客户端。它提供了一些API,可以采用插件机制进行扩展,上图中,“部门”部分就是使用插件机制扩展出来的新功能。要想实现你的扩展,首先要了解 Spark API的架构,其中最关键的是要了解它的工厂类,这些工厂类可以获得Spark 提供的诸如XMPPConnection、ChatContainer 等实例,从而你可以实现获取服务器的信息,与另外的Client 通信等功能。最核心的类是SparkManager,这个类是一系列工厂类的工厂类(呵呵,还真拗口)。它的getChatManager() 、getSessionManager ()、getMainWindow() 、getConnection() 等方法分别可以获得聊天管理器、会话管理器、主窗口、与服务器的连接等等非常有用的实例。基本上可以说SparkManager 是你与Spark 打交道的衔接口。其实,每一个Manager 都使用了单例模式,你也可以不通过SparkManager 来获取它们,但笔者建议你从单一的入口着手,这样有利于代码的开发和维护。接下来描述一下插件的开发流程:1、创建插件配置文件 plugin.xml2、实现你自己的Plugin 类的实现(如果你需要实现自己规定格式的XML 发送、接收和处理,那么你需要在这里注册你的IQProvider,关于IQProvider 你可以查询Smack API,简单的来讲是处理你自定义的IQ 处理器。)3、打包你的插件(Spark 有自己的打包机制,我研究了半天才发现其中的玄机,后面介绍)4、部署你的插件(其实3、4两步可以糅合在一起,当然要利用Ant 啦) 好滴,下面结合一个实际的例子讲述上面的四个步骤 1、plugin.xml <plugin> <name>Enterprise IM Client</name> <version>1.0</version> <author>Phoenix</author> <homePage>http://phoenixtoday.blogbus.com</homePage> <email>phoenixtoday@gmail.com</email> <description>Enterprise Client Plug-in</description> <!-- 关键是这里,这里要定义你的Plugin 类 --> <class>com.im.plugin.IMPlugin</class> <!-- 这里定义你使用的Spark 最低版本 --> <minSparkVersion>2.5.0</minSparkVersion> <os>Windows</os> </plugin>这是一个 plugin.xml 文件的内容,插件体系会自动调用你在此文件中定义的Plugin 类,从而完成你自己扩展的功能。最关键的部分我用红色标识出来了,要声明你的插件扩展类,采用完整的命名空间方式(包括包名),其余的部分结合我的注释,大家应该都能理解,就不做详细的描述了。要注意的是plugin.xml 文件要放在项目的根目录下,这是严格规定好的。2、Plugin 类的实现你的类首先要实现Spark 提供的Plugin 接口,然后实现它的一些方法。其中最主要的是实现initialize() 发放,在这里注册你的的IQProvider ProviderManager providerManager = ProviderManager.getInstance();providerManager.addIQProvider("groups", "com:im:group", //1 new GroupTreeIQProvider());System.out.println("注册GroupTree IQ 提供者");requestGroupTree();上述的代码,就在该类就是我实现的IMPlugin.initialize() 方法中的一小段,大概的含义是,先获取ProviderManager(这个貌似不能从SparkManager 直接获取),然后注册一个GroupTreeIQProvider(自己创建的)这是一个IQProvider 的具体实现,它用于解析像下面这样的一个XML 文件:<?xml version="1.0" encoding="UTF-8"?><iq type='result' to='domain@server.com' from='phoenixtoday@gmail.com' id='request_1'> <groups xmlns='com:im:group'> <group> <groupId>1</groupId> <name>西安交通大学</name> <upGroup>ROOT</upGroup> <isLeaf>0</isLeaf> <description>xjtu</description> <user> <userGroupId>1</userGroupId> <userName>phoenix_test</userName> <role>normal</role> </user> </group> <group> <groupId>2</groupId> <name>电信学院</name> <upGroup>1</upGroup> <isLeaf>1</isLeaf> <description>xjtu info</description> </group> </groups></iq>可以看到,在注册 IQProvider 的时候(代码中标注的1部分),需要你提供名称和命名空间,我的XML 文件中的iq 下的第一个子节点是<groups> 所以我的名称就写“groups”,命名空间对应于groups 节点的xmlns(XML Name Space)所以是“com:im:group”,其实IQProvider 中最关键的方法是parseIQ(XmlPullParser parser) 该方法就是解析XML,完成你的功能,并返回一个相应的IQ 实例(这里可以把IQ 看做一个回馈的Model 类)。说到底实现基于XMPP 协议的IM 就是解析XML 文件,而这正是客户端的IQProvider 和服务器端的IQHandler(下一篇文章会涉及到)所做的事情。3、打包你的插件现在该有的功能都实现了,那么就是打包了。这最好利用Ant 来完成,因为每次你都要打包,要部署,如果纯手动的话,那也太不敏捷了,大大影响开发效率。<?xml version="1.0" encoding="UTF-8"?><project name="IM" default="release" basedir="."> <property name="src.dir" value="src" /> <property name="dest.dir" value="bin" /> <property name="lib.dir" value="lib" /> <property name="im.path" value="E:/workspace/europa/spark_new/doc/spark/target/build" /> <target name="clean"> <!-- <delete dir="${dest.dir}" /> <delete dir="${lib.dir}" /> --> </target> <target name="init" depends="clean"> <!-- <mkdir dir="${dest.dir}" /> <mkdir dir="${lib.dir}" /> --> </target> <target name="build" depends="init"> <!-- <javac srcdir="${src.dir}" destdir="${dest.dir}" /> --> </target> <!-- 最重要的是这里,打两次包 --> <target name="jar" depends="build"> <jar jarfile="${lib.dir}/eim.jar" basedir="${dest.dir}" /> <jar jarfile="${im.path}/plugins/eim.jar"> <fileset dir="."> <include name="lib/*.jar" /> </fileset> <fileset dir="."> <include name="plugin.xml" /> </fileset> </jar> </target> <target name="release" depends="jar"> <!-- <exec executable="cmd.exe" failοnerrοr="true"> <arg line="/c e:"/> <arg line="/c cd workspace\europa\spark_new\doc\spark\target\build\bin"/> <arg line="/c startup.bat"/> </exec> --> </target></project>这是我的这个项目的 build.xml 文件中的内容。因为Eclipse 其实帮我自动完成了编译的任务,所以我也就省去了这写编译的步骤,最重要的是大家要看到“jar” 部分,Spark 打包的神秘之处也就在此,打两次包首先把你的项目打包到本项目lib 文件夹下,比如说你的项目目录是MyPlugin 那么,你就将你的类打包到MyPlugin/lib 目录下,然后再次的打包,将所有的lib 文件夹下的内容打包起来,记得这次要包含plugin.xml。也就是说,最后Spark 插件体系会读取你的项目下的lib 文件夹下的内容。这里我也有个疑问,我本来想每次打包后自动执行bat 文件,启动插件,看看效果,为啥死都调用不了呢,那段代码在最后面,注释掉了,谁能帮我解决,我请他吃饭滴!4、最后就是发布了其实我的发布很简单,就是将这个打包好的jar 文件拷到Spark 本身的plugins 目录下,每次启动Spark 的时候,它会自动调用自定义的插件的。我这里用Ant 第二次jar 的时候,就自动拷贝过去了,这里用的是绝对路径,所以你不能直接拷贝就用滴呦(是不是很丑陋呀,这段Ant 代码)。基本上客户端的实现原理就是这样的,只是有些地方需要特别注意,还有就是应该利用像Ant 这样的工具大大简化开发步骤,加快开发效率。还有就是,我建议你在开发自己的插件的时候,多利用MVC 模式,尤其是在IQProvider 解析后,生成的部分可以实例化Model,然后你可以编写自己的Manager 进行这些Model 的处理。多写Log,当然Log4j 貌似不太起作用,那就System.out.println() 吧,哈哈!今天就写到这里啦,偶有点累啦。