訊息驅動的 POJO!

工程 | Mark Fisher | 2006 年 8 月 11 日 | ...

在 Spring 2.0 的所有新功能和改進中,我必須承認訊息驅動的 POJO 是我個人最喜歡的功能之一。我感覺很多其他的 Spring 使用者也會有同樣的感覺。

在這裡,我提供一個快速的介紹。還有很多東西要展示,我將在後續的文章中繼續說明。不過現在,這些資訊應該足以讓您開始使用真正基於 POJO 的非同步 JMS!我希望您和我一樣對此感到興奮 ;)

先決條件

您需要在您的類別路徑 (classpath) 上擁有以下 JAR 檔案。我也列出了我正在使用的版本(任何 spring-2.x 版本都可以。事實上,我大約 2 分鐘前才將 RC3 放入其中)

  • activemq-core-3.2.2.jar
  • concurrent-1.3.4.jar
  • geronimo-spec-j2ee-managment-1.0-rc4.jar
  • commmons-logging-1.0.4.jar
  • log4j-1.2.9.jar
  • jms-1.1.jar
  • spring-2.0-rc3.jar

設定環境

首先,我們需要設定環境。我將使用 ActiveMQ,但更改提供者的影響將僅限於此檔案中的修改。我將這個檔案命名為 "shared-context.xml",因為您很快就會看到,我將匯入這些 bean 定義,用於 JMS 通訊的雙方。以下是 "共享" 的 bean 定義:連線工廠和兩個佇列(一個用於請求,一個用於回覆)


<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans 
                           http://www.springframework.org/schema/beans/spring-beans.xsd">
	
    <bean id="requestQueue" class="org.activemq.message.ActiveMQQueue">
        <constructor-arg value="requestQueue"/>
    </bean>
 
    <bean id="replyQueue" class="org.activemq.message.ActiveMQQueue">
        <constructor-arg value="replyQueue"/>
    </bean>
 
    <bean id="connectionFactory" class="org.activemq.ActiveMQConnectionFactory">
        <property name="brokerURL" value="tcp://127.0.0.1:61616"/>
    </bean>
 
</beans>

如您所見,我將在 tcp 上執行 ActiveMQ(我只是從發佈版的 bin 目錄執行 'activemq')。也可以執行嵌入式(使用 "vm://127.0.0.1" 代替) - 或者您可以執行 org.activemq.broker.impl.Main 類別的 main 方法。如果您想獲取發佈版,請訪問:http://www.activemq.org

範例網域

我在這裡故意保持簡單 - 主要目標是展示這些部分如何組合在一起。但我想指出的最重要的事情之一是,我「網域」中的這些類別是 POJO。您將看不到任何 Spring 或 JMS 相依性。

最終,我們將接受來自使用者的輸入(通過 stdin 輸入的 "name"),這將轉換為某個未指定事件的 "註冊請求"。訊息將以非同步方式傳送,但我們將有另一個佇列來處理回覆。然後,ReplyNotifier 會將確認訊息(或 "未確認" 訊息)寫入 stdout。

順便說一句,我正在 "blog.mdp" 套件中建立所有這些類別。第一個類別是 RegistrationRequest


package blog.mdp;

import java.io.Serializable;

public class RegistrationRequest implements Serializable {

    private static final long serialVersionUID = -6097635701783502292L;

    private String name;
	
    public RegistrationRequest(String name) {
        this.name = name;
    }
	
    public String getName() {
        return name;
    }
}

接下來是 RegistrationReply


package blog.mdp;

import java.io.Serializable;

public class RegistrationReply implements Serializable {

    private static final long serialVersionUID = -2119692510721245260L;

    private String name;
    private int confirmationId;
	
    public RegistrationReply(String name, int confirmationId) {
        this.name = name;
        this.confirmationId = confirmationId;
    }
	
    public String toString() {
        return (confirmationId >= 0) 
                ? name + ": Confirmed #" + confirmationId 
                : name + ": Not Confirmed";
    }
}

以及 RegistrationService


package blog.mdp;

import java.util.HashMap;
import java.util.Map;

public class RegistrationService {
	
    private Map registrations = new HashMap();
    private int counter = 100;

    public RegistrationReply processRequest(RegistrationRequest request) {
        int id = counter++;
        if (id % 5 == 0) {
            id = -1;
        }
        else {
            registrations.put(new Integer(id), request);
        }
        return new RegistrationReply(request.getName(), id);
    }
}

如您所見,這僅僅提供了一個範例。實際上,可能會對 registrations map 進行一些處理。此外,您會看到 20% 的註冊嘗試會被拒絕(給出 -1 的 confirmationId)- 這不是處理註冊請求的非常實用的方法,但它將為回覆訊息提供一些變化。再次強調,重要的事情是,這個服務類別與 Spring 或 JMS 沒有任何關聯。然而,正如您稍後將看到的,它將處理通過 JMS 傳送的訊息的 payload。換句話說,這個 RegistrationService 訊息驅動的 POJO

最後,建立一個簡單的類別來記錄回覆訊息


package blog.mdp;

public class ReplyNotifier {

    public void notify(RegistrationReply reply) {
        System.out.println(reply);
    }
}

配置訊息驅動的 POJO

現在是最重要的部分。我們如何使用 Spring 來配置 POJO 服務,以便它可以接收 JMS 訊息?答案是以 2 個 bean 定義的形式出現(如果算上服務本身,則是 3 個)。在下一個 bean 定義檔案中,請注意 "container",它實際上接收訊息並啟用非同步監聽器的使用。容器需要知道 connectionFactory 和它從中接收訊息的 destination。有多種類型的容器可用,但這超出了本部落格的範圍。閱讀參考文件以獲取更多資訊:訊息監聽器容器

在本例中,"listener" 是 Spring 的 MessageListenerAdapter 的一個實例。它具有對 delegate(POJO 服務)和處理常式方法的名稱的引用。在本例中,我們還提供了 defaultResponseDestination。對於 void 返回的方法,您顯然不需要這樣做。此外(並且在生產應用程式中可能更常見),您可以忽略此設定,而選擇設定傳入 JMS 訊息的 "reply-to" 屬性。

現在我們已經討論了各種參與者,以下是 bean 定義(我將此檔案命名為 "server-context.xml")


<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans 
                           http://www.springframework.org/schema/beans/spring-beans.xsd">
	
    <import resource="shared-context.xml"/>
	
    <bean id="registrationService" class="blog.mdp.RegistrationService"/>
	
    <bean id="listener" class="org.springframework.jms.listener.adapter.MessageListenerAdapter">
        <property name="delegate" ref="registrationService"/>
        <property name="defaultListenerMethod" value="processRequest"/>
        <property name="defaultResponseDestination" ref="replyQueue"/>
    </bean>
	
    <bean id="container" class="org.springframework.jms.listener.SimpleMessageListenerContainer">
        <property name="connectionFactory" ref="connectionFactory"/>
        <property name="messageListener" ref="listener"/>
        <property name="destination" ref="requestQueue"/>
    </bean>
	
</beans>

這裡的最後一步是為執行服務提供一個引導機制,因為這是一個簡單的獨立範例。我只建立了一個簡單的 main 方法來使用相關的 bean 定義啟動 ApplicationContext,然後阻塞


package blog.mdp;

import java.io.IOException;

import org.springframework.context.support.ClassPathXmlApplicationContext;

public class RegistrationServiceRunner {
	
    public static void main(String[] args) throws IOException {
        new ClassPathXmlApplicationContext("/blog/mdp/server-context.xml");
        System.in.read();
    }
}

配置客戶端

在 "客戶端" 方面,我們將傳送註冊請求並記錄回覆。首先,我將列出 bean 定義。在上一節之後,您應該了解 "container" 和 "listener" 的作用。在本例中,delegateReplyNotifier,並且由於它具有 void 返回類型,因此它本身不會傳送回覆(因此,不存在 'defaultResponseDestination' 屬性)。我將此檔案命名為 "client-context.xml"

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans 
                           http://www.springframework.org/schema/beans/spring-beans.xsd">
	
    <import resource="shared-context.xml"/>
	
    <bean id="replyNotifier" class="blog.mdp.ReplyNotifier"/>
	
    <bean id="listener" class="org.springframework.jms.listener.adapter.MessageListenerAdapter">
        <property name="delegate" ref="replyNotifier"/>
        <property name="defaultListenerMethod" value="notify"/>
    </bean>
	
    <bean id="container" class="org.springframework.jms.listener.SimpleMessageListenerContainer">
        <property name="connectionFactory" ref="connectionFactory"/>
        <property name="messageListener" ref="listener"/>
        <property name="destination" ref="replyQueue"/>
    </bean>
	
    <bean id="jmsTemplate" class="org.springframework.jms.core.JmsTemplate">
        <property name="connectionFactory" ref="connectionFactory"/>
        <property name="defaultDestination" ref="requestQueue"/>
    </bean>
	
</beans>

還有另一個 bean 在那裡定義 - Spring 的 "jmsTemplate" 的一個實例。我們將使用它將註冊請求訊息傳送到其 defaultDestination。使用 Spring 提供的簡單 convertAndSend(..) 方法,傳送 JMS 訊息非常簡單。我建立了一個類別,該類別接收使用者輸入,然後使用此 "jmsTemplate" 傳送訊息


package blog.mdp;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.jms.core.JmsTemplate;

public class RegistrationConsole {
	
    public static void main(String[] args) throws IOException {
        ApplicationContext context = new ClassPathXmlApplicationContext("/blog/mdp/client-context.xml");
        JmsTemplate jmsTemplate = (JmsTemplate) context.getBean("jmsTemplate");
		
        BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));		
		
        for (;;) {
            System.out.print("To Register, Enter Name: ");
            String name = reader.readLine();
            RegistrationRequest request = new RegistrationRequest(name);
            jmsTemplate.convertAndSend(request);
        }
    }
}

執行範例

現在是有趣的部分。啟動 ActiveMQ broker(如 "設定環境" 節中所簡要討論的)。執行 RegistrationServiceRunner 的 main(..) 方法。執行 RegistrationConsole 的 main(..) 方法。輸入一個名稱,您應該在同一個主控台中看到回覆。

更多資源

希望這足以讓您了解 Spring 新的訊息驅動 POJO 支援。但是,正如我提到的,還有很多東西需要參與 - 不同的容器類型、交易支援、消費者執行緒的配置、可外掛的訊息轉換策略等等。請繼續關注 Interface21 Team Blog,以獲取有關這些功能的更多範例和資訊。同時,您可以查看 Spring 參考文件中關於 JMS 的說明。此外,請務必訪問 Spring Support Forums 的 "Remoting and JMS" 區段,以便您開始探索這個令人興奮的新功能。

獲取 Spring 電子報

訂閱 Spring 電子報,隨時掌握最新資訊

訂閱

取得領先

VMware 提供培訓和認證,以加速您的進展。

瞭解更多

取得支援

Tanzu Spring 在一個簡單的訂閱中提供對 OpenJDK™、Spring 和 Apache Tomcat® 的支援和二進位檔案。

瞭解更多

即將到來的活動

查看 Spring 社群中所有即將到來的活動。

檢視全部