Java消息服务与JNDI使用指南
1. JMS消息处理基础
当同一个Session对象处理多条消息时,对一条消息的确认会影响同一会话中的所有消息。在消息处理中,发布消息和订阅主题是两个重要的操作。
1.1 消息发布
程序向主题发布消息,这些主题需由消息中间件(MOM)系统管理员提前创建。多个订阅者可以获取发布到同一主题的消息,这就是所谓的一对多模式。消息发布与消息发送非常相似,但程序需要创建Topic而不是Queue,创建Publisher而不是Sender,并调用publish()方法而不是send()方法。以下是发布消息到主题的示例代码:
TopicConnection connection = connectionFactory.createTopicConnection();
TopicSession pubSession = connection.createTopicSession(false,
Session.AUTO_ACKNOWLEDGE);
Topic myTopic = pubSession.createTopic (“Price_Drop_Alerts”);
TopicPublisher publisher = pubSession.createPublisher(myTopic);
connection.start();
TextMessage message = pubSession.createTextMessage();
message.setText(“Sale in Apple starts tomorrow”);
publisher.publish(message);
1.2 主题订阅
订阅者可以分为持久订阅者和非持久订阅者。持久订阅者保证能收到消息,即使消息到达时他们不处于活动状态。非持久订阅者只能收到他们处于活动状态时到来的消息,类似于聊天室的工作方式,必须在线才能接收消息。以下是创建非持久订阅者的代码示例:
TopicSession subSession =
connection.createTopicSession(false, Session.AUTO_ACKNOWLEDGE);
subSession.createTopic(“Price_Drop_Alerts”);
TopicSubscriber subscriber = subSession.createSubscriber(topic);
connection.start();
subscriber.setMessageListener(this);
public void onMessage(Message message) {
String msgText;
try{
if (msg instanceof TextMessage){
msgText = ((TextMessage) msg).getText();
System.out.println(“Got “ + msgText);
}else{
System.out.println(“Got a non-text message”);
}
}
catch (JMSException e){
System.out.println(“Error: “ + e.getMessage());
}
}
若要创建持久订阅者,需要对上述代码进行两处修改:为连接分配客户端ID(
connection.setClientID(username)
),并使用
createDurableSubscriber(topic)
方法代替
createSubscriber(topic)
。
1.3 消息选择器
如果需要与团队中的其他应用程序或开发者共享队列,可以使用消息选择器(也称为过滤器)来避免“窃取”他人的消息。例如:
String selector = “StoreName=Apple”;
session.createReceiver(queue, selector);
在这种情况下,队列监听器只会出队那些具有字符串属性
StoreName
且值为
Apple
的消息。消息生产者需要设置这个属性:
TextMessage outMsg = session.createTextMessage();
outMsg.setText(“Super sale starts tomorrow”);
outMsg.setStringProperty(“StoreName”, “Apple”);
需要注意的是,消息选择器会减慢检索过程。消息会一直留在队列中,直到具有匹配选择器的监听器将其取出。如果团队的队列数量有限,且每个人都需要在不干扰他人的情况下接收消息,选择器会很有帮助。但如果有人在没有选择器的情况下启动队列监听器,它会清空整个队列。
2. Open MQ对象管理
为了测试相关代码示例,需要一个MOM提供者来传输消息,这里使用开源的MOM提供者Open MQ。它是知名的商业级软件,并且与GlassFish集成。如果要将Open MQ与其他应用服务器一起使用,可以从https://mq.dev.java.net/ 单独下载。以下是管理Open MQ对象的操作步骤:
1. 打开命令(或终端)窗口,进入glassfishv3/mq/bin目录,启动Open MQ代理。在Mac OS中输入以下命令(在Windows中需要运行imqbrokerd.exe):
./imqbrokerd -port 7677
会看到提示信息,表明代理已在端口7677上准备就绪。
2. 打开另一个命令窗口,再次进入glassfishv3/mq/bin目录,启动管理GUI工具imqadmin来创建所需的消息目的地:
./imqadmin
Open MQ管理控制台窗口将打开。添加一个新的代理并将其命名为StockBroker,将端口更改为7677,输入密码admin,然后点击OK。
3. 连接到StockBroker(使用右键菜单),并创建一个名为TestQueue的新目的地,以匹配代码示例中的队列名称。
创建管理的MOM对象(TestQueue)完成后,就可以编写和测试消息发送者和接收者了。
3. 实践操作
目标是编写两个Java程序,一个向TestQueue发送消息,另一个从TestQueue接收消息。
3.1 操作要求
需要安装Java和Open MQ,还需要JMS类及其Open MQ实现(jms.jar和imq.jar)。可以从指定网站下载相关代码和资源。
3.2 操作步骤
- 在Eclipse中,切换到Java透视图,选择File➤New➤Create a New Java Project,将项目命名为Lesson30,然后点击Next。选择Libraries选项卡,点击Add External JARs,在glassfishv3/mq/lib中找到jms.jar和imq.jar并添加到项目中,最后点击Finish。这样就创建了一个常规的Java SE项目,其CLASSPATH中包含两个额外的jar文件。
- 创建一个名为MessageSender的新类,包含main()方法,并添加所有JMS类和Open MQ实现的ConnectionFactory的导入语句:
import javax.jms.*;
import com.sun.messaging.ConnectionFactory;
import com.sun.messaging.ConnectionConfiguration;
- 将相关代码输入到main()方法中。
- 编译并运行程序,应该会看到消息“Successfully placed an order to purchase 200 shares of IBM.”,此时消息位于Open MQ服务器配置的TestQueue中,直到有程序将其出队。
- 创建另一个Java类MessageReceiver,使其与相关代码示例中的类相似。
-
运行MessageReceiver,它会在控制台打印消息“Listening to the TestQueue”,并从队列中检索所有消息。
Thread.sleep()会使程序保持运行。 - 观察一个控制台显示生产者应用程序的消息,另一个控制台显示消费者正确接收消息并打印确认信息。
4. JNDI简介
Java命名和目录接口(JNDI)的作用是使在分布式应用程序中查找对象变得更容易,类似于公司的电话目录查询服务。各种软件供应商提供专门的目录查询软件,而JNDI提供了一个标准API来读写这些目录。
4.1 命名服务和目录服务
- 命名服务 :允许添加、更改或删除存在于某个命名层次结构中的对象名称,以便其他Java类可以查找它们的位置。每个注册到命名服务的条目都有一个唯一的名称,命名服务有一个或多个上下文,类似于文件系统中的目录和子目录,命名树从根节点(初始上下文)开始。
- 目录服务 :允许通过对象属性而不是对象名称来搜索命名树,例如域名系统,它根据网络计算机或服务的域名返回资源的IP地址和端口号。
为了让客户端进行查找,需要有一个过程将对象绑定到命名树。Java EE服务器在启动时会将EJB、Servlets、JMS和数据库连接池等对象绑定到其内部或外部命名服务器。
4.2 GlassFish中JNDI对象的管理
每个Java EE应用服务器都提供了管理其服务模块的工具,这里关注的是将对象绑定到其目录名称。启动GlassFish时,控制台会显示“Waiting for DAS to start…”,DAS代表域管理服务器,它对具有管理权限的用户进行身份验证,并响应基于图形Web浏览器的管理控制台的请求。使用控制台时,在浏览器中输入以下URL:http://localhost:4848/,会提示输入用户ID(admin)和密码。
登录成功后,会看到控制台,它可以管理各种Java对象。以JMS相关对象为例,目录树左侧有两个JMS节点,一个是JMS Resources,另一个是Java Message Service,后者显示了使用Open MQ管理实用程序创建的物理队列TestQueue。
由于GlassFish与Open MQ集成,它会自动知道这个MOM中的目的地。若要配置另一个MOM服务器,需要通过填写相关表单创建新的JMS主机。接下来需要创建一个GlassFish JMS条目来映射到物理MOM队列,将新的目的地资源添加到JMS Resources中,例如将其命名为MyJMSTestQueue并映射到之前创建的TestQueue。还需要创建另一个管理资源,即连接工厂。由于连接和发送者的创建和关闭操作较慢,可以考虑编写JMS连接池的代码,Java EE服务器允许配置这样的池,默认情况下会创建8个JMS连接,随着用户数量的增加,GlassFish最多会创建32个连接。
5. 使用JNDI访问GlassFish命名服务
Java程序要在JNDI树中搜索对象,需要获取其初始节点。有两种情况需要考虑:
5.1 外部Java客户端
外部Java客户端需要找到运行命名服务的服务器,需要准备一个Java Hashtable或Properties对象的实例,并将其作为参数传递给
javax.naming.InitialContext
类的构造函数。以下是一个示例代码:
Properties props = new Properties();
props.setProperty(“java.naming.factory.initial”,
“com.sun.enterprise.naming.SerialInitContextFactory”);
props.setProperty(“java.naming.factory.url.pkgs”,”com.sun.enterprise.naming”);
props.setProperty(“java.naming.factory.state”,
“com.sun.corba.ee.impl.presentation.rmi.JNDIStateFactoryImpl”);
props.setProperty(“org.omg.CORBA.ORBInitialHost”, “localhost”);
props.setProperty(“org.omg.CORBA.ORBInitialPort”, “8080”);
InitialContext ic = new InitialContext(props);
5.2 内部Java客户端
Java客户端在运行命名服务的Java EE服务器内部运行时,可以直接创建一个无参数构造函数的InitialContext实例,因为Java EE服务器知道在哪里找到其命名服务:
InitialContext ic = new InitialContext();
之后可以从初始上下文节点开始进行查找,或者使用Java注解的语法让容器注入所需资源的引用。
以下是一个Servlet作为Java客户端的示例代码:
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@WebServlet(description = “The servlet that Sends a message to a queue”,
urlPatterns = { “/quote” }, name=”QuoteService”)
public class QuoteService extends HttpServlet {
protected void doGet(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {
PrintWriter out = response.getWriter();
out.println(“<html><body bgcolor=yellow>”);
out.println(“<h2>Hello from QuoteService</h2>”);
out.println(“Sending a message to the TestQueue”);
MessageSender mySender = new MessageSender();
mySender.sendMessage(“IBM 200 Buy”);
}
}
这个Servlet接收客户端的请求,并实例化MessageSender类。以下是MessageSender类的代码示例:
package com.practicaljava.lesson31;
import javax.jms.*;
import javax.naming.*;
public class MessageSender {
void sendMessage(String messageToSend) {
Session session = null;
ConnectionFactory factory = null;
Connection connection = null;
try{
// Find the JNDI context
Context jndiContext = new InitialContext();
// Look up the factory and the queue
factory = (ConnectionFactory) jndiContext.lookup(“MyTestConnectionFactory”);
Queue ioQueue = (Queue) jndiContext.lookup(“MyJMSTestQueue”);
connection = factory.createConnection();
connection.start();
session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
MessageProducer queueSender = session.createProducer(ioQueue);
// Buy 200 shares of IBM in this example
TextMessage outMsg = session.createTextMessage(messageToSend);
queueSender.send(outMsg);
queueSender.close();
System.out.println(“Successfully placed an order to purchase 200 shares of
IBM”);
} catch (JMSException e) {
System.out.println(“Error: “ + e.getMessage());
} catch (NamingException e) {
e.printStackTrace();
} finally {
try{
session.close();
connection.close();
} catch (Exception e) {
System.out.println(“Can’t close JMS connection/session “ + e.getMessage());
}
}
}
}
在Eclipse中运行quote Servlet或直接在浏览器中访问http://localhost:8080/Lesson31/quote,购买200股IBM的消息将被放入Open MQ服务器的物理队列TestQueue中。如果不确定应用程序是否按预期工作,可以查看glassfish/domains/domain1/logs/server.log目录中的域日志文件,如果程序执行正常,日志文件末尾会显示消息“Successfully placed an order to purchase 200 shares of IBM.”。
6. JNDI资源注入
虽然查找JNDI对象看起来并不太难,但使用@Resource注解可以更简洁地为Java EE组件提供这些资源。应用服务器可以注入资源,无需进行JNDI查找。例如,注入ConnectionFactory和Queue可以如下所示:
import javax.annotation.Resource;
...
@Resource(name=”MyTestConnectionFactory”)
private ConnectionFactory factory;
@Resource(name=”MyJMSTestQueue”)
private Queue ioQueue;
资源可以注入到变量中,也可以将@Resource注解放在方法或类定义之上。根据注解的位置,注入时间会有所不同。如果将注解放在类级别,资源将在应用程序查找时在运行时注入;如果放在字段或setter方法声明之上,资源将在应用程序初始化时注入。如果需要覆盖注解中指定的资源,可以在XML配置文件中进行操作。
7. 数据源与JNDI
在学习JDBC时,执行数据库查询之前需要创建数据库连接。想象多个客户端向应用服务器发送请求,执行数据库查询时,创建和关闭连接是较慢的操作,应尽量减少这些操作。通常,Java EE服务器的管理员会预先创建数据库连接池,并配置最小和最大连接数以及其他参数。
GlassFish管理控制台中的JNDI条目DerbyPool代表一个JDBC连接池,
javax.sql.DataSource
是数据库连接的工厂,管理员将其配置为JNDI资源,指定使用的JDBC驱动程序、初始创建的连接数和允许的最大连接数。用户的请求将与部署在服务器上的Java类通信,该类通过其JNDI名称(如DerbyPool)找到连接池,并调用
getConnection()
方法。如果有可用的空闲连接,Java类将立即获得一个
PooledConnection
对象的实例;如果许多用户同时发出请求,所有连接可能都被占用,需要等待一个繁忙的连接返回到池中。返回到池中的连接不会被销毁,应用服务器会保留它们以供将来的请求使用。
可以通过JNDI查找DataSource对象或将其注入到Java代码中。如果配置的DataSource对象名称为DerbyPool,获取池化数据库连接的示例代码如下:
InitialContext ic = new InitialContext();
DataSource ds = (DataSource) ic.lookup(“DerbyPool”);
Connection myConnection = ds.getConnection();
//The rest of the JDBC processing goes here as in Lesson 22
// Closing the connection just returnes it back to the pool
总结
本文详细介绍了JMS消息处理的基础知识,包括消息发布、主题订阅和消息选择器的使用。同时,阐述了Open MQ对象的管理方法和实践操作步骤。此外,还介绍了JNDI的概念、GlassFish中JNDI对象的管理、使用JNDI访问GlassFish命名服务、JNDI资源注入以及数据源与JNDI的关系。通过这些知识和操作示例,可以更好地理解和应用Java中的消息处理和命名服务。
流程图
graph LR
A[开始] --> B[JMS消息处理]
B --> B1[消息发布]
B --> B2[主题订阅]
B --> B3[消息选择器]
B --> C[Open MQ对象管理]
C --> C1[启动代理]
C --> C2[创建目的地]
C --> D[实践操作]
D --> D1[创建项目]
D --> D2[编写发送者和接收者]
D --> D3[运行程序]
B --> E[JNDI简介]
E --> E1[命名服务]
E --> E2[目录服务]
E --> F[GlassFish中JNDI对象管理]
F --> F1[登录控制台]
F --> F2[创建资源]
F --> G[使用JNDI访问服务]
G --> G1[外部客户端]
G --> G2[内部客户端]
G --> H[JNDI资源注入]
G --> I[数据源与JNDI]
I --> I1[查找数据源]
I --> I2[获取连接]
I --> J[结束]
D3 --> J
H --> J
表格
| 操作 | 说明 |
|---|---|
| 消息发布 | 创建Topic和Publisher,调用publish()方法 |
| 主题订阅 | 分为持久和非持久订阅者,使用不同方法创建 |
| 消息选择器 | 过滤队列中的消息 |
| Open MQ管理 | 启动代理,创建目的地 |
| JNDI查找 | 外部和内部客户端不同方式 |
| 资源注入 | 使用@Resource注解 |
| 数据源获取 | 通过JNDI查找或注入 |
Java消息服务与JNDI使用指南(续)
8. 实践案例分析
为了更好地理解上述知识的实际应用,我们通过一个具体的实践案例来进一步分析。假设我们正在开发一个股票交易系统,需要使用消息队列来处理交易订单,同时利用JNDI来管理相关资源。
8.1 需求分析
- 交易系统需要接收用户的股票购买订单,并将订单信息发送到消息队列中。
- 订单处理模块从消息队列中获取订单信息,并进行相应的处理。
- 使用JNDI来管理消息队列的连接工厂和队列对象,方便后续的维护和扩展。
8.2 实现步骤
-
配置JNDI资源
:在GlassFish管理控制台中,创建JMS连接工厂和队列资源,并将其绑定到JNDI名称。例如,创建一个名为
MyStockConnectionFactory的连接工厂和一个名为StockOrderQueue的队列。 -
编写消息发送者
:创建一个Java类
StockOrderSender,用于将用户的股票购买订单发送到消息队列中。以下是示例代码:
package com.stocktrading;
import javax.jms.*;
import javax.naming.*;
public class StockOrderSender {
void sendOrder(String stockSymbol, int quantity) {
Session session = null;
ConnectionFactory factory = null;
Connection connection = null;
try {
// Find the JNDI context
Context jndiContext = new InitialContext();
// Look up the factory and the queue
factory = (ConnectionFactory) jndiContext.lookup("MyStockConnectionFactory");
Queue orderQueue = (Queue) jndiContext.lookup("StockOrderQueue");
connection = factory.createConnection();
connection.start();
session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
MessageProducer orderSender = session.createProducer(orderQueue);
// Create the order message
TextMessage orderMsg = session.createTextMessage();
orderMsg.setText("Buy " + quantity + " shares of " + stockSymbol);
orderSender.send(orderMsg);
orderSender.close();
System.out.println("Successfully placed an order to buy " + quantity + " shares of " + stockSymbol);
} catch (JMSException e) {
System.out.println("Error: " + e.getMessage());
} catch (NamingException e) {
e.printStackTrace();
} finally {
try {
session.close();
connection.close();
} catch (Exception e) {
System.out.println("Can't close JMS connection/session " + e.getMessage());
}
}
}
}
-
编写消息接收者
:创建一个Java类
StockOrderReceiver,用于从消息队列中获取订单信息并进行处理。以下是示例代码:
package com.stocktrading;
import javax.jms.*;
import javax.naming.*;
public class StockOrderReceiver implements MessageListener {
public StockOrderReceiver() {
try {
// Find the JNDI context
Context jndiContext = new InitialContext();
// Look up the factory and the queue
ConnectionFactory factory = (ConnectionFactory) jndiContext.lookup("MyStockConnectionFactory");
Queue orderQueue = (Queue) jndiContext.lookup("StockOrderQueue");
Connection connection = factory.createConnection();
connection.start();
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
MessageConsumer orderReceiver = session.createConsumer(orderQueue);
orderReceiver.setMessageListener(this);
System.out.println("Listening to the StockOrderQueue");
} catch (JMSException e) {
System.out.println("Error: " + e.getMessage());
} catch (NamingException e) {
e.printStackTrace();
}
}
@Override
public void onMessage(Message message) {
try {
if (message instanceof TextMessage) {
String orderText = ((TextMessage) message).getText();
System.out.println("Received order: " + orderText);
// Here you can add the logic to process the order
} else {
System.out.println("Got a non-text message");
}
} catch (JMSException e) {
System.out.println("Error: " + e.getMessage());
}
}
}
-
测试程序
:创建一个主类
StockTradingTest来测试消息发送和接收功能。以下是示例代码:
package com.stocktrading;
public class StockTradingTest {
public static void main(String[] args) {
// Start the order receiver
new StockOrderReceiver();
// Send an order
StockOrderSender sender = new StockOrderSender();
sender.sendOrder("IBM", 200);
}
}
8.3 案例总结
通过这个实践案例,我们可以看到如何将JMS和JNDI结合使用来实现一个简单的股票交易系统。使用JNDI管理资源可以提高代码的可维护性和可扩展性,而JMS则提供了可靠的消息传递机制。
9. 常见问题与解决方案
在使用JMS和JNDI的过程中,可能会遇到一些常见的问题,以下是一些问题及相应的解决方案。
| 问题 | 解决方案 |
|---|---|
| JNDI查找失败 | 检查JNDI配置是否正确,包括上下文属性、资源名称等。确保GlassFish服务器正常运行,并且资源已正确绑定到JNDI。 |
| 消息发送失败 | 检查JMS连接是否正常,包括连接工厂、队列等。确保消息内容符合要求,并且消息生产者和消费者的配置正确。 |
| 消息接收不到 | 检查消息选择器是否过滤掉了所需的消息。确保消息消费者正常运行,并且消息队列中有消息可供接收。 |
| 资源注入失败 | 检查@Resource注解的使用是否正确,包括资源名称、类型等。确保应用服务器支持资源注入,并且配置正确。 |
10. 性能优化建议
为了提高系统的性能和可靠性,以下是一些性能优化建议。
10.1 JMS性能优化
- 使用连接池 :创建和关闭JMS连接是比较耗时的操作,可以使用连接池来复用连接,减少连接创建和关闭的开销。
- 批量发送消息 :如果需要发送大量消息,可以考虑批量发送,减少网络开销。
- 合理配置消息确认模式 :根据业务需求选择合适的消息确认模式,如自动确认或手动确认,以提高消息处理的效率。
10.2 JNDI性能优化
- 缓存JNDI查找结果 :由于JNDI查找可能会比较耗时,可以将查找结果缓存起来,避免重复查找。
- 优化JNDI配置 :合理配置JNDI上下文属性,减少不必要的查找操作。
11. 未来发展趋势
随着技术的不断发展,JMS和JNDI也在不断演进。以下是一些未来可能的发展趋势。
- 云原生支持 :随着云计算的普及,JMS和JNDI将更好地支持云原生环境,如容器化、微服务等。
- 与其他技术的融合 :JMS和JNDI可能会与其他技术如Kafka、Redis等进行融合,提供更强大的消息处理和资源管理能力。
- 智能化管理 :未来的JMS和JNDI系统可能会引入智能化管理功能,如自动优化配置、故障预测等。
流程图
graph LR
A[开始] --> B[实践案例]
B --> B1[需求分析]
B --> B2[实现步骤]
B2 --> B21[配置JNDI资源]
B2 --> B22[编写发送者]
B2 --> B23[编写接收者]
B2 --> B24[测试程序]
B --> C[常见问题]
C --> C1[JNDI查找失败]
C --> C2[消息发送失败]
C --> C3[消息接收不到]
C --> C4[资源注入失败]
C --> D[解决方案]
D --> D1[检查配置]
D --> D2[检查连接]
D --> D3[检查选择器]
D --> D4[检查注解]
B --> E[性能优化]
E --> E1[JMS优化]
E1 --> E11[使用连接池]
E1 --> E12[批量发送]
E1 --> E13[合理配置确认模式]
E --> E2[JNDI优化]
E2 --> E21[缓存查找结果]
E2 --> E22[优化配置]
B --> F[未来趋势]
F --> F1[云原生支持]
F --> F2[与其他技术融合]
F --> F3[智能化管理]
B24 --> G[结束]
D --> G
E --> G
F --> G
总结
本文通过实践案例进一步展示了JMS和JNDI的实际应用,同时分析了常见问题及解决方案,并提供了性能优化建议和未来发展趋势。通过深入学习和掌握这些知识,可以更好地应对实际开发中的挑战,提高系统的性能和可靠性。希望本文对读者在Java消息处理和命名服务方面有所帮助。
超级会员免费看
996

被折叠的 条评论
为什么被折叠?



