Статьи

Отправка бинов в формате XML с помощью JmsTemplate

Мы часто хотим отправлять XML через веб-сервисы. Возможно, в нашем приложении уже настроены схемы или аннотированные классы JAXB2. Что если мы хотим отправить тот же формат через JMS? По умолчанию Spring JMS настроен на сериализацию отправки и получения объектов. Как мы можем перейти на использование JAXB2 (или любой другой стратегии маршалинга OXM)?


В следующем примере предполагается, что мы сначала идем от аннотаций, а не от схемы XML.

Краткая информация

  1. Annotate Bean с JAXB2
  2. Настройте конвертер OXM
  3. Интеграционный тест
  4. Визуализируйте результаты
  5. Конфигурация журнала
  6. Конфигурация Maven

1. Аннотировать бин с JAXB2

  • Используйте аннотации JAXB2 для нашего компонента
package com.gordondickens.jmswithoxm;

import java.math.BigDecimal;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;

@XmlRootElement(name = "account")
@XmlAccessorType(XmlAccessType.FIELD)
public class Account {
@XmlElement(required = true)
private String name;

@XmlElement
private String description;

@XmlElement
private BigDecimal balance;

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public String getDescription() {
return description;
}

public void setDescription(String description) {
this.description = description;
}

public BigDecimal getBalance() {
return balance;
}

public void setBalance(BigDecimal balance) {
this.balance = balance;
}

@Override
public String toString() {
return "Account [name=" + name + ", description=" + description
+ ", balance=" + balance + "]";
}
}

 2. Настройте конвертер OXM

  • Определите наших маршалеров — мы видим, что <oxm: jaxb2-marshaller …> определяет JAXB2 в качестве нашего маршаллера для класса Account
  • Зарегистрируйте наш MarshallingMessageConverter — мы зарегистрируем MarshallingMessageConverter, чтобы использовать маршаллер JAXB2 для входящих и исходящих данных.
  • Зарегистрируйте наш конвертер — в JmsTemplate мы регистрируем наш oxmMessageConverter как messageConverter. Это заменяет SimpleMessageConverter по умолчанию, который будет опираться на сериализацию
  • Обратите внимание на пространство имен ActiveMQ?

 

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:amq="http://activemq.apache.org/schema/core" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:oxm="http://www.springframework.org/schema/oxm"
xsi:schemaLocation="http://www.springframework.org/schema/oxm http://www.springframework.org/schema/oxm/spring-oxm-3.0.xsd
http://activemq.apache.org/schema/core http://activemq.apache.org/schema/core/activemq-core-5.4.0.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

<amq:broker persistent="false" useJmx="true">
<amq:transportConnectors>
<amq:transportConnector uri="tcp://localhost:61616" />
</amq:transportConnectors>
</amq:broker>

<amq:connectionFactory brokerURL="vm://localhost" id="jmsFactory" />

<!-- Spring JMS Template -->
<bean id="jmsTemplate" class="org.springframework.jms.core.JmsTemplate">
<property name="connectionFactory" ref="jmsFactory" />
<property name="defaultDestination" ref="oxmTestQueue" />
<property name="messageConverter" ref="oxmMessageConverter" />
</bean>

<amq:queue id="oxmTestQueue" physicalName="oxm.test.queue" />

<bean id="oxmMessageConverter"
class="org.springframework.jms.support.converter.MarshallingMessageConverter">
<property name="marshaller" ref="marshaller" />
<property name="unmarshaller" ref="marshaller" />
</bean>

<oxm:jaxb2-marshaller id="marshaller">
<oxm:class-to-be-bound name="com.gordondickens.jmswithoxm.Account" />
</oxm:jaxb2-marshaller>
</beans>

 

3. Интеграционный тест

  • Заполнить компонент Account
  • Вызывает convertAndSend на JmsTemplate для маршала и отправки учетной записи
  • Включает обратный вызов postProcessor для регистрации данных XML
  • Вызывает receiveAndConvert на JmsTemplate, чтобы получить и распаковать аккаунт
  • Утверждает конечное состояние
?

package com.gordondickens.jmswithoxm;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import java.math.BigDecimal;
import javax.jms.BytesMessage;
import javax.jms.JMSException;
import javax.jms.Message;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jms.core.JmsTemplate;
import org.springframework.jms.core.MessagePostProcessor;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
public class JmsWithOxmTest {
private static final Logger logger = LoggerFactory
.getLogger(JunitWithOxmTest.class);
private static final String TEST_DEST = "oxmTestQueue";

@Autowired
JmsTemplate jmsTemplate;

@Test
public void testSendingMessage() {
Account account = generateTestMessage();
jmsTemplate.convertAndSend(TEST_DEST, account,
new MessagePostProcessor() {
@Override
public Message postProcessMessage(Message message)
throws JMSException {
if (message instanceof BytesMessage) {
BytesMessage messageBody = (BytesMessage) message;
// message is in write mode, close & reset to start
// of byte stream
messageBody.reset();

Long length = messageBody.getBodyLength();
logger.debug("***** MESSAGE LENGTH is {} bytes",
length);
byte[] byteMyMessage = new byte[length.intValue()];
int red = messageBody.readBytes(byteMyMessage);
logger.debug(
"***** SENDING MESSAGE - \n<!-- MSG START -->\n{}\n<!-- MSG END -->",
new String(byteMyMessage));
}
return message;
}
});
Account account2 = (Account) jmsTemplate.receiveAndConvert(TEST_DEST);
assertNotNull("Account MUST return from JMS", account2);
assertEquals("Name MUST match", account.getName(), account2.getName());
assertEquals("Description MUST match", account.getDescription(),
account2.getDescription());
assertEquals("Balance MUST match", account.getBalance(),
account2.getBalance());
}

private Account generateTestMessage() {
Account account = new Account();
account.setBalance(new BigDecimal(12345.67));
account.setDescription("A no account varmint");
account.setName("Waskally Wabbit");
logger.debug("Generated Test Message: " + account.toString());
return account;
}
}

 4. Визуализация результатов

  • Выполнить: mvn clean test
  • См. Учетную запись XML между блоком <! — MSG START -> & <! — MSG END ->
  • Вывод был отформатирован для ясности
-------------------------------------------------------
T E S T S
-------------------------------------------------------
Running com.gordondickens.jmswithoxm.JmsWithOxmTest

INFO o.s.o.j.Jaxb2Marshaller - Creating JAXBContext
with classes to be bound [class com.gordondickens.jmswithoxm.Account]

DEBUG c.g.j.JmsWithOxmTest - Generated Test Message:
Account [name=Waskally Wabbit, description=A no account
varmint, balance=12345.670000000000072759576141834259033203125]

DEBUG o.s.j.c.JmsTemplate - Executing callback on JMS Session:
ActiveMQSession {id=ID:Technophiliac-61135-1296856347600-2:1:1,started=false}

DEBUG c.g.j.JmsWithOxmTest - ***** MESSAGE LENGTH is 213 bytes

DEBUG c.g.j.JmsWithOxmTest - ***** SENDING MESSAGE -
<!-- MSG START -->
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<account>
<name>Waskally Wabbit</name>
<description>A no account varmint</description>
<balance>12345.670000000000072759576141834259033203125</balance>
</account>
<!-- MSG END -->

DEBUG o.s.j.c.JmsTemplate - Sending created message:
ActiveMQBytesMessage {commandId = 0, responseRequired = false, messageId = null, originalDestination = null, originalTransactionId = null, producerId = null, destination = null, transactionId = null, expiration = 0, timestamp = 0, arrival = 0, brokerInTime = 0, brokerOutTime = 0, correlationId = null, replyTo = null, persistent = false, type = null, priority = 0, groupID = null, groupSequence = 0, targetConsumerId = null, compressed = false, userID = null, content = org.apache.activemq.util.ByteSequence@b364dcb, marshalledProperties = null, dataStructure = null, redeliveryCounter = 0, size = 0, properties = null, readOnlyProperties = false, readOnlyBody = true, droppable = false} ActiveMQBytesMessage{ bytesOut = null, dataOut = null, dataIn = java.io.DataInputStream@1a2d502d }

DEBUG o.s.j.c.JmsTemplate - Executing callback on JMS Session:
ActiveMQSession {id=ID:Technophiliac-61135-1296856347600-2:2:1,started=true}

Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 2.276 sec

 

5. Конфигурация журнала

<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<pattern>%-5level %logger{5} - %msg%n</pattern>
</encoder>
</appender>

<logger name="com.gordondickens" level="DEBUG" />
<logger name="org.springframework.jms" level="DEBUG" />
<logger name="org.springframework.oxm" level="DEBUG" />
<!--<logger name="org.apache.activemq" level="DEBUG"/> -->

<root level="WARN">
<appender-ref ref="STDOUT" />
</root>
</configuration>

 6. Конфигурация Maven

  • Использование Logback для поддержки Log4J, SLF4J, Apache (JCL) и Java Util Logging
  • Включены компоновщики IDE для STS / Eclipse & IntelliJ IDEA
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<modelVersion>4.0.0</modelVersion>
<groupId>com.gordondickens.jmswithoxm</groupId>
<artifactId>spring-jms-oxm</artifactId>
<version>1.0.0.CI-SNAPSHOT</version>
<packaging>jar</packaging>
<name>JMS to OXM Spring</name>
<url>http://gordondickens.com</url>
<description>Sample JMS with OXM Message Conversion</description>
<developers>
<developer>
<id>gordon.dickens</id>
<name>Gordon Dickens</name>
<email>[email protected]</email>
<roles>
<role>Author</role>
</roles>
<organization>http://www.gordondickens.com</organization>
</developer>
</developers>

<properties>
<spring.version>3.0.5.RELEASE</spring.version>
<junit.version>4.8.1</junit.version>
<jms.version>1.1.1</jms.version>
<slf4j.version>1.6.1</slf4j.version>
<activemq.version>5.4.2</activemq.version>
<logback.version>0.9.27</logback.version>
<log4j.version>1.2.16</log4j.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.test.failure.ignore>false</maven.test.failure.ignore>
</properties>
<profiles>
<profile>
<id>quick</id>
<properties>
<maven.test.failure.ignore>true</maven.test.failure.ignore>
</properties>
</profile>
</profiles>

<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>${log4j.version}</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>${slf4j.version}</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jcl-over-slf4j</artifactId>
<version>${slf4j.version}</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jul-to-slf4j</artifactId>
<version>${slf4j.version}</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>${logback.version}</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-core</artifactId>
<version>${logback.version}</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-access</artifactId>
<version>${logback.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>${spring.version}</version>
<exclusions>
<exclusion>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>${spring.version}</version>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.apache.activemq</groupId>
<artifactId>activemq-core</artifactId>
<version>${activemq.version}</version>
<exclusions>
<exclusion>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
</exclusion>
<exclusion>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
</exclusion>
<exclusion>
<groupId>commons-logging</groupId>
<artifactId>commons-logging-api</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.apache.xbean</groupId>
<artifactId>xbean-spring</artifactId>
<version>3.7</version>
<exclusions>
<exclusion>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jms</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-oxm</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId>
<version>2.2.2</version>
</dependency>
<dependency>
<groupId>org.apache.geronimo.specs</groupId>
<artifactId>geronimo-jms_1.1_spec</artifactId>
<version>${jms.version}</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.7.1</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-eclipse-plugin</artifactId>
<version>2.8</version>
<configuration>
<downloadSources>true</downloadSources>
<downloadJavadocs>true</downloadJavadocs>
<wtpversion>2.0</wtpversion>
<additionalBuildcommands>
<buildCommand>
<name>org.springframework.ide.eclipse.core.springbuilder</name>
</buildCommand>
</additionalBuildcommands>
<additionalProjectnatures>
<projectnature>org.springframework.ide.eclipse.core.springnature</projectnature>
</additionalProjectnatures>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>2.3.2</version>
<configuration>
<source>1.6</source>
<target>1.6</target>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-idea-plugin</artifactId>
<version>2.2</version>
<configuration>
<downloadSources>true</downloadSources>
<downloadJavadocs>true</downloadJavadocs>
<dependenciesAsLibraries>true</dependenciesAsLibraries>
</configuration>
</plugin>
</plugins>
</build>
</project>

 6. Получение кода

 Дальнейшее чтение

С http://gordondickens.com/wordpress/2011/02/07/sending-beans-as-xml-with-jmstemplate/