訊息驅動POJO!

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

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

我在這裡提供一個快速介紹。還有很多內容可以展示,我將在後續文章中補充。但目前,這應該足以讓你瞭解如何使用真正基於POJO的非同步JMS並開始執行!希望你和我一樣對此感到興奮;)

前置條件

你需要在類路徑中包含以下JAR檔案。我也列出了我使用的版本(任何spring-2.x版本都可以。事實上我大約在兩分鐘前剛把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",因為正如你稍後會看到的那樣,我將在JMS通訊的兩端匯入這些bean定義。以下是“共享”的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://:61616"/>
    </bean>
 
</beans>

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

示例領域模型

我在這裡故意保持簡單——主要目標是演示各個部分如何協同工作。然而,我想指出的最重要的事情之一是我的“領域”中的這些類都是POJO。你將完全看不到任何Spring或JMS依賴。

最終,我們將接受使用者的輸入(透過標準輸入獲取一個“name”),並將其轉換為某個未指定事件的“註冊請求”。訊息將非同步傳送,但我們將有另一個佇列來處理回覆。ReplyNotifier然後將確認(或“未確認”訊息)寫入標準輸出。

順便說一句,我將所有這些類建立在“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對映做一些處理。此外,你還會看到20%的註冊嘗試將被拒絕(給出-1的確認ID)——這不是處理註冊請求的非常實用的方法,但它會為回覆訊息提供一些變化。同樣,重要的是這個服務類與Spring或JMS沒有任何關聯。然而,正如你稍後將看到的那樣,它將處理透過JMS傳送的訊息的有效載荷。換句話說,這個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定義檔案中,請注意實際接收訊息並啟用非同步 listener 使用的“container”。該container需要知道 connectionFactory 以及接收訊息的 destination。有多種型別的container可用,但這超出了本文部落格的範圍。更多資訊請閱讀參考文件:訊息監聽容器

在這種情況下,“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團隊部落格,瞭解更多關於這些功能的示例和資訊。與此同時,你可以檢視Spring JMS參考文件。此外,在你開始探索這項令人興奮的新功能時,務必訪問Spring支援論壇的“遠端呼叫和JMS”部分

訂閱Spring時事通訊

訂閱Spring時事通訊,保持聯絡

訂閱

領先一步

VMware提供培訓和認證,助你加速前行。

瞭解更多

獲取支援

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

瞭解更多

近期活動

檢視Spring社群的所有近期活動。

檢視全部