9.1.3 Nhận tin nhắn JMS
Khi tiêu thụ tin nhắn, bạn có thể chọn mô hình pull, nơi mã của bạn yêu cầu một tin nhắn và chờ đến khi có tin nhắn đến, hoặc mô hình push, trong đó tin nhắn được gửi đến mã của bạn ngay khi chúng sẵn sàng.
JmsTemplate cung cấp nhiều phương thức để nhận tin nhắn, nhưng tất cả đều sử dụng mô hình pull. Bạn gọi một trong các phương thức đó để yêu cầu tin nhắn, và luồng sẽ bị chặn cho đến khi tin nhắn sẵn sàng (có thể ngay lập tức hoặc mất một lúc).
Mặt khác, bạn cũng có thể chọn mô hình push, trong đó bạn định nghĩa một listener tin nhắn sẽ được gọi bất cứ khi nào có tin nhắn đến.
Cả hai tùy chọn đều phù hợp với nhiều tình huống sử dụng khác nhau. Thông thường, mô hình push được xem là lựa chọn tốt nhất vì nó không chặn luồng. Nhưng trong một số trường hợp, listener có thể bị quá tải nếu tin nhắn đến quá nhanh. Mô hình pull cho phép bên tiêu thụ tự khai báo rằng họ đã sẵn sàng xử lý một tin nhắn mới.
Hãy cùng xem cả hai cách nhận tin nhắn. Chúng ta sẽ bắt đầu với mô hình pull do JmsTemplate cung cấp.
NHẬN TIN NHẮN VỚI JMSTEMPLATE
JmsTemplate cung cấp nhiều phương thức để lấy tin nhắn từ broker, bao gồm các phương thức sau:
Message receive() throws JmsException;
Message receive(Destination destination) throws JmsException;
Message receive(String destinationName) throws JmsException;
Object receiveAndConvert() throws JmsException;
Object receiveAndConvert(Destination destination) throws JmsException;
Object receiveAndConvert(String destinationName) throws JmsException;Như bạn thấy, sáu phương thức này phản chiếu các phương thức send() và convertAndSend() từ JmsTemplate. Các phương thức receive() nhận một Message thô, trong khi các phương thức receiveAndConvert() sử dụng một converter đã cấu hình để chuyển đổi tin nhắn thành kiểu dữ liệu miền. Và với mỗi phương thức, bạn có thể chỉ định một Destination hoặc một chuỗi chứa tên đích, hoặc bạn có thể lấy tin nhắn từ đích mặc định.
Để thấy chúng hoạt động như thế nào, bạn sẽ viết một đoạn mã để lấy một TacoOrder từ đích tacocloud.order.queue. Danh sách dưới đây cho thấy OrderReceiver, một thành phần dịch vụ nhận dữ liệu đơn hàng bằng cách sử dụng JmsTemplate.receive().
Danh sách 9.2 Lấy đơn hàng từ hàng đợi
package tacos.kitchen.messaging.jms;
import javax.jms.Message;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jms.core.JmsTemplate;
import org.springframework.jms.support.converter.MessageConverter;
import org.springframework.stereotype.Component;
@Component
public class JmsOrderReceiver implements OrderReceiver {
private JmsTemplate jms;
private MessageConverter converter;
@Autowired
public JmsOrderReceiver(JmsTemplate jms, MessageConverter converter) {
this.jms = jms;
this.converter = converter;
}
public TacoOrder receiveOrder() {
Message message = jms.receive("tacocloud.order.queue");
return (TacoOrder) converter.fromMessage(message);
}
}Ở đây bạn đã sử dụng một chuỗi để chỉ định đích từ đó lấy đơn hàng. Phương thức receive() trả về một Message chưa được chuyển đổi. Nhưng điều bạn thực sự cần là TacoOrder nằm trong Message, vì vậy bước tiếp theo bạn sử dụng một converter đã được inject để chuyển đổi tin nhắn. Thuộc tính type ID trong tin nhắn sẽ hướng dẫn converter chuyển đổi thành TacoOrder, nhưng nó sẽ được trả về dưới dạng Object, cần ép kiểu trước khi bạn có thể trả lại nó.
Nhận một đối tượng Message thô có thể hữu ích trong một số trường hợp khi bạn cần kiểm tra các thuộc tính và header của tin nhắn. Nhưng thường thì bạn chỉ cần payload. Việc chuyển payload thành kiểu dữ liệu miền là một quy trình hai bước và yêu cầu inject MessageConverter vào thành phần. Khi bạn chỉ quan tâm đến payload của tin nhắn, receiveAndConvert() đơn giản hơn rất nhiều. Danh sách tiếp theo cho thấy cách JmsOrderReceiver có thể được chỉnh sửa để sử dụng receiveAndConvert() thay vì receive().
Danh sách 9.3 Nhận đối tượng TacoOrder đã được chuyển đổi
package tacos.kitchen.messaging.jms;
import org.springframework.jms.core.JmsTemplate;
import org.springframework.stereotype.Component;
import tacos.TacoOrder;
import tacos.kitchen.OrderReceiver;
@Component
public class JmsOrderReceiver implements OrderReceiver {
private JmsTemplate jms;
public JmsOrderReceiver(JmsTemplate jms) {
this.jms = jms;
}
@Override
public TacoOrder receiveOrder() {
return (TacoOrder) jms.receiveAndConvert("tacocloud.order.queue");
}
}Phiên bản mới này của JmsOrderReceiver có phương thức receiveOrder() đã được rút gọn chỉ còn một dòng. Thêm vào đó, bạn không còn cần inject MessageConverter, vì toàn bộ việc chuyển đổi tin nhắn sẽ được xử lý ở hậu trường trong receiveAndConvert().
Trước khi chuyển sang phần tiếp theo, hãy xem xét cách receiveOrder() có thể được sử dụng trong ứng dụng bếp của Taco Cloud. Một người chế biến thực phẩm ở một trong các bếp có thể nhấn nút hoặc thực hiện hành động nào đó để cho biết họ đã sẵn sàng làm taco. Tại thời điểm đó, receiveOrder() sẽ được gọi và lời gọi đến receive() hoặc receiveAndConvert() sẽ bị chặn. Không có gì xảy ra cho đến khi có một tin nhắn đơn hàng sẵn sàng. Khi đơn hàng đến, nó sẽ được trả về từ receiveOrder() và thông tin của nó sẽ được dùng để hiển thị chi tiết đơn hàng cho người chế biến bắt đầu công việc. Đây có vẻ là lựa chọn tự nhiên cho mô hình pull.
Giờ hãy xem cách mô hình push hoạt động bằng cách khai báo một listener JMS.
KHAI BÁO LISTENER TIN NHẮN
Không giống như mô hình pull, nơi bạn phải gọi rõ ràng receive() hoặc receiveAndConvert() để nhận tin nhắn, một listener tin nhắn là một thành phần bị động, không hoạt động cho đến khi có tin nhắn đến.
Để tạo một listener phản ứng với tin nhắn JMS, bạn chỉ cần đánh dấu một phương thức trong một thành phần bằng @JmsListener. Danh sách tiếp theo hiển thị một thành phần OrderListener mới lắng nghe tin nhắn một cách bị động thay vì chủ động yêu cầu.
Danh sách 9.4 Một thành phần OrderListener lắng nghe đơn hàng
package tacos.kitchen.messaging.jms.listener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Profile;
import org.springframework.jms.annotation.JmsListener;
import org.springframework.stereotype.Component;
import tacos.TacoOrder;
import tacos.kitchen.KitchenUI;
@Profile("jms-listener")
@Component
public class OrderListener {
private KitchenUI ui;
@Autowired
public OrderListener(KitchenUI ui) {
this.ui = ui;
}
@JmsListener(destination = "tacocloud.order.queue")
public void receiveOrder(TacoOrder order) {
ui.displayOrder(order);
}
}Phương thức receiveOrder() được đánh dấu với @JmsListener để "lắng nghe" tin nhắn trên đích tacocloud.order.queue. Nó không liên quan gì đến JmsTemplate, cũng không được gọi rõ ràng bởi mã của ứng dụng bạn. Thay vào đó, mã của framework trong Spring sẽ chờ tin nhắn đến trên đích đã chỉ định, và khi chúng đến, phương thức receiveOrder() sẽ được tự động gọi với payload TacoOrder của tin nhắn làm tham số.
Ở nhiều khía cạnh, annotation @JmsListener giống như một trong các annotation ánh xạ request trong Spring MVC, chẳng hạn như @GetMapping hoặc @PostMapping. Trong Spring MVC, các phương thức được đánh dấu với một trong các annotation ánh xạ sẽ phản ứng với các yêu cầu đến một đường dẫn cụ thể. Tương tự, các phương thức được đánh dấu với @JmsListener sẽ phản ứng với các tin nhắn đến một đích cụ thể.
Các listener tin nhắn thường được coi là lựa chọn tốt nhất vì chúng không chặn và có thể xử lý nhiều tin nhắn một cách nhanh chóng. Tuy nhiên, trong ngữ cảnh của ứng dụng Taco Cloud, có lẽ chúng không phải là lựa chọn tối ưu. Người chế biến thực phẩm là một nút thắt cổ chai trong hệ thống và có thể không làm taco nhanh bằng tốc độ đơn hàng đến. Một người chế biến có thể đang làm dở một đơn hàng thì một đơn hàng mới xuất hiện trên màn hình. Giao diện người dùng tại nhà bếp sẽ cần phải đệm các đơn hàng khi chúng đến để tránh gây quá tải cho nhân viên.
Điều đó không có nghĩa là listener tin nhắn là không tốt. Ngược lại, chúng là lựa chọn hoàn hảo khi tin nhắn có thể được xử lý nhanh chóng. Nhưng khi bộ xử lý tin nhắn cần có khả năng yêu cầu thêm tin nhắn theo thời gian riêng của họ, mô hình pull do JmsTemplate cung cấp có vẻ phù hợp hơn.
Vì JMS được định nghĩa bởi một đặc tả chuẩn Java và được hỗ trợ bởi nhiều triển khai broker, nó là một lựa chọn phổ biến cho truyền tin trong Java. Nhưng JMS có một vài điểm yếu, đáng kể nhất là vì là một đặc tả Java, nên việc sử dụng nó bị giới hạn trong các ứng dụng Java. Các lựa chọn truyền tin mới hơn như RabbitMQ và Kafka khắc phục những điểm yếu này và có sẵn cho các ngôn ngữ và nền tảng khác ngoài JVM. Hãy tạm gác JMS lại và xem bạn có thể triển khai việc gửi đơn hàng taco bằng RabbitMQ như thế nào.
