В одном из моих проектов я должен был создать маршрутизатор сообщений, который, как и все маршрутизаторы, должен был брать сообщения 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.