组件开发指南

时间:2022-11-25 15:15:50

原文链接https://community.igniterealtime.org/docs/DOC-1924

1.介绍

XMPP组件增强了XMPP域的功能,他们接收发送到XMPP域的某个子域的所有节。定义了两种组件:“内部组件”,运行在服务器软件上,与服务器软件API有直接联系;“外部组件”,独立运行在服务器软件之外,通过网络协议与XMPP域进行连接。


对组件开发者来说,Tinder提供 org.xmpp.component.Component接口,这个接口可以用来实现XMPP组件,它有两个典型的用法:在openfire插件中使用,可以用来实现一个内部组件;使用Whack库相同的组件接口可以实现外部组件。


大量编写组件接口是一个重复的工作,很多任务,像节的处理、服务发现请求处理和错误处理经常是重复的形式代码的实现。一些需要的功能很容易被忽略,例如确保每个IQ请求都被应答,这使得很多组件破坏了XMPP规范。而这样的问题通过使用组件接口的抽象实现是很容易克服的,Tinder的1.2版本中引入了这个组件接口。


2.抽象组件特性

org.xmpp.component.AbstractComponent实现允许你开发内部组件和外部组件(例如,将它与Whack结合起来),他提供很多特性,包括:

确保每个IQ请求都能应答。XMPP规范声明每个get或set类型的IQ节必须有一个error或者result类型的IQ节应答。抽象组件的实现需要开发者以特定的方法处理IQ请求节,使得结果对请求是有效的应答。如果应答无效,一个error节应该返回给原始的XMPP实体,此外,抽象组件确保在极端情况下也能发送错误响应,例如在关闭组件的时候正在处理一个节或者组件负载很大的情况下。

确保了生产者/生产者模式。组件实现经常是Openfires Achilles’ heel,Openfire使用有限的工作线程池来处理大量的任务,这些线程也被用来调用组件的processPacket()方法,这种方式允许同步操作,但是也增加了Openfire耗尽工作线程的风险。如果这种情况发生了,整个XMPP域就要遭受巨大的损失。每个抽象组件的实例都会使用一个可配置的专用线程池(队列形式的)来处理节,这种处理方式可以保证抽象组件在传送的节入队列后立刻释放(传送节到组件的)线程。此外,如果你的抽象组件耗尽了资源,这个问题通常只会发生在组件内部而不会影响到其它的域。

实现了对服务器发现、最后活跃以及XMPP的ping请求的默认回答。所有的这些XMPP扩展协议实现通常都使用了很多样模板代码。抽象组件使得它们不需要再使用这些模板代码,并且提供简单的方法来扩展这些功能。

允许组件仅仅服务本地域的用户。一个常常被忽略的事实是典型的组件实现服务所有连接到它的XMPP用户,包括本地域的用户和连接过来的用户。有些组件想当然的认为用户都是源于本地域的,只考虑用户的JID节点(这样可能会导致很严重的问题,如果别的域的用户名和本地的用户名一样并试图去访问组件的功能)。抽象组件实现提供一个可配置的交换机,允许定义是否允许别的域的用户使用组件,如果这些用户不被允许,将会返回适当的错误给这些用户发送的节。

3.使用抽象组件

抽象组件默认功能可以通过重写实现该功能的具体的方法来改变。

定义组件身份

通过扩展抽象组件类来创建一个组件的运行实现,你需要定义你组件的身份,通过实现抽象组件的三个抽象类来实现:

getName()必须返回组件的名字,例如“testcomponent”,该名字可以用来生成组件的子域地址。

getDomain()必须返回该组件要连接的XMPP域,例如,“example.org”

getDescription()应该返回组件的文本描述,该描述可用于管理软件

添加功能

对大多数开发者来说,对方法名以handle开始的方法最感兴趣。这样的处理方法存在每一种类型的节:每一个发送到这个组件的消息节都要经过handleMessage(Message)方法。同样,每一个状态节被送到handlePresence(Presence)。四个handle方法对应每一个类型的IQ节:handleIQGet(IQ),handleIQSet(IQ),handleIQResult(IQ),handleIQError(IQ),重写任何一个可选择的处理程序:开发者只需要重写一个方法,如果他希望这个组件处理相应类型的节。

例子

下面的代码展示了如何创建一个组件,该组件简单、静态地响应聊天消息。

import org.xmpp.packet.JID;  
import org.xmpp.packet.Message;

public class HelloComponent extends AbstractComponent {

private JID myAddress = null;

@Override
public String getDescription() {
return "A component that will respond with a friendly "
+ "'hello' to every message it receives.";
}

@Override
public String getDomain() {
return "example.org";
}

@Override
public String getName() {
return "hello";
}

public void initialize(JID jid, ComponentManager componentManager)
throws ComponentException {
this.myAddress = jid;
}

@Override
protected void handleMessage(Message received) {
// construct the response
Message response = new Message();
response.setFrom(myAddress);
response.setTo(received.getFrom());
response.setBody("Hello!");

// send the response using AbstractComponent#send(Packet)
send(response);
}
}
上面的代码展示了如何快速实现一个功能组件,尽管我们只写了几行代码,这个组件将正确地响应它接收的任何XMPP ping、服务发现以及其他IQ请求(最后一个目录将响应服务不可用错误的响应)。

优化服务器发现响应

我们选择特定的namespace来识别我们的功能,“example:hello”,我们的组件应该将这个namespace添加到特性列表。

抽象组件允许你添加新的特征到服务器发现应答,通过重写discoInfoFeatureNamespaces()方法,这个方法返回一个namespaces的数组,namespaces被添加到识别实现的特征的namespaces列表中,这个例子展示了“example:hello”的namespace是如何被添加到包含在服务器发现应答的特征列表中去的:

@Override  
protected String[] discoInfoFeatureNamespaces() {
String[] ns = { "example:sayhello" };
return ns;
}
修改线程池

每一个抽象组件实例都从专用线程池里使用线程,默认情况下,多达17个线程在使用中,1000个节可以排队等待可用的线程来处理。

我们的Hello组件处理请求将会非常的快,我们可用安全的调整线程的使用数量,要做到这一点,我们得确保组件是实例化使用构造器来重写默认数量的线程。

public HelloComponent() {  
super(2, 1000, true);
}

4.我想改进我的组件

“但是我的组件不能够及时响应每一个IQ请求”

抽象组件需要开发者对每一个处理过的IQ请求节返回一个有效的应答,空应答将被转换成服务不可用错误,当该技术声明每一个IQ请求必须被应答的时候需要实现去遵循XMPP规范。

不是每个组件能够立刻应答请求,如果组件很长时间才响应,那么使线程池里的线程保持等待就没有任何意义了,现有组件实现并不总是那么容易用和抽象自己设置匹配的方式重写,这种情况下,停止抽象组件执行每一个IQ请求的响应将会比较有帮助。

抽象组件允许你关闭每一个IQ都必须响应的要求,其中IQ请求由函数handleIQSet()或者handleIQGet()处理,通过将在抽象组件的构造器里的Boolean标志设置为false,当函数handleIQSet()或者handleIQGet()返回值为null时,抽象组件就不会再用服务不可用错误来响应。

public HelloComponent() {  
super(17, 1000, false);
}

这样做之后,确保每个IQ请求都被响应的责任就落在开发者身上了。