Статьи

Spring JMS, Автоматическое преобразование сообщений, Шаблон JMS

В одном из моих проектов я должен был создать маршрутизатор сообщений, который, как и все маршрутизаторы, должен был брать сообщения JMS из одной темы и помещать их в другую. Само сообщение было текстовым сообщением JMS, которое фактически содержало сообщение XML. Более того, после его получения я должен был дополнить сообщение некоторыми дополнительными данными.

Нам не разрешили использовать ни Spring, ни JAXB, ни какую-либо другую полезную библиотеку, поэтому я решил проверить, насколько легко было бы сделать это, используя их. Изначально я хотел использовать только Spring и JAXB, но в следующем посте я попытаюсь повторить тот же сценарий, используя Apache Camel (поэтому в названии пакета вы найдете слово «верблюд»). Связь JMS присутствовала благодаря серверу обмена сообщениями ActiveMQ.

В любом случае возвращаюсь к коду.

Я использовал maven для разрешения зависимостей, и они являются обязательными с точки зрения JMS и JAXB и преобразования сообщений:

pom.xml

<dependency>
 <groupId>org.springframework</groupId>
 <artifactId>spring-jms</artifactId>
 <version>3.1.1.RELEASE</version>
</dependency>
<dependency>
 <groupId>com.sun.xml.bind</groupId>
 <artifactId>jaxb-impl</artifactId>
 <version>2.2.6</version>
</dependency>
<dependency>
 <groupId>org.springframework</groupId>
 <artifactId>spring-oxm</artifactId>
 <version>3.1.1.RELEASE</version>
</dependency>

Вот как я разделил проект (верблюжья часть пакета станет более понятной в следующей статье).


Чтобы преобразовать мое сообщение в объекты через JAXB, мне понадобилась схема:

Player.xsd

<?xml version="1.0" encoding="UTF-8"?>
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">

 <xsd:element name="PlayerDetails">
  <xsd:complexType>
   <xsd:sequence>
    <xsd:element name="Name" type="xsd:string" />
    <xsd:element name="Surname" type="xsd:string" />
    <xsd:element name="Position" type="PositionType" />
    <xsd:element name="Age" type="xsd:int" />
    <xsd:element name="TeamName" type="xsd:string" />
   </xsd:sequence>
  </xsd:complexType>
 </xsd:element>


 <xsd:simpleType name="PositionType">
  <xsd:restriction base="xsd:string">
   <xsd:enumeration value="GK" />
   <xsd:enumeration value="DEF" />
   <xsd:enumeration value="MID" />
   <xsd:enumeration value="ATT" />
  </xsd:restriction>
 </xsd:simpleType>

</xsd:schema>

Мне пришлось скачать двоичные файлы JAXB и выполнить следующую команду для создания моих объектов:

./xjc.sh -p pl.grzejszczak.marcin.camel.jaxb.generated ~/PATH/TO/THE/SCHEMA/FILE/Player.xsd

Примером результата этой команды является:

PlayerDetails.java

//
// This file was generated by the JavaTM Architecture for XML Binding(JAXB) Reference Implementation, v2.2.6
// See <a href="http://java.sun.com/xml/jaxb">http://java.sun.com/xml/jaxb</a>
// Any modifications to this file will be lost upon recompilation of the source schema.
// Generated on: 2012.11.05 at 09:23:22 PM CET
//


package pl.grzejszczak.marcin.camel.jaxb.generated;

import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlType;


/**
 * Java class for anonymous complex type.
 *
 *

The following schema fragment specifies the expected content contained within this class.
 *
 *

<pre> * <complexType>
 *   <complexContent>
 *     <restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
 *       <sequence>
 *         <element name="Name" type="{http://www.w3.org/2001/XMLSchema}string"/>
 *         <element name="Surname" type="{http://www.w3.org/2001/XMLSchema}string"/>
 *         <element name="Position" type="{}PositionType"/>
 *         <element name="Age" type="{http://www.w3.org/2001/XMLSchema}int"/>
 *         <element name="TeamName" type="{http://www.w3.org/2001/XMLSchema}string"/>
 *       </sequence>
 *     </restriction>
 *   </complexContent>
 * </complexType>
 * </pre>
*
 *
 */
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "", propOrder = {
    "name",
    "surname",
    "position",
    "age",
    "teamName"
})
@XmlRootElement(name = "PlayerDetails")
public class PlayerDetails {

    @XmlElement(name = "Name", required = true)
    protected String name;
    @XmlElement(name = "Surname", required = true)
    protected String surname;
    @XmlElement(name = "Position", required = true)
    protected PositionType position;
    @XmlElement(name = "Age")
    protected int age;
    @XmlElement(name = "TeamName", required = true)
    protected String teamName;

    /**
     * Gets the value of the name property.
     *
     * @return
     *     possible object is
     *     {@link String }
     *    
     */
    public String getName() {
        return name;
    }

    /**
     * Sets the value of the name property.
     *
     * @param value
     *     allowed object is
     *     {@link String }
     *    
     */
    public void setName(String value) {
        this.name = value;
    }

    /**
     * Gets the value of the surname property.
     *
     * @return
     *     possible object is
     *     {@link String }
     *    
     */
    public String getSurname() {
        return surname;
    }

    /**
     * Sets the value of the surname property.
     *
     * @param value
     *     allowed object is
     *     {@link String }
     *    
     */
    public void setSurname(String value) {
        this.surname = value;
    }

    /**
     * Gets the value of the position property.
     *
     * @return
     *     possible object is
     *     {@link PositionType }
     *    
     */
    public PositionType getPosition() {
        return position;
    }

    /**
     * Sets the value of the position property.
     *
     * @param value
     *     allowed object is
     *     {@link PositionType }
     *    
     */
    public void setPosition(PositionType value) {
        this.position = value;
    }

    /**
     * Gets the value of the age property.
     *
     */
    public int getAge() {
        return age;
    }

    /**
     * Sets the value of the age property.
     *
     */
    public void setAge(int value) {
        this.age = value;
    }

    /**
     * Gets the value of the teamName property.
     *
     * @return
     *     possible object is
     *     {@link String }
     *    
     */
    public String getTeamName() {
        return teamName;
    }

    /**
     * Sets the value of the teamName property.
     *
     * @param value
     *     allowed object is
     *     {@link String }
     *    
     */
    public void setTeamName(String value) {
        this.teamName = value;
    }

}

@XmlRootElement (name = «PlayerDetails») означает, что этот класс будет выводить корневой узел в файле XML. @XmlAccessorType (XmlAccessType.FIELD), как говорит JavaDoc, означает, что
«каждое нестатическое, непереходное поле в классе, связанном с JAXB, будет автоматически привязано к XML, если только XmlTransient не аннотировано». Другими словами, если у вас есть поле, аннотированное аннотацией XmlTransient, оно не будет сериализовано. Затем у нас есть @XmlType (name = «», propOrder = {«name», «фамилия», «position», «age», «teamName»}), который как JavaDoc определяет « Отображает
класс или тип перечисления в Тип XML-схемы « , Другими словами, наш класс сопоставлен с элементом PlayerDetails в схеме. Наконец, у нас есть аннотация @XmlElement (name = «Name», required = true), которая представляет собой отображение узла (элемента) XML на поле в классе.

Это мое сообщение для отправки, получения, обогащения и маршрутизации:

RobertLewandowski.xml

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<PlayerDetails>
    <Name>Robert</Name>
    <Surname>Lewandowski</Surname>
    <Position>ATT</Position>
</PlayerDetails>

Теперь перейдем к конфигурации JMS — я настроил очереди отправления и назначения

jms.properties

jms.origin=Initial.Queue
jms.destination=Routed.Queue

Это моя конфигурация Spring (я добавил комментарии внутри конфигурации, которые объясняют происхождение этих компонентов):

jmsApplicationContext.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" xmlns:context="http://www.springframework.org/schema/context"
 xmlns:jms="http://www.springframework.org/schema/jms" xmlns:oxm="http://www.springframework.org/schema/oxm"
 xsi:schemaLocation="http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsdhttp://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans-3.0.xsdhttp://www.springframework.org/schema/jmshttp://www.springframework.org/schema/jms/spring-jms-3.0.xsdhttp://www.springframework.org/schema/oxmhttp://www.springframework.org/schema/oxm/spring-oxm-3.0.xsd">

 <!-- Spring configuration based on annotations -->
 <context:annotation-config />
 <!-- Show Spring where to search for the beans (in which packages) -->
 <context:component-scan base-package="pl.grzejszczak.marcin.camel" />

 <!-- Show Spring where to search for the properties files -->
 <context:property-placeholder location="classpath:/camel/jms.properties" />

 <!-- The ActiveMQ connection factory with specification of the server URL -->
 <bean id="activeMQConnectionFactory" class="org.apache.activemq.ActiveMQConnectionFactory">
  <property name="brokerURL" value="tcp://localhost:61616" />
 </bean>

 <!-- Spring's jms connection factory -->
 <bean id="cachingConnectionFactory"
  class="org.springframework.jms.connection.CachingConnectionFactory">
  <property name="targetConnectionFactory" ref="activeMQConnectionFactory" />
  <property name="sessionCacheSize" value="10" />
 </bean>

 <!-- The name of the queue from which we will take the messages -->
 <bean id="origin" class="org.apache.activemq.command.ActiveMQQueue">
  <constructor-arg value="${jms.origin}" />
 </bean>
 <!-- The name of the queue to which we will route the messages -->
 <bean id="destination" class="org.apache.activemq.command.ActiveMQQueue">
  <constructor-arg value="${jms.destination}" />
 </bean>

 <!-- Configuration of the JmsTemplate together with the connection factory and the message converter -->
 <bean id="producerTemplate" class="org.springframework.jms.core.JmsTemplate">
  <property name="connectionFactory" ref="cachingConnectionFactory" />
  <property name="messageConverter" ref="oxmMessageConverter" />
 </bean>

 <!-- Custom message sender sending messages to the initial queue -->
 <bean id="originPlayerSender" class="pl.grzejszczak.marcin.camel.manual.jms.PlayerDetailsSenderImpl">
  <property name="destination" ref="origin" />
 </bean>
 <!-- Custom message sender sending messages to the destination queue -->
 <bean id="destinationPlayerSender" class="pl.grzejszczak.marcin.camel.manual.jms.PlayerDetailsSenderImpl">
  <property name="destination" ref="destination" />
 </bean>

 <!-- Custom message listener - listens to the initial queue  -->
 <bean id="originListenerImpl" class="pl.grzejszczak.marcin.camel.manual.jms.ListenerImpl"/>
 <!-- Custom message listener - listens to the destination queue  -->
 <bean id="destinationListenerImpl" class="pl.grzejszczak.marcin.camel.manual.jms.FinalListenerImpl"/>

 <!-- Spring's jms message listener container - specified the connection factory, the queue to be listened to and the component that listens to the queue -->
 <bean id="jmsOriginContainer" class="org.springframework.jms.listener.DefaultMessageListenerContainer">
  <property name="connectionFactory" ref="cachingConnectionFactory" />
  <property name="destination" ref="origin" />
  <property name="messageListener" ref="originListenerImpl" />
 </bean>

 <!-- Spring's jms message listener container - specified the connection factory, the queue to be listened to and the component that listens to the queue -->
 <bean id="jmsDestinationContainer" class="org.springframework.jms.listener.DefaultMessageListenerContainer">
  <property name="connectionFactory" ref="cachingConnectionFactory" />
  <property name="destination" ref="destination" />
  <property name="messageListener" ref="destinationListenerImpl" />
 </bean>

 <!-- Message converter - automatically marshalls and unmarshalls messages using the provided marshaller / unmarshaller-->
 <bean id="oxmMessageConverter" class="org.springframework.jms.support.converter.MarshallingMessageConverter">
          <property name="marshaller" ref="marshaller" />
          <property name="unmarshaller" ref="marshaller" />
     </bean>

 <!-- Spring's JAXB implementation of marshaller - provided a class the JAXB generated class -->
     <oxm:jaxb2-marshaller id="marshaller">
          <oxm:class-to-be-bound name="pl.grzejszczak.marcin.camel.jaxb.generated.PlayerDetails" />
     </oxm:jaxb2-marshaller>

</beans>

Теперь давайте посмотрим на код Java — давайте начнем с класса, в котором есть основная функция

ActiveMQRouter.java

package pl.grzejszczak.marcin.camel.manual;

import java.io.File;
import java.util.Scanner;

import javax.jms.JMSException;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;

import pl.grzejszczak.marcin.camel.jaxb.PlayerDetailsConverter;
import pl.grzejszczak.marcin.camel.jaxb.generated.PlayerDetails;
import pl.grzejszczak.marcin.camel.manual.jms.Sender;

public class ActiveMQRouter {

 /**
  * @param args
  * @throws JMSException
  */
 public static void main(String[] args) throws Exception {
  ApplicationContext context = new ClassPathXmlApplicationContext("/camel/jmsApplicationContext.xml");
  @SuppressWarnings("unchecked")
  Sender<PlayerDetails> sender = (Sender<PlayerDetails>) context.getBean("originPlayerSender");

  Resource resource = new ClassPathResource("/camel/RobertLewandowski.xml");

  Scanner scanner = new Scanner(new File(resource.getURI())).useDelimiter("\\Z");
  String contents = scanner.next();

  PlayerDetailsConverter converter = context.getBean(PlayerDetailsConverter.class);

  sender.sendMessage(converter.unmarshal(contents));
 }
}

Здесь мы видим, что мы инициализируем контекст Spring из пути к классам и получаем компонент с именем originPlayerSender. Этот компонент используется для отправки сообщения в исходную очередь. Чтобы получить сообщение для отправки, мы извлекаем файл RobertLewandowski.xml из пути к классам и читаем его в переменную String через класс Scanner. Затем мы используем наш собственный класс PlayerDetailsConverter, чтобы демонтировать содержимое String в объект PlayerDetails, который по сути отправляется originPlayerSender в очередь источника.

Теперь давайте посмотрим на логику отправителя:

PlayerDetailsSenderImpl.java

package pl.grzejszczak.marcin.camel.manual.jms;

import javax.jms.Destination;
import javax.jms.JMSException;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jms.core.JmsTemplate;
import org.springframework.stereotype.Component;

import pl.grzejszczak.marcin.camel.jaxb.generated.PlayerDetails;

@Component
public class PlayerDetailsSenderImpl implements Sender<PlayerDetails> {

 private static final Logger LOGGER = LoggerFactory.getLogger(PlayerDetailsSenderImpl.class);

 private Destination destination;

 @Autowired
 private JmsTemplate jmsTemplate;

 @Override
 public void sendMessage(final PlayerDetails object) throws JMSException {
  LOGGER.debug("Sending [{}] to topic [{}]", new Object[] { object, destination });
  jmsTemplate.convertAndSend(destination, object);
 }

 public Destination getDestination() {
  return destination;
 }

 public void setDestination(Destination destination) {
  this.destination = destination;
 }

}

Этот класс реализует мой интерфейс Sender, который предоставляет функцию sendMessage. Мы используем объект JmsTemplate для преобразования и отправки сообщения в указанный пункт назначения, который вводится через Spring.

Хорошо, теперь, когда мы отправили сообщение, кто-то должен его получить:

ListenerImpl.java

package pl.grzejszczak.marcin.camel.manual.jms;

import java.util.List;

import javax.jms.BytesMessage;
import javax.jms.Message;
import javax.jms.MessageListener;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.jms.support.converter.MessageConverter;
import org.springframework.stereotype.Component;

import pl.grzejszczak.marcin.camel.enricher.Enrichable;
import pl.grzejszczak.marcin.camel.jaxb.Convertable;
import pl.grzejszczak.marcin.camel.jaxb.generated.PlayerDetails;

@Component
public class ListenerImpl implements MessageListener {

 private static final Logger LOG = LoggerFactory.getLogger(ListenerImpl.class);

 @Autowired
 private Convertable<PlayerDetails> playerDetailsConverter;

 @Autowired
 private List<Enrichable<PlayerDetails>> listOfEnrichers;

 @Autowired
 private MessageConverter messageConverter;

 @Autowired
 @Qualifier("destinationPlayerSender")
 private Sender<PlayerDetails> sender;

 @Override
 public void onMessage(Message message) {
  if (!(message instanceof BytesMessage)) {
   LOG.error("Wrong msg!");
   return;
  }

  PlayerDetails playerDetails = null;
  try {
   playerDetails = (PlayerDetails) messageConverter.fromMessage(message);

   LOG.debug("Enriching the input message");
   for (Enrichable<PlayerDetails> enrichable : listOfEnrichers) {
    enrichable.enrich(playerDetails);
   }
   LOG.debug("Enriched text message: [{}]", new Object[] { playerDetailsConverter.marshal(playerDetails) });
   sender.sendMessage(playerDetails);
  } catch (Exception e) {
   LOG.error("Exception occured", e);
  }

 }

}

Этот класс имеет список всех классов, реализующих интерфейс Enrichable, благодаря которому он будет обеспечивать обогащение сообщения без необходимости знать количество обогатителей в системе. Существует также класс PlayerDetailsConverter, который помогает с маршалингом и демаршаллингом PlayerDetails. Как только сообщение обогащено, оно отправляется в очередь назначения через bean-компонент, который реализует интерфейс Sender и имеет идентификатор destinationPlayerSender. Важно помнить, что то, что мы получаем из очереди, является сообщением BytesMessage, поэтому мы делаем начальную проверку.

Давайте посмотрим на одно из обогащений (другое — установка другого поля в объекте PlayerDetails )

ClubEnricher.java

package pl.grzejszczak.marcin.camel.enricher;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import pl.grzejszczak.marcin.camel.jaxb.generated.PlayerDetails;

@Component("ClubEnricher")
public class ClubEnricher implements Enrichable<PlayerDetails> {

 private static final Logger LOGGER = LoggerFactory.getLogger(ClubEnricher.class);

 @Override
 public void enrich(PlayerDetails inputObject) {
  LOGGER.debug("Enriching player [{}] with club data", new Object[] { inputObject.getSurname() });
  // Simulating accessing DB or some other service
  try {
   Thread.sleep(2000);
  } catch (InterruptedException e) {
   LOGGER.error("Exception while sleeping occured", e);
  }
  inputObject.setTeamName("Borussia Dortmund");
 }

}

Как видите, класс просто имитирует некоторый доступ к БД или любому другому сервису, а затем устанавливает имя команды во входном объекте PlayerDetails.

Давайте теперь рассмотрим механизм преобразования:

PlayerDetailsConverter.java

package pl.grzejszczak.marcin.camel.jaxb;

import java.io.ByteArrayOutputStream;
import java.io.OutputStream;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;

import org.apache.activemq.util.ByteArrayInputStream;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import pl.grzejszczak.marcin.camel.jaxb.generated.PlayerDetails;

@Component("PlayerDetailsConverter")
public class PlayerDetailsConverter implements Convertable<PlayerDetails> {
 private static final Logger LOGGER = LoggerFactory.getLogger(PlayerDetailsConverter.class);

 private final JAXBContext jaxbContext;
 private final Marshaller jaxbMarshaller;
 private final Unmarshaller jaxbUnmarshaller;

 public PlayerDetailsConverter() throws JAXBException {
  jaxbContext = JAXBContext.newInstance(PlayerDetails.class);
  jaxbMarshaller = jaxbContext.createMarshaller();
  jaxbMarshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
  jaxbUnmarshaller = jaxbContext.createUnmarshaller();
 }

 @Override
 public String marshal(PlayerDetails object) {
  OutputStream stream = new ByteArrayOutputStream();
  try {
   jaxbMarshaller.marshal(object, stream);
  } catch (JAXBException e) {
   LOGGER.error("Exception occured while marshalling", e);
  }
  return stream.toString();
 }

 @Override
 public PlayerDetails unmarshal(String objectAsString) {
  try {
   return (PlayerDetails) jaxbUnmarshaller.unmarshal(new ByteArrayInputStream(objectAsString.getBytes()));
  } catch (JAXBException e) {
   LOGGER.error("Exception occured while marshalling", e);
  }
  return null;
 }

}

В конструкторе мы устанавливаем некоторые компоненты JAXB — JAXBContext, JAXB Marshaller и JAXB Unmarshaller, которые имеют необходимые методы маршала и демаршала.

Последний, но не менее важный, это FinalListenerImpl, который прослушивает входящее сообщение из очереди назначения и закрывает приложение.

FinalListenerImpl.java

package pl.grzejszczak.marcin.camel.manual.jms;

import javax.jms.BytesMessage;
import javax.jms.Message;
import javax.jms.MessageListener;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jms.support.converter.MessageConverter;
import org.springframework.stereotype.Component;

import pl.grzejszczak.marcin.camel.jaxb.generated.PlayerDetails;

@Component
public class FinalListenerImpl implements MessageListener {

 private static final Logger LOG = LoggerFactory.getLogger(FinalListenerImpl.class);

 @Autowired
 private MessageConverter messageConverter;

 @Override
 public void onMessage(Message message) {
  if (!(message instanceof BytesMessage)) {
   LOG.error("Wrong msg!");
   return;
  }

  PlayerDetails playerDetails = null;
  try {
   playerDetails = (PlayerDetails) messageConverter.fromMessage(message);

   if (playerDetails.getTeamName() != null) {
    LOG.debug("Message already enriched! Shutting down the system");
   } else {
    LOG.debug("The message should have been enriched but wasn't");
   }

  } catch (Exception e) {
   LOG.error("Exception occured", e);
  } finally {
   System.exit(0);
  }

 }

}

Используя MessageConverter, после проверки того, что сообщение имеет правильный тип, мы проверяем, было ли уже заполнено имя команды — если это так, мы закрываем приложение.

И журналы таковы:

2012-11-05 [main] org.springframework.context.support.ClassPathXmlApplicationContext:495 Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@34fbb7cb: startup date [Mon Nov 05 21:47:00 CET 2012]; root of context hierarchy
2012-11-05 [main] org.springframework.beans.factory.xml.XmlBeanDefinitionReader:315 Loading XML bean definitions from class path resource [camel/jmsApplicationContext.xml]
2012-11-05 [main] org.springframework.beans.factory.config.PropertyPlaceholderConfigurer:177 Loading properties file from class path resource [camel/jms.properties]
2012-11-05 [main] org.springframework.beans.factory.support.DefaultListableBeanFactory:557 Pre-instantiating singletons in org.springframework.beans.factory.support.DefaultListableBeanFactory@3313beb5: defining beans [org.springframework.context.annotation.internalConfigurationAnnotationProcessor,org.springframework.context.annotation.internalAutowiredAnnotationProcessor,org.springframework.context.annotation.internalRequiredAnnotationProcessor,org.springframework.context.annotation.internalCommonAnnotationProcessor,org.springframework.context.annotation.internalPersistenceAnnotationProcessor,myRoute,AgeEnricher,ClubEnricher,PlayerDetailsConverter,finalListenerImpl,listenerImpl,playerDetailsSenderImpl,org.springframework.beans.factory.config.PropertyPlaceholderConfigurer#0,activeMQConnectionFactory,cachingConnectionFactory,origin,destination,producerTemplate,originPlayerSender,destinationPlayerSender,originListenerImpl,destinationListenerImpl,jmsOriginContainer,jmsDestinationContainer,oxmMessageConverter,marshaller,org.springframework.context.annotation.ConfigurationClassPostProcessor$ImportAwareBeanPostProcessor#0]; root of factory hierarchy
2012-11-05 [main] org.springframework.oxm.jaxb.Jaxb2Marshaller:436 Creating JAXBContext with classes to be bound [class pl.grzejszczak.marcin.camel.jaxb.generated.PlayerDetails]
2012-11-05 [main] org.springframework.context.support.DefaultLifecycleProcessor:334 Starting beans in phase 2147483647
2012-11-05 [main] org.springframework.jms.connection.CachingConnectionFactory:291 Established shared JMS Connection: ActiveMQConnection {id=ID:marcin-SR700-38535-1352148424687-1:1,clientId=null,started=false}
2012-11-05 [main] pl.grzejszczak.marcin.camel.manual.jms.PlayerDetailsSenderImpl:26 Sending [pl.grzejszczak.marcin.camel.jaxb.generated.PlayerDetails@6ae2d0b2] to topic [queue://Initial.Queue]
2012-11-05 [jmsOriginContainer-1] pl.grzejszczak.marcin.camel.manual.jms.ListenerImpl:49 Enriching the input message
2012-11-05 [jmsOriginContainer-1] pl.grzejszczak.marcin.camel.enricher.AgeEnricher:17 Enriching player [Lewandowski] with age data
2012-11-05 [jmsOriginContainer-1] pl.grzejszczak.marcin.camel.enricher.ClubEnricher:16 Enriching player [Lewandowski] with club data
2012-11-05 [jmsOriginContainer-1] pl.grzejszczak.marcin.camel.manual.jms.ListenerImpl:53 Enriched text message: [<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<PlayerDetails>
    <Name>Robert</Name>
    <Surname>Lewandowski</Surname>
    <Position>ATT</Position>
    <Age>19</Age>
    <TeamName>Borussia Dortmund</TeamName>
</PlayerDetails>
]
2012-11-05 [jmsOriginContainer-1] pl.grzejszczak.marcin.camel.manual.jms.PlayerDetailsSenderImpl:26 Sending [pl.grzejszczak.marcin.camel.jaxb.generated.PlayerDetails@3dca1588] to topic [queue://Routed.Queue]
2012-11-05 [jmsDestinationContainer-1] pl.grzejszczak.marcin.camel.manual.jms.FinalListenerImpl:35 Message already enriched! Shutting down the system

Таким образом, благодаря модулю Spring JMS и библиотеке JAXB вы можете легко создавать JMS-слушатели, отправителей и преобразователи сообщений для сообщений XML.