???? 此篇文章為《開發(fā)你自己的XMPP IM 續(xù) - Spark 插件開發(fā)》的轉(zhuǎn)發(fā),原文章地址: http://phoenixtoday.blogbus.com/logs/17878527.html
繼續(xù)3月18日介紹基于XMPP IM開發(fā)的那篇Blog,今天主要總結(jié)一下如何基于Spark 的插件架構(gòu)來新增客戶端的功能,這里列舉出一個獲取服務(wù)器端群組信息的實際例子,實現(xiàn)后的效果如下圖所示:


Spark 是一個基于XMPP 協(xié)議,用Java 實現(xiàn)的IM 客戶端。它提供了一些API,可以采用插件機制進行擴展,上圖中,“部門”部分就是使用插件機制擴展出來的新功能。要想實現(xiàn)你的擴展,首先要了解 Spark API的架構(gòu),其中最關(guān)鍵的是要了解它的工廠類,這些工廠類可以獲得Spark 提供的諸如XMPPConnection、ChatContainer 等實例,從而你可以實現(xiàn)獲取服務(wù)器的信息,與另外的Client 通信等功能。最核心的類是SparkManager,這個類是一系列工廠類的工廠類(呵呵,還真拗口)。它的getChatManager()、getSessionManager ()、getMainWindow() 、getConnection() 等方法分別可以獲得聊天管理器、會話管理器、主窗口、與服務(wù)器的連接等等非常有用的實例。基本上可以說SparkManager 是你與Spark 打交道的銜接口。其實,每一個Manager 都使用了單例模式,你也可以不通過SparkManager 來獲取它們,但筆者建議你從單一的入口著手,這樣有利于代碼的開發(fā)和維護。

接下來描述一下插件的開發(fā)流程:
1、創(chuàng)建插件配置文件 plugin.xml
2、實現(xiàn)你自己的Plugin 類的實現(xiàn)(如果你需要實現(xiàn)自己規(guī)定格式的XML 發(fā)送、接收和處理,那么你需要在這里注冊你的IQProvider,關(guān)于IQProvider 你可以查詢Smack API,簡單的來講是處理你自定義的IQ 處理器。)
3、打包你的插件(Spark 有自己的打包機制,我研究了半天才發(fā)現(xiàn)其中的玄機,后面介紹)
4、部署你的插件(其實3、4兩步可以糅合在一起,當然要利用Ant 啦)

好滴,下面結(jié)合一個實際的例子講述上面的四個步驟
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>
????<!-- 關(guān)鍵是這里,這里要定義你的Plugin 類 -->
??? <class>com.im.plugin.IMPlugin</class>
??? <!-- 這里定義你使用的Spark 最低版本 -->
??? <minSparkVersion>2.5.0</minSparkVersion>
??? <os>Windows</os>
</plugin>

這是一個 plugin.xml 文件的內(nèi)容,插件體系會自動調(diào)用你在此文件中定義的Plugin 類,從而完成你自己擴展的功能。最關(guān)鍵的部分我用紅色標識出來了,要聲明你的插件擴展類,采用完整的命名空間方式(包括包名),其余的部分結(jié)合我的注釋, 大家應(yīng)該都能理解,就不做詳細的描述了。要注意的是plugin.xml 文件要放在項目的根目錄下,這是嚴格規(guī)定好的。

2、Plugin 類的實現(xiàn)
你的類首先要實現(xiàn)Spark 提供的Plugin 接口,然后實現(xiàn)它的一些方法。其中最主要的是實現(xiàn)initialize() 發(fā)放,在這里注冊你的的IQProvider?

ProviderManager providerManager = ProviderManager.getInstance();
providerManager.addIQProvider("groups", "com:im:group", //1
??? ??? ??? ??? new GroupTreeIQProvider());
System.out.println("注冊GroupTree IQ 提供者");
requestGroupTree();

上述的代碼,就在該類 就是我實現(xiàn)的IMPlugin.initialize() 方法中的一小段,大概的含義是,先獲取ProviderManager(這個貌似不能從SparkManager 直接獲取),然后注冊一個GroupTreeIQProvider(自己創(chuàng)建的)這是一個IQProvider 的具體實現(xiàn),它用于解析像下面這樣的一個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>西安交通大學(xué)</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>電信學(xué)院</name>
??? ??? ??? ?<upGroup>1</upGroup>
??? ??? ??? ?<isLeaf>1</isLeaf>
??? ??? ??? ?<description>xjtu info</description>
??? ??? </group>
??? </groups>
</iq>

可以看到,在注冊 IQProvider 的時候(代碼中標注的1部分),需要你提供名稱和命名空間,我的XML 文件中的iq 下的第一個子節(jié)點是<groups> 所以我的名稱就寫“groups”,命名空間對應(yīng)于groups 節(jié)點的xmlns(XML Name Space)所以是“com:im:group”,其實IQProvider 中最關(guān)鍵的方法是parseIQ(XmlPullParser parser) 該方法就是解析XML,完成你的功能,并返回一個相應(yīng)的IQ 實例(這里可以把IQ 看做一個回饋的Model 類)。說到底實現(xiàn)基于XMPP 協(xié)議的IM 就是解析XML 文件,而這正是客戶端的IQProvider 和服務(wù)器端的IQHandler(下一篇文章會涉及到)所做的事情。

3、打包你的插件
現(xiàn)在該有的功能都實現(xiàn)了,那么就是打包了。這最好利用Ant 來完成,因為每次你都要打包,要部署,如果純手動的話,那也太不敏捷了,大大影響開發(fā)效率。

<?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"
??? ??? ??? failonerror="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 文件中的內(nèi)容。因為Eclipse 其實幫我自動完成了編譯的任務(wù),所以我也就省去了這寫編譯的步驟,最重要的是大家要看到“jar” 部分,Spark 打包的神秘之處也就在此,打兩次包首先把你的項目打包到本項目lib 文件夾下,比如說你的項目目錄是MyPlugin 那么,你就將你的類打包到MyPlugin/lib 目錄下,然后再次的打包,將所有的lib 文件夾下的內(nèi)容打包起來,記得這次要包含plugin.xml。也就是說,最后Spark 插件體系會讀取你的項目下的lib 文件夾下的內(nèi)容。這里我也有個疑問,我本來想每次打包后自動執(zhí)行bat 文件,啟動插件,看看效果,為啥死都調(diào)用不了呢,那段代碼在最后面,注釋掉了,誰能幫我解決,我請他吃飯滴!

4、最后就是發(fā)布了

其實我的發(fā)布很簡單,就是將這個打包好的jar 文件拷到Spark 本身的plugins 目錄下,每次啟動Spark 的時候,它會自動調(diào)用自定義的插件的。我這里用Ant 第二次jar 的時候,就自動拷貝過去了,這里用的是絕對路徑,所以你不能直接拷貝就用滴呦(是不是很丑陋呀,這段Ant 代碼)。

基本上客戶端的實現(xiàn)原理就是這樣的,只是有些地方需要特別注意,還有就是應(yīng)該利用像Ant 這樣的工具大大簡化開發(fā)步驟,加快開發(fā)效率。還有就是,我建議你在開發(fā)自己的插件的時候,多利用MVC 模式,尤其是在IQProvider 解析后,生成的部分可以實例化Model,然后你可以編寫自己的Manager 進行這些Model 的處理。多寫Log,當然Log4j 貌似不太起作用,那就System.out.println() 吧.