| « | September 2010 | » | | 日 | 一 | 二 | 三 | 四 | 五 | 六 | | | | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | | | |
|
公告 | Log4J = blog for Java
Moved to blog.baturu.com/
GTalk: 
|
| Blog信息 | blog名称:Open your thoughts 日志总数:122 评论数量:90 留言数量:2 访问次数:446860 建立时间:2004年11月9日 |

|
|
|
[J2EE]JMS HOW-TO from sun developers
Nurhachi.Zhang 发表于 2005-1-21 16:36:02 |
|
使用JMS队列
Java消息发送服务(Java Messaging Service,JMS)
是提供商无关的一套API,用于在程序间进行可靠的消息发送。在客户端-服务器计算中,客户端程序与服务器与服务器建立联系并请求服务。相反,消息发送应
用在相互协作的程序之间发送消息。有些程序(在所谓的“对等(peer-to-peer)”应用中)则相互之间直接交换信息(JXTA使用的就是这种模
型)。
这两种类型的连网方式如下图所示:
500)this.width=500'>
JMS提供了一个中间件消息代理(message broker),后者提供了程序间可靠的、事务性的消息发送。
下面的图演示了点到点的消息发送,这正是本期中要讲到的一种消息发送方式。
500)this.width=500'>
虽然这些图中显示的消息提供者和消费者都是物理上的机器,实际上它们也可以是运行在一台或者多台机器上的相互协作的一些进程。
JMS提供者是一个程序,它实现了JMS公共接口中的JMS服务约定。J2EE平台规范要求平台实现包含一个JMS提供者。
大多数人都比较熟悉客户端-服务器模型的消息发送,在这种模型中,客户端程序与服务器建立联系,请求服务、数据,或者同时请求服务和数据。相反,JMS提供了一种更丰富的消息发送模型,这种模型具有以下高级特性:
可靠的消息发送。当消息发送者正在发送消息时,消息接受者无需处于运行状态。而是等等接受者下次做好准备时,再将消息发送到接受者手上。点到点或者发布/订阅式的消息发送模型。消息的传送可能是一对一的,也可能是一对多的。事务。消息发送可作为一个分布式事务的一部分。同步的或异步的消息发送。消息生产者可能会等待接受者的确认,也可能不会。面向对象的消息发送。JMS允许在客户端之间发送对象,而不是通过使用一些协议来发送结构化的数据。遗留整合。JMS可以与底层的第三方消息发送系统进行整合。
本期解释了如何使用JMS消息队列来实现两个进程之间简单、可靠的点到点的消息发送。发布/订阅式的消息发送将在后面的期“Enterprise
Java Technologies Tech Tips”中讲述。
JMS队列术语
JMS
使用消息队列的概念来实现点到点的消息发送。在点到点的消息发送中,总是有一个明确的消息生产者和一个消息消费者。在点到点的消息发送中,与时间没有多大
的关系,除非消息发送者为消息定义了一个期限。消息接受这可以接收由消息生产者在过去任何一个时候发送的消息,即使在该消息被编入队列时消息消费者没有处
于运行状态。
下面的图显示了一个点到点的消息发送场景。
500)this.width=500'>
JMS提供者是一个消息发送服务器,它负责处理消息的持久性、超时、重发、事务回滚以及由JMS提供的其他服务。对于J2EE
SDK这种情况,JMS提供者是J2EE服务器程序的一部分。消息生产者发送对象到由JMS维护的一个队列中。消息消费者则从该队列接收消息,并发出确认,表示已经收到消息。
JMS规范定义了一些可用于在进程间发送消息的对象:
JMS管理的对象。有两种JMS管理的对象:目的地和连接工厂。这两种对象都是由系统管理员通过环境管理工具创建的。目的地。这是一种服务器端的对象,通过这个对象进行消息的发送和接收。JMS队列就是一种目的地对象。连接工厂。这是一种服务器端的对象,负责配置和创建到一个特定目的地的连接。JMS 队列的JMS连接工厂就是一种QueueConnectionFactory。连接。这是一种到一个JMS提供者(而不是到一个目的地)的虚拟连接。它被用来创建会话。用于访问队列的一个连接就是一种QueueConnection。会话。这是一个潜在的消息传送和接收的事务性的工作单元。会话用于创建消息、消息生产者和消息消费者。用于访问队列的一个会话就是一种QueueSession。消息。这是一种可以从消息目的地发送到消息消费者的对象。消息的类型随要发送的对象类型的不同而不同。例如,为本期提供的示例代码就使用了TextMessage对象。消息生产者。这是一种由JMS客户端程序使用的、用于发送消息到目的地的对象。JMS客户端程序从一个会话中获取消息生产者对象。程序可以使用QueueSender这种类型的消息生产者对象来发送消息到一个队列中。消息消费者。这是一种由JMS客户端程序使用的、用于从一个目的地接收消息的对象。JMS客户端程序从一个会话中获取消息接受者对象。程序可以使用QueueReceiver这种类型的消息消费者对象来从一个队列中接收消息。
这里颇有几个新的术语。现在让我们看看如何发送消息。以下步骤展示了从与本期一起提供的示例代码中抽出的一些例子。(想知道如何下载和运行该示例代码,请参考运行示例代码一节。)不过,在运行示例代码之前,你需要配置一下服务器(参见 配置服务器一节)。
发送消息
J2EE参考实现预先配有一个队列连接工厂(名为QueueConnectionFactory)和一个队列(名为jms/Queue)。如果你使用的是JMS服务器,而不是参考实现,或者如果你想试着更改队列连接工厂和/或队列队列的名称,请参考配置服务器一节。
下面是通过一个JMS队列发送消息的步骤。从示例程序TestQueue中抽出的代码片段也穿插在这些步骤中。
通过按名字在JNDI中进行查找,获得一个指向QueueConnectionFactory 的引用:
protected static String qfactoryName =3.
"jms/queue/TechTipsQueueConnectionFactory";4.
...
try
{
//
获得JNDI上下文
InitialContext
ctx = new InitialContext();
//
获得连接工厂
QueueConnectionFactory
qcf =
(QueueConnectionFactory)ctx.lookup(qfactoryName);
2.从 QueueConnectionFactory获得一个QueueConnection,再从这个连接获得一个QueueSession
。按名字在JNDI中查找:
// 获得一个到队列的连接
qc = qcf.createQueueConnection();
//从该连接获得一个会话
QueueSession qs
= qc.createQueueSession(
false,
Session.AUTO_ACKNOWLEDGE);
// 获得一个队列
Queue q = (Queue)ctx.lookup(queueName);
3. 使用QueueSession创建一个QueueSender,将该队列作为一个参数来传递。(QueueSender是QueueSession与某个特定队列之间的一个关联。):
// 使用这个会话创建一个QueueSender
// and a TextMessage.
QueueSender qsnd = qs.createSender(q);
现在可以用QueueSender来发送消息到队列。
4. 创建一个消息对象(Message的子类),然后使用QueueSender的发送方法将它们发送至目的地。示例程序从Session中获得一个TextMessage对象。接着示例程序将每个程序参数打包到TextMessage
中,然后使用QueueSender将其发送至队列。注意,同一个TextMessage 可以使用多次。
TextMessage tm = qs.createTextMessage();
// 为第一个参数之后的每个参数进行一次循环
// 以文本消息的形式发送参数字符串
for (int i = 2; i <
args.length; i++) {
tm.setText(args[i]);
qsnd.send;
}
5.关闭QueueConnection。在try/finally程序块的最后一条语句中关闭连接是一个好习惯。这一步很重要:忘记关闭
QueueConnections 将可能导致服务器上的资源泄漏:
} finally
{
if
(qc != null) {
qc.close();
}
}
要发送消息到一个消息队列,可以使用TestQueue程序(在缺省的包中),加上一个参数“send”,例如:
$ java TestQueue
send jms/queue/MyTestQueue a b c d
Java
Message Service 1.0.2 Reference
Implementation
(build b14)
Sent:
'a'
Sent:
'b'
Sent:
'c'
Sent:
'd'
接收消息
TestQueue程序按照以下步骤接收消息:
和2从一个消息队列接收消息的起先两步与发送消息是一样的:先是查找连接工厂,再获得一个QueueConnection,然后查找Queue。
从QueueSession 获得一个QueueReceiver :
QueueReceiver qrcv = qs.createReceiver(q);
3. 从Queue接收消息。示例程序进入一个循环,在这个循环中从队列获取消息并将它们打印到标准输出设备。
qc.start();
Message
m = qrcv.receive(10000);
while
(m != null) {
if (m instanceof TextMessage)
{
TextMessage tm =
(TextMessage)m;
System.out.println("Received
text: '" +
tm.getText()
+ "'");
}
else {
System.out.println("Received
a " +
m.getClass().getName());
}
m
= qrcv.receive(100);
} finally {
if
(qc != null) {
qc.close();
}
}
对QueueConnection
的启动方法的调用将告诉连接开始接收消息。QueueReceiver接收方法带有一个参数,该参数表明了等待一条消息的毫秒数。如果消息没有在规定时间
内到达,该方法将返回null值。直到消息队列在100毫秒的时间内都保持为空,程序才开始读取和打印收到的消息。在try/finally程序块的结尾
有一个finally子句,该子句像前面例子中一样地关闭QueueConnection。
要接收消息队列中的消息,可以使用TestQueue程序,再带上一个参数“recy”,例如:
$ java TestQueue
recv jms/queue/MyTestQueue
Java
Message Service 1.0.2 Reference
Implementation
(build b14)
Received
text: 'a'
Received
text: 'b'
Received
text: 'c'
Received
text: 'd'
配置服务器
如
果你使用的不是参考引用,或者你想更改队列和/或队列连接工厂的名字,你就需要通过使用平台的管理工具配置JMS提供者。队列或者连接工厂被创建之后,便
留在服务器中,直到服务器重新启动。有些平台实现可能会为客户端提供扩展API,以便程序化地创建目的地和连接工厂。
用来在J2EE SDK下创建受管理的对象的工具是。要配置服务器,需:
启动应用服务器。 Create a message queue, giving it a JNDI name. (Do
this only once.) To create a new message queue in
the J2EE SDK, first decide on a name for your message
queue. It can be convenient for administration purposes
to choose a name that indicates that the queue is
used by JMS, for example, jms/MyTestQueue. Then execute
the command:创建一个消息队列,并为它取一个JNDI名。(只做一次。)要用J2EE SDK创建一个新的消息队列,首先需要为消息队列决定一个名字。为了便于管理,
应该选择一个可以表明该队列是JMS使用的队列,例如jms/MyTestQueue。然后执行命令:
j2eeadmin -addJmsDestination <queuename> queue
用消息队列的实际名字替换<queuename>
。
由于连接工厂名QueueConnectionFactory被硬性地放在示例程序中,因此要告诉程序使用一个不同的连接工厂名,就必须修改源代码,并且重新编译。例如,在TestQueue.java中:
// Change this variable's value to change the
// connection factory name
protected static String qfactoryName =
"QueueConnectionFactory"
重新编译之后,使用j2eeadmin(或者你自己的服务器的工具)创建一个connection factory,给它取个JNDI名,如:
j2eeadmin -addJmsFactory
jms/MyQueueConnectionFactory
要列出连接工厂,使用:
j2eeadmin -listJmsFactory
要列出目的地,使用:
j2eeadmin -listJmsDestination
对于不是J2EE SDK的平台,可以在该平台上使用J2EE产品的部署工具来创建所需的消息队列和连接工厂。
运行示例代码
下载这些期的示例存档。这个JAR文件包含了示例程序的完整源代码,包括Java程序文件和HTML格式的文件。你可以使用命令jar
xvf ttmar2003.jar 将示例jar中的内容解压缩到你的工作目录下。在运行示例程序前,要确保J2EE服务器正在运行。
多次运行示例程序。试着发送一些成批的消息,但是不接收这些消息,检查一下输出的顺序。
这个示例程序为你试着使用JMS队列提供了切入点。探索一下JMS接口使用的API。例如,可以更改一下程序,试着使用三种不同的接收方式。探索一下事务性的消息发送,或者试着发送各种类型的消息对象。JMS有许多特性,熟练使用它可以提高你的应用设计水平。
利用JMS Topic发布/订阅消息
2003年3月11日那一期(使用JMS Queue) 解释了如何使用Java消息服务(Java Messaging
Service,JMS)Queue进行点到点的消息发送。下面这一技巧将解释如何使用JMS Topic实现发布/订阅式的消息发送。
发布/订阅式的消息发送
在发布/订阅式的消息发送中,一个发布者利用一个方法调用将每条消息发送给多个预订者。介于发布者和预订者之间的是一台消息服务器。在JMS中,消息服务器被叫做“JMS提供者”。发布者发送消息到JMS提供者,预订者从JMS提供者接收消息。
下图演示了这一方案。
500)this.width=500'>
在JMS中,发布/订阅式的消息发送使用JMS管理的一个叫做Topic的对象来管理发布者到预订者的消息流。JMS发布者又叫做消息生产者,而JMS预订者又叫做消息消费者。消息生产者获得服务器上一个JMS
Topic的引用,并向该Topic发送消息。当消息到达时,JMS提供者负责通知所有预订了该Topic的消息消费者。JMS提供者每次发送消息后(可选地)将接收到消息收据的确认。
这一过程描述如下:
500)this.width=500'>
使用JMS Topic的发布/订阅式消息发送在几方面类似于点到点的消息发送。以下是两种消息发送方式共同的特点:
消息发送可以是面向对象的,允许将整个的对象作为消息发送。消息发送可以是事务性的。消息发送可以是同步或异步的。消息发送可以与基础的第三方产品集成。消息可以发送给在消息发送时不在运行的消息消费者(即QueueReceiver或TopicSubscriber)。消息一旦被递送到队列或主题,发送消息的函数调用就会立即返回。可以显式地或自动地确认收到消息。
发布/订阅式消息发送与点到点消息发送之间也有几点不同:
发布/订阅式消息发送是一对多的,而点到点消息发送是一对一的。发布的消息只递送给Topic当前的预订者。客户只能接收到他向一个Topic预订了的那些消息。而在点到点消息发送中,永久的消息将一直在Queue中,直到它超时或者某个接收者来取走该消息。发
布/订阅式消息发送中的永久消息是由“耐久的预订”提供的。JMS提供者存储由于预订者出于某种原因不可用而无法递送给预订者的消息。在下次预订者连接上
的时候,这些存储的消息将会被递送给他。这确保了客户预订一个Topic之后,所有发布的消息都会递送给他,哪怕该预订者不是总在运行。如果预订不是耐久
的,在预订者掉线时发布的任何消息都不会递送到预订者。
发布/订阅消息和点到点消息发送没有优劣之分,它们是相互补充的工具,各自用于不同的目的。点到点消息发送通常用于消息接收者在一个系统内有惟一的标识的情况下。发布/订阅式消息发送更多地用于一个系统中的几个代理需要知道某个事件或条件何时出现这种情况下。
JMS
消息发送模型非常类似于常规的Java
2编程中的事件侦听器。点到点消息发送就像一个单播事件侦听器模型,而发布/订阅消息就像一个组播侦听器模型。传统的Java事件侦听器与JMS(不同于
编程语法)之间的差别是,事件源和侦听器分别叫做消息生产者和消费者。JMS消息生产者和消费者可以运行在不同的地址空间,甚至是在不同的机器上。JMS
消息发送还提供比传统的事件侦听器模型所实现的更高级别的服务。不过基本的消息发送模型是相同的。
该技巧的示例代码由三个程序组成:
一个servlet,名字叫做PublishWeatherServlet,它向JMS Topic发布一个XML格式的天气报告。一个命令行Java应用程序,名字叫做WeatherReceiver,它向Topic发出预订并打印接收到的XML消息。一个GUI应用客户端,名字叫做WeatherClient,它解析并以图形方式显示XML消息中的数据。
下面是用于发布天气报告的HTML页面和Web表单、运行命令行预订者的终端会话和GUI应用的屏幕快照:
500)this.width=500'>
点击这里看大图
发布消息到Topic
名
叫PublishWeatherServlet的servlet从HTML表单接收POST参数,并转化为XML格式,然后使用JMS
Topic将产生的XML文档发布到所有的侦听器。该servlet中的大多数代码用于接收POST参数并将它们转化为XML文档。代码的有趣部分在于发
布方法。该方法接收一个String参数,其中包含有将发布的XML文本。下面我们来仔细研究发布方法,看它是如何发布到JMS Topic的:
1.Get a TopicConnectionFactory
and a Topic.
protected void
publish(String text) {
TopicConnectionFactory
tcf = null;
Topic topic =
null;
try
{
Context
jndiContext = new InitialContext();
tcf
= (TopicConnectionFactory)10.
jndiContext.lookup(11.
"java:comp/env/jms/TopicConnectionFactory");12.
topic
= (Topic) jndiContext.lookup(13.
"java:comp/env/jms/Topic");14.
}
catch (NamingException nameEx) {
System.err.println(nameEx.toString());16.
}
该
代码使用Java命名和目录接口(Naming and Directory
Interface,JNDI)API在JMS提供者上查找两个对象:Topic和TopicConnectionFactory。该servlet将发
送消息到Topic。TopicConnectionFactory用于创建一个到JMS提供者的连接。请注意该servlet用于查找这些对象的名称。
记住,J2EE应用中所有对象的JNDI API名称都应该以java:comp/env/打头
Create a
Connection, Session, and Publisher.
TopicConnection tc =
null;
try { 21.
tc
= tcf.createTopicConnection();
TopicSession
ts =
tc.createTopicSession(
false,
Session.AUTO_ACKNOWLEDGE);
TopicPublisher
tp =
ts.createPublisher(topic);
该
代码使用从JNDI
API得到的TopicConnectionFactory来创建TopicConnection。TopicConnection可用于创建
TopicSession。用于创建TopicSession的参数告诉连接创建一个不是事务性的TopicSession,并且自动地确认消息收到。
(如果消息递送是事务性的,那么在同一TopicSession中发送的所有消息将形成一个工作单元,该单元可以被提交或回滚)。然后,使用
Session创建TopicPublisher,TopicPublisher充当消息发布的通道的角色。
注
意,J2EE
2.0规范指出,JMS消息的事务和确认是由J2EE容器管理的。这意味着如果代码运行在容器中,那么这些参数就可以被忽略。遗憾的是,并不是所有的提供
商都会按照这一要求去做。如果事务、确认或者这二者对您的应用很重要,请务必检查您的产品文档,或者自己测试这些参数的行为。这些参数应该像在容器外工作
一样。
Create and
publish the message.
TextMessage
textMessage =
ts.createTextMessage();
textMessage.setText(text);
tp.publish(textMessage);
...
} // End
of method publish
发布消息很简单。TopicSession充当创建新的TextMessage的工厂。该代码将TextMessage的文本设置为包含将发送的XML的字符串,然后使用TopicPublisher将消息发布到Topic。
发布方法到此就介绍完了。JMS提供者负责将消息递送给所有的预订者。
向Topic预订消息和接收消息
命
令行程序WeatherReceiver向Topic预订消息并打印出从该Topic接收到的任何消息。为了简化,预订Topic的过程被封装在辅助类
SubscriptionHelper中。WeatherReceiver类充当一个异步消息接收者,并实际执行输出操作。
向一个Topic预订消息
以下的代码来自类SubscriptionHelper,创建对一个Topic的预订:
protected TopicConnection
_tc;
...
public SubscriptionHelper(String tcfName,
String topicName,
MessageListener listener) {
// Get references to topic connection factory
// and topic. \
_tc = null;
TopicConnectionFactory tcf = null;
Topic topic = null;
try {
InitialContext ic = new InitialContext();
tcf = (TopicConnectionFactory)
ic.lookup(tcfName);
topic = (Topic) ic.lookup(topicName);
} catch (NamingException e) {
System.err.println(e.toString());
e.printStackTrace(System.err);
}
try {
// Create a connection and so on
// Subscribe self to topic--messages will be
// delivered to this.onMessage()
_tc = tcf.createTopicConnection();
TopicSession ts =
_tc.createTopicSession(
false, Session.AUTO_ACKNOWLEDGE);
TopicSubscriber tsub =
ts.createSubscriber(topic);
tsub.setMessageListener(listener);
} catch (JMSException e) {
System.err.println(e.toString());
e.printStackTrace(System.err);
close();
}
}
SubscriptionHelper
类的大部分等同于发布者代码。它使用JNDI API来获得对Topic
和TopicConnectionFactory的引用,并创建TopicConnection和TopicSession对象。但是该类不是创建一个
TopicPublisher,而是创建一个TopicSubscriber,并将TopicSubscriber的消息侦听器设置为已经传递进来的
MessageListener。从此以后,每当该Topic接收到一条消息,该消息就会被递送到MessageListener的onMessage方
法。因为这种方式中使用了一个回调,因此该例子演示了异步消息接收。
2.Receiving Messages
接
收消息惟一的要求是类实现接口javax.jms.MessageListener。WeatherReceiver类本身是一个
MessageListener。MessageListener接口只有一个方法:onMessage。WeatherReceiver的
onMessage方法出现在下面:
public class WeatherReceiver implements
MessageListener
{
// Print a weather
message when it is received
public void onMessage(Message
message) {
try {
if (message instanceof TextMessage) {
TextMessage m = (TextMessage) message;
System.out.println(
"--- Received weather report");
System.out.println(m.getText());
System.out.println("----------");
} else {
System.out.println(
"Received
message of type " +
message.getClass().getName());
}
}
catch (JMSException e) {
System.err.println(e.toString());
e.printStackTrace(System.err);
}
}
...
public static void
main(String[] args) {
if
(args.length != 2) {
System.out.println(
"Usage: WeatherReceiver " +
"topicConnectionFactorName topicName");
System.exit(1);
}
// Create
a receiver, then set it up to listen
// for messages
on the topic. Then wait for
// messages
and print them as they come in.
WeatherReceiver
wr = new WeatherReceiver();
SubscriptionHelper
sh =
new SubscriptionHelper(args[0], args[1], wr);
// Wait for publications...
System.out.println(
"Waiting for publications to topic " +
args[1]);
sh.waitForMessages();
}
WeatherReceiver
的主方法创建一个WeatherReceiver实例和一个SubscriptionHelper实例。它向SubscriptionHelper传递应
用将会使用的WeatherReceiver、Topic名称和TopicConnectionFactory(这些参数在命令行指定)。
SubscriptionHelper实例创建预订。然后主方法将WeatherReceiver注册为来自Topic的消息的消费者。
onMessage方法只是在适当的地方将接收到的Messages转化为类TextMessage,并输出接收到的XML文档。
注意,在Web层使用JMS侦听器是一个坏主意。实际上,J2EE 1.3参考实现不允许这样做。服务器端JMS侦听器在EJB层被适当模型化为消息驱动Bean。
部署Web应用
有
了JMS,发布/订阅式消息发送的代码就很容易。但是,部署描述文件提出了一个需要解决的问题。PublishWeatherServlet是一个使用
JNDI
API查找外部组件的Web组件。Web组件使用编码名称查找外部资源(比如Topics和TopicConnectionFactories)。部署描
述文件必须将这些编码名称定义为资源引用或资源环境引用。下面从Web应用的部署描述文件web.xml抽取出的代码定义了servlet使用的编码名称
(该代码出现在web.xml中的<welcome-file-list>之后):
<!-- JMS
topics and connection factories used -->
<resource-env-ref>
<resource-env-ref-name>
jms/Topic
</resource-env-ref-name>
<resource-env-ref-type>
javax.jms.Topic
</resource-env-ref-type>
</resource-env-ref>
<resource-ref>
<res-ref-name>
jms/TopicConnectionFactory
</res-ref-name>
<res-type>
javax.jms.TopicConnectionFactory
</res-type>
<res-auth>
Container
</res-auth>
</resource-ref>
resource-env-ref块定义名称“jms/Topic”的类型为javax.jms.Topic。字符串“jms/Topic”是用于查找Topic
("java:comp/env/jms/Topic")的字符串,其中“java:comp/env/”部分删除了。产品的部署工具允许应用部署人员将这一名称映射到环境中的一个Topic。
在J2EE参考实现中,这一映射已经预先配置在文件META-INF/sun-j2ee-ri.xml中的Web档案中。该文件是Web应用的运行时部署描述文件。部署描述文件在名称和内容方面都是特定于提供商的。
resource
-ref块定义了TopicConnectionFactory的名称、类型和授权模式。通常,部署人员会使用部署工具将编码名称
jms/TopicConnectionFactory与平台中的TopicConnectionFactory相关联。J2EE参考实现预配置了在
JNDI中命名空间中叫做jms/TopicConnectionFactory的TopicConnectionFactory。
|
|
|
回复:JMS HOW-TO from sun developers
fdasd(游客)发表评论于2008-5-5 19:05:39 |
|
|
回复:JMS HOW-TO from sun developers
不会(游客)发表评论于2008-5-15 16:32:24 |
|
9 1 :
|