Статьи

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

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

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

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

pom.xml

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
<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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<?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 и выполнить следующую команду для создания моих объектов:

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

Запись

То же самое вы можете достичь с помощью Maven. Этот подход не в репозитории блога, но поверьте мне — он работает

Добавить зависимость к пом

1
2
3
4
5
<dependency>
    <groupId>javax.xml.bind</groupId>
    <artifactId>jaxb-api</artifactId>
    <version>2.1</version>
</dependency>

Используйте плагин (имейте в виду, что файл схемы должен быть указан или по умолчанию ищется в
src / main / xsd / folder)

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<build>
        <pluginManagement>
            <plugins>
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-compiler-plugin</artifactId>
                    <version>2.5.1</version>
                </plugin>
            </plugins>
        </pluginManagement>
        <plugins>
            <plugin>
                <groupId>org.codehaus.mojo</groupId>
                <artifactId>jaxb2-maven-plugin</artifactId>
                <version>1.5</version>
                <executions>
                    <execution>
                        <id>xjc</id>
                        <goals>
                            <goal>xjc</goal>
                        </goals>
                    </execution>
                </executions>
                <configuration>
                    <packageName>pl.grzejszczak.marcin.camel.jaxb.generated</packageName>
                </configuration>
            </plugin>
        </plugins>
    </build>

Пример результата этой команды или плагина maven следующий:

PlayerDetails.java

001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
050
051
052
053
054
055
056
057
058
059
060
061
062
063
064
065
066
067
068
069
070
071
072
073
074
075
076
077
078
079
080
081
082
083
084
085
086
087
088
089
090
091
092
093
094
095
096
097
098
099
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
//
// This file was generated by the JavaTM Architecture for XML Binding(JAXB) Reference Implementation, v2.2.6
// 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.
 *
 *
 
  * <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>
 *
*
 *
 */
@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

1
2
3
4
5
6
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<PlayerDetails>
    <Name>Robert</Name>
    <Surname>Lewandowski</Surname>
    <Position>ATT</Position>
</PlayerDetails>

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

jms.properties

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

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

jmsApplicationContext.xml

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
<?xml version="1.0" encoding="UTF-8"?>
 
 <!-- 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 — давайте начнем с класса, который имеет функцию main

ActiveMQRouter.java

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
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");
    System.exit(0);
   } else {
    LOG.debug("The message should have been enriched but wasn't");
    System.exit(1);
   }
 
  } catch (Exception e) {
   LOG.error("Exception occured", e);
  }
 
 }
 
}

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

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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
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  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  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.