32、开发 Web 应用与 Java 消息服务

开发 Web 应用与 Java 消息服务

1. 转换器和验证器

在 Java 开发中,托管 Bean 可以分配转换器和验证器。Java 类有强类型字段,但数据到达网页时需转换为字符串。JSF 会自动为 Java 数据类型执行此转换,不过也能编写自定义转换器并添加特定应用逻辑。

1.1 验证器的使用

验证器可对托管 Bean 中的数据设置约束。可以使用预定义注解(如 @NotNull @Min @Max )来指定 Bean 字段的数据验证约束,也能创建自定义注解实现特定应用的约束。以下是 Student 类的示例:

@ManagedBean
@RequestScoped
public class Student implements Serializable{
    @NotNull(message="you must enter Student ID")
    @Size(min=8, max=10, message="Student ID must include between 8 and 10 symbols") 
    private long studentId;

    @Size(min=2, message="Name can have less than two characters")   
    private String name;

    @NotNull(message="Please enter Class ID")
    private int classId;
    ...
}

核心 JSF 库有标准验证器,可验证长度以及长整型和双精度型值的范围。可以在 JSF 文件中封装这些验证器,示例如下:

<h:inputText  value="#{student.name}" title="name" id="name" >
    <f:validateLength minimum="2"/>
</h:inputText>

1.2 自定义验证器

要创建自定义验证器(如 CreditCardValidator ),需编写一个实现 javax.faces.validator.Validator 接口的 Java 类,并在 validate() 方法中执行验证。若值无效,该方法必须抛出 ValidatorException 。自定义验证器必须在 faces-config.xml 中注册:

<validator>
    <validator-id>CreditCardValidator</validator-id>
    <validator-class>myvalidators.CreditCardValidator</validator-class>
</validator>

自定义验证器可在 JSF 页面中使用 <f: validator> 标签,示例如下:

<h:inputText  value="#{student.creditcad}" title="card" id="card" >
    <f:validator id="CreditCardValidator"/>
</h:inputText>

2. 使用 JSF 开发股票服务器应用

2.1 需求概述

重写之前创建的股票服务器应用,这次使用 JSF。JSF 客户端应有一个文本输入框和一个提交按钮。用户输入股票代码获取价格报价,生成随机报价并返回包含报价的网页。网页应显示可用股票列表。托管 Bean 需有一个 getQuotes() 方法,用户点击提交按钮时调用。

2.2 实现步骤

  1. 创建新的 StockServer Bean :包含 getQuote() getNasdaqSymbols() 方法。
  2. 定义 JSF 模板
  3. 创建两个 JSF 页面 :一个用于请求特定股票的价格报价,另一个用于显示结果。第一个网页的提交按钮应调用托管 Bean 上的 getQuote() 方法。
  4. 运行并测试 JSF 应用

2.3 开发提示

NetBeans 7 IDE 对 JSF 的支持优于 Eclipse,它有许多向导,尤其能在图形界面中轻松定义网页导航。另一个流行的 IDE IntelliJ IDEA 9 也对 JSF 提供了出色支持。

3. 介绍 JMS 和 MOM

3.1 消息传递概念

人们通过电子邮件、即时消息、Twitter、Facebook 等方式相互发送消息,也可使用传统方式如邮寄。应用程序可以使用消息导向中间件(MOM)相互发送消息,MOM 类似于快递服务。程序使用 Java 消息服务(JMS)API 将消息放入消息队列,消息会被传递到从该队列读取消息的另一个应用程序。

3.2 消息传递与传统通信的区别

之前学习了分布式 Java 应用中的几种数据交换方法(直接套接字通信、RMI 和基于 HTTP 的交互),但它们都基于远程过程调用(RPC)或请求/响应模型。MOM 能构建异步通信的分布式系统。JMS 是与 MOM 一起工作的 API,它本身不是消息传输方式,就像 JDBC 对于关系型数据库管理系统一样,Java 应用程序可以使用相同的 JMS 类与任何 MOM 提供商配合使用。

3.3 常见的 MOM 软件

MOM 软件 提供商
WebSphere MQ IBM
EMS Tibco Software
SonicMQ Progress Software
ActiveMQ 开源,Apache
Open MQ 开源,Oracle

3.4 同步与异步通信

同步通信(如调用远程机器上的 placeOrder() 方法)是阻塞调用,调用程序需等待方法执行完成或抛出错误才能继续。而异步通信中,可下单但无需等待执行,就像寄信或发邮件一样,发送后可继续做其他事情,收件人也无需在线接收。

3.5 交易订单处理流程

下单过程是将描述订单的 Java 对象放入 MOM 提供商的特定消息队列。下单后,程序可继续执行,无需等待订单处理完成。多个用户可将订单放入同一队列,另一个程序负责出队和处理消息。为提高基于消息的应用程序的吞吐量,可添加多个消费者从同一队列读取消息。

4. JMS 消息传递模式

4.1 消息传递的两种模式

4.1.1 点对点(P2P)消息传递

程序将消息发送到特定队列,另一个程序从该队列接收消息,消息成功接收后即从队列中删除。

4.1.2 发布/订阅(pub/sub)模式

程序发布消息供多个接收者消费,消息发布到特定主题,许多订阅者可订阅接收。主题代表应用程序和/或用户的重要消息,如价格下跌警报、突发新闻等。在 pub/sub 模式中,消息通常在所有订阅者接收后从队列中删除。聊天室就是 pub/sub 应用的一个很好例子。

4.2 消息传递的保证模式

消息传递可以是有保证的,MOM 会像邮局一样将消息保留在队列中,直到接收者获取。在这种模式下,消息是持久的,MOM 提供商将其存储在内部存储中(可以是数据库管理系统或文件系统)。在无保证模式下,MOM 仅将消息传递给消息到达时正在运行的接收应用程序。

5. JMS API 概述

5.1 主要 JMS 接口

接口名称 描述
Queue 消息存放或获取的地方,消息按先进先出(FIFO)规则检索。消息生产者将消息放入队列,消息消费者从队列中取出消息。
QueueConnection 表示与 MOM 的特定连接,类似于 JDBC 中的 Connection 类。
QueueConnectionFactory 创建 Connection 对象,类似于 JDBC 中的 DataSource 类。
QueueSession 表示客户端与 MOM 服务器之间的特定会话,由 QueueConnection 创建。
QueueSender 实际发送消息的对象。
QueueReceiver 接收消息的对象。
TopicPublisher 发布消息,功能类似于 QueueSender
TopicSubscriber 接收消息,功能类似于 QueueReceiver
Topic 在 pub/sub 模式中表示重要应用事件的对象, TopicPublisher 向主题发布消息, TopicSubscribers 可订阅该主题。
Message 作为应用程序特定对象的包装器,可放入 JMS 队列或发布到主题。

5.2 消息类型

每个消息包含头部、可选的主体和提供附加属性的功能。头部包含消息标识(唯一消息 ID、目的地、类型等),可选属性可由程序设置,用于标记具有特定应用数据的消息。可选主体包含要传递的消息,JMS 消息有以下几种类型:
- TextMessage :可包含任何 Java 字符串。
- ObjectMessage :可保存任何可序列化的 Java 对象。
- BytesMessage :保存字节数组。
- StreamMessage :包含 Java 基本类型的流。
- MapMessage :包含任何键/值对,例如 id=123

5.3 发送消息的步骤

  1. 创建(或从命名服务器获取) ConnectionFactory 对象。
  2. 创建 Connection 对象并调用其 start() 方法。
  3. 创建 Session 对象。
  4. 创建 Queue 对象。
  5. 创建 MessageProducer 对象。
  6. 创建消息对象(如 TextMessage )并放入数据。
  7. 调用 QueueSender send() 方法。
  8. 关闭 QueueSender Session Connection 对象以释放系统资源。

以下是发送消息到名为 TestQueue 的队列的代码示例:

Session session=null;
ConnectionFactory factory;
QueueConnection connection=null;

try{
    // 特定于 Open MQ 的连接创建方式
    factory = new com.sun.messaging.ConnectionFactory();  
    factory.setProperty(ConnectionConfiguration.imqAddressList, 
                        "mq://localhost:7677,mq://localhost:7677");

    connection = factory.createQueueConnection("admin","admin");

    connection.start();
    session = connection.createQueueSession( 
                        false, Session.AUTO_ACKNOWLEDGE);
    Queue ioQueue = session.createQueue("TestQueue");
    MessageProducer queueSender = session.createProducer(ioQueue);

    // 以市场价格购买 200 股 IBM 股票
    TextMessage outMsg = session.createTextMessage("IBM 200 Mkt");

    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());
} 
finally{
    try{
        session.close();
        connection.close();
    } catch (Exception e) {
        System.out.println("Can’t close JMS connection/session " + 
                                                    e.getMessage());
    }
}

5.4 接收消息的方式

接收消息的程序称为消息消费者,可以是独立的 Java 程序或消息驱动 Bean(MDB)。可以使用 receive() 方法同步接收消息,也可以通过实现 MessageListener 接口并编程回调 onMessage() 方法异步接收消息。

5.4.1 同步接收消息
QueueReceiver queueReceiver = Session.createReceiver(ioQueue);
Message myMessage = queueReceiver.receive(); 
// 设置超时时间为 500 毫秒
Message myMessage = queueReceiver.receive(500); 
5.4.2 异步接收消息

异步接收消息是更好的方式,因为消息消费者无需多次向 MOM 发送请求来检查消息是否在队列中。当消息放入队列时, onMessage() 回调方法将立即被调用。以下是异步接收消息的示例代码:

package com.practicaljava.lesson30;
import javax.jms.*;
import com.sun.messaging.ConnectionFactory;
import com.sun.messaging.ConnectionConfiguration;

public class MessageReceiver implements MessageListener{
    Session session=null;
    ConnectionFactory factory;
    QueueConnection connection=null;
    MessageConsumer consumer=null;

    MessageReceiver(){
        try{
            factory = new com.sun.messaging.ConnectionFactory();  
            factory.setProperty(ConnectionConfiguration.imqAddressList, 
                                "mq://localhost:7677,mq://localhost:7677");
            connection = factory.createQueueConnection("admin","admin");

            connection.start();
            Session session = connection.createQueueSession( 
                                false, Session.AUTO_ACKNOWLEDGE);
            Queue ioQueue = session.createQueue( "TestQueue" );
            consumer = session.createConsumer(ioQueue);
            consumer.setMessageListener(this);

            System.out.println("Listening to the TestQueue...");

            // 等待消息
            Thread.sleep(100000);

        } catch (InterruptedException e){
            System.out.println("Error: " + e.getMessage());
        }
        catch (JMSException e){
            System.out.println("Error: " + e.getMessage());
        } 
        finally{
            try{
                connection.close();
            } catch (Exception e) {
                System.out.println("Can’t close JMS connection/session " 
                                                            + e.getMessage());
            }
        }      
    }

    public static void main(String[] args){
        new MessageReceiver();
    }      

    public void onMessage(Message msg){
        String msgText;
        try{
            if (msg instanceof TextMessage){
                msgText = ((TextMessage) msg).getText();
                System.out.println("Got from the queue: " + msgText);
            }else{
                System.out.println("Got a non-text message");
            }
        }
        catch (JMSException e){
            System.out.println("Error while consuming a message: " + 
                                                      e.getMessage());
        } 
    }
}

5.5 消息确认模式

消息确认模式在创建 Session 对象时定义。 createSession() 方法有两个参数,如果第一个参数为 true ,会话是事务性的,第二个参数的值无关紧要,消费者可以提交或回滚消息。如果调用了 commit() 方法,消息将从队列中删除; rollback() 方法会将消息保留在队列中。如果会话是非事务性的,第二个参数定义确认模式:
- AUTO_ACKNOWLEDGE 模式: onMessage() 方法成功完成后立即发送确认。
- CLIENT_ACKNOWLEDGE 模式:需要显式确认,如 msg.acknowledge() ,这允许从队列中删除消息。
- DUP_OK_ACKNOWLEDGE 模式:用于服务器故障的情况,同一消息可能会多次传递,在某些用例中是可以接受的。

总结

本文介绍了 JSF 开发中的转换器和验证器,包括自定义验证器的创建和使用。同时详细阐述了 JMS 和 MOM 的概念、消息传递模式、JMS API 以及消息的发送和接收方式,还介绍了消息确认模式。通过这些知识,可以更好地开发基于消息传递的分布式 Java 应用程序。

6. 消息传递架构中的 Java EE 应用服务器

在消息传递架构中引入 Java EE 应用服务器具有诸多好处。Java EE 应用服务器可以提供更强大的管理和监控功能,确保消息传递的可靠性和性能。它可以管理连接池、事务处理和资源分配,使得消息传递系统更加稳定和高效。

6.1 连接管理

Java EE 应用服务器可以管理 JMS 连接,避免手动创建和关闭连接带来的资源浪费和错误。通过连接池技术,服务器可以重用连接,提高系统的响应速度。例如,在高并发的情况下,连接池可以快速提供可用的连接,减少连接创建的时间开销。

6.2 事务处理

在 Java EE 应用服务器中,可以使用 JTA(Java Transaction API)来管理消息传递的事务。事务处理可以确保消息的一致性和完整性。例如,在一个业务流程中,如果消息的发送和数据库的更新是一个原子操作,那么可以使用事务来保证要么两者都成功,要么两者都失败。

6.3 监控和管理

Java EE 应用服务器提供了丰富的监控和管理工具,可以实时监控消息队列的状态、消息的处理情况和资源的使用情况。管理员可以通过这些工具及时发现和解决问题,确保系统的正常运行。

7. 消息驱动 Bean 的价值

消息驱动 Bean(MDB)是 Java EE 中的一种特殊的企业 Bean,它可以异步处理 JMS 消息。MDB 为消息传递架构带来了很多价值。

7.1 异步处理

MDB 可以异步处理消息,使得应用程序可以在不阻塞主线程的情况下处理消息。这对于提高系统的吞吐量和响应速度非常有帮助。例如,在一个电商系统中,当用户下单后,系统可以将订单消息发送到消息队列,MDB 可以异步处理这些订单,而主线程可以继续处理其他请求。

7.2 解耦

MDB 可以将消息的发送者和接收者解耦。发送者只需要将消息发送到消息队列,而不需要关心谁来处理这些消息。接收者只需要从消息队列中接收消息并处理,而不需要关心消息是从哪里来的。这种解耦可以提高系统的灵活性和可维护性。

7.3 可扩展性

MDB 可以很容易地进行扩展。可以通过增加 MDB 的实例数量来提高消息处理的能力。例如,在高并发的情况下,可以增加 MDB 的实例数量,使得系统可以同时处理更多的消息。

8. 实际应用案例

以下是一个简单的实际应用案例,展示如何使用 JMS 和 MDB 来构建一个分布式系统。

8.1 系统架构

graph LR
    A[客户端] -->|发送消息| B(消息队列)
    B -->|接收消息| C(MDB)
    C -->|处理消息| D(数据库)

8.2 代码实现

8.2.1 消息发送端
import javax.jms.*;
import com.sun.messaging.ConnectionFactory;
import com.sun.messaging.ConnectionConfiguration;

public class MessageSender {
    public static void main(String[] args) {
        Session session = null;
        ConnectionFactory factory;
        QueueConnection connection = null;

        try {
            factory = new com.sun.messaging.ConnectionFactory();
            factory.setProperty(ConnectionConfiguration.imqAddressList,
                    "mq://localhost:7677,mq://localhost:7677");

            connection = factory.createQueueConnection("admin", "admin");
            connection.start();
            session = connection.createQueueSession(
                    false, Session.AUTO_ACKNOWLEDGE);
            Queue ioQueue = session.createQueue("OrderQueue");
            MessageProducer queueSender = session.createProducer(ioQueue);

            TextMessage outMsg = session.createTextMessage("Order: 123");
            queueSender.send(outMsg);
            queueSender.close();

            System.out.println("Successfully sent an order message");
        } catch (JMSException e) {
            System.out.println("Error: " + e.getMessage());
        } finally {
            try {
                session.close();
                connection.close();
            } catch (Exception e) {
                System.out.println("Can’t close JMS connection/session " +
                        e.getMessage());
            }
        }
    }
}
8.2.2 消息驱动 Bean
import javax.ejb.ActivationConfigProperty;
import javax.ejb.MessageDriven;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageListener;
import javax.jms.TextMessage;

@MessageDriven(activationConfig = {
        @ActivationConfigProperty(propertyName = "destinationType", propertyValue = "javax.jms.Queue"),
        @ActivationConfigProperty(propertyName = "destination", propertyValue = "OrderQueue")
})
public class OrderMDB implements MessageListener {

    @Override
    public void onMessage(Message message) {
        try {
            if (message instanceof TextMessage) {
                TextMessage textMessage = (TextMessage) message;
                String order = textMessage.getText();
                System.out.println("Received order: " + order);
                // 处理订单,例如将订单信息存入数据库
            } else {
                System.out.println("Got a non-text message");
            }
        } catch (JMSException e) {
            System.out.println("Error while consuming a message: " +
                    e.getMessage());
        }
    }
}

8.3 案例分析

在这个案例中,客户端将订单消息发送到消息队列,MDB 从消息队列中接收消息并处理订单。通过这种方式,客户端和 MDB 实现了解耦,系统的可扩展性和灵活性得到了提高。同时,使用 Java EE 应用服务器可以管理连接和事务,确保消息传递的可靠性。

9. 总结与展望

9.1 总结

本文全面介绍了 JSF 开发中的转换器和验证器,为 Web 应用的数据处理提供了有效的手段。同时,深入探讨了 JMS 和 MOM 的相关知识,包括消息传递模式、JMS API、消息的发送和接收方式以及消息确认模式。此外,还介绍了 Java EE 应用服务器在消息传递架构中的作用和消息驱动 Bean 的价值,并通过实际案例展示了如何构建基于消息传递的分布式 Java 应用程序。

9.2 展望

随着技术的不断发展,消息传递在分布式系统中的应用将越来越广泛。未来,我们可以期待更高效、更可靠的消息传递技术的出现。例如,随着云计算和容器化技术的发展,消息传递系统可以更加灵活地部署和扩展。同时,人工智能和机器学习技术也可以应用于消息传递系统,实现智能的消息路由和处理。在开发过程中,我们需要不断学习和掌握新的技术,以适应不断变化的需求。

通过对这些知识的掌握和应用,开发者可以构建出更加稳定、高效和灵活的分布式 Java 应用程序,满足不同业务场景的需求。在实际项目中,需要根据具体的需求选择合适的技术和架构,充分发挥消息传递的优势,提高系统的性能和可靠性。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值