Messaging¶
JMS Spring-ben¶
A Spring a JMS támogatását template-en keresztül adja, melyet JmsTemplate
-nek hívnak.
A JMS-t támogató üzenetküldő rendszerek közül most az Apache ActiveMQ-t fogjuk használni, melynek két változata létezik
- Apache ActiveMQ
- Apache ActiveMQ Artemis
Az utóbbi egy újra implementálása az elsőnek, így ezt célszerű használni új projektekben:
Feladat
Készítsünk egy egyszerű point-to-point üzenet alapú rendszert, melyben Artemis-t használunk! Bár az üzenetküldés lényege pont az, hogy a küldő és a fogadó külön-külön projektben van, hogy növeljük a lazán csatoltságot, ugyanakkor az egyszerűség kedvéért most készítsük el ezt egy alkalmazáson belül!
1. lépés - Artemis telepítés¶
Ebben a lépésben az Artemis-t kell beszereznünk és telepítenünk. Az Artemis rendszer lesz az üzenetküldő rendszerünk, azaz ő adja broker-t. A szoftver letölthető a https://activemq.apache.org/components/artemis/download/ URL-ről. Töltsük le és csomagoljuk ki egy számunkra megfelelő helyre, majd próbáljuk is ki! Ehhez a következőket kell tennünk:
1 |
|
Ezután nyissunk egy parancssort/terminált, majd adjuk ki a következő parancsot!
1 |
|
Ezzel a paranccsal létrehozunk egy új message broker-t, mely az üzenetküldést megvalósítja majd. A fenti parancs kiadása után a rendszer bekéri a felhasználónevet és jelszót, illetve azt, hogy az anonymous hozzáférés engedélyezett-e. Mi a következőket használtuk:
- felhasználónév:
artemis
- jelszó:
artemis
- anonymous hozzáférések tiltása
A felhasználónév és jelszó megadására használhatjuk a következő parancssori paraméterek megadását is:
1 |
|
Megjegyzés
A broker-t a számítógépen bárhova telepíthetjük, nem kell az artemis mappájába lennie.
A létrehozás után az artemis ki is írja, hogy hogyan lehet a broker elindítani:
1 |
|
Amennyiben a háttérben futó win service-ként szeretnénk futtatni a broker-t, arra is lehetőségünk van a következőkkel:
1 2 |
|
Leállítani a
1 |
|
utasítással állíthatjuk le a service-t.
Most a sima command line futtatást válasszuk, mivel így láthatunk sok-sok hasznos infot!
Az egyik hasznos információ a log alján olvasható:
HTTP Server started at http://localhost:8161
Látogassunk is el ide!
Itt menjünk a Management Console
menüponthoz!
Itt kéri majd be a rendszer az általunk a telepítéskor megadott felhasználónevet és jelszót.
Amennyiben be tudtunk lépni, akkor kicsit ismerkedhetünk a felülettel, de most ezt félretesszük egy kicsit és megírjuk magát az alkalmazásunkat, amely ezt a broker-t fogja használni.
2. lépés - JMS alkalmazás megírása¶
Készítsünk egy új alkalmazást a Spring Initializr segítségével!
A függőségek között adjuk meg az Artemis
-t és a Web
-et (továbbá a lombok-ot amennyiben ezt is használni szeretnénk)!
A pom.xml
így a következőképpen fog kinézni (már ami ezt a két függőséget illeti):
1 2 3 4 5 6 7 8 |
|
Amennyiben a classpath-ba bekerül az artemis függőség, akkor alapértelmezetten localhost:61616
-on fog futni az Artemis broker.
Fejlesztés során ez így rendben is van, de production-ben jó ha ezeket tudjuk konfigurálni:
spring.artemis.host
spring.artemis.port
spring.artemis.user
spring.artemis.password
Az egyszerűség kedvéért az üzenetküldést és az üzenet kiolvasást is ugyanebben a projektben végezzük még most.
Elsőként készítsük el az üzenet küldéséért felelős komponenst!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
|
Vegyük szépen sorjában, hogy mi történik itt.
Az üzeneteink küldéséhez szükségünk lesz egy JmsTemplate
-re, melyen keresztül a tényleges üzenetküldést tehetjük meg.
A JmsTemplate
a központi eleme a JMS integrációnak, mely nélkül saját magunknak kellene létrehoznunk a kapcsolatot és a session-t is.
A fenti példában a convertAndSend()
metódust használjuk.
Emellett megtalálható egy másik metódus is, mely a send()
.
Mindkét üzenetküldő metódusnak különböző paraméterezése létezik.
A send()
-nek több esetben egy MessageCreator
-ra van szüksége, mely a Message
objektum előállításáért felelős.
A convertAndSend()
metódus egy Object
objektumot vár, melyet automatikusan átkonvertál egy Message
objektummá.
A convertAndSend()
-nek megadhatunk egy MessagePostProcessor
-t, melyet még a küldés előtt hív meg a rendszer, így tovább formálhatjuk az üzenetet olyan formájúra, amilyenre csak szeretnénk.
A fenti két metódusnak különböző változatai léteznek, melyek attól függően alkalmazhatóak, hogy hogyan adjuk meg az üzenet célpontját (queue vagy topic attól függően, hogy milyen üzenetküldési modellt alkalmazunk). Ezek alapján a következő 3 lehetőségünk van:
- destination nélküli paraméterezés: ilyenkor a default destination-re küldjük az üzenetet
Destination
objektum megadásaString
alapú megadás: A célpontot a neve alapján azonosítjuk
Lássunk egy-két további példát az üzenetküldések megadására! Default queue-ba küldjük az üzenetet:
1 2 3 4 5 6 |
|
Itt egy anonymous inner class-ban adjuk meg a MessageCreator
-t, melynek egyetlen createMessage()
metódusa megkapja a Session
-t.
A megvalósításban a Session
-ön keresztül hozzuk létre a Message
objektumot, melybe belecsomagoljuk az egyszerű String
üzenetünket.
Mivel a MessageCreator
egy Functional interface
, így használhatunk lambdát is:
1 |
|
Mivel nincs megadva a queue, amibe az üzenetet szeretnénk küldeni, így szükséges megadnunk a default destination-t, melyet az application.properties
állományban tehetünk meg:
1 |
|
Amennyiben a Destination
interface-t szeretnénk használni a küldések során, akkor azt talán a legegyszerűbb módon egy bean formájában tudjuk megtenni:
1 2 3 4 |
|
Ezután ezt szabadon injektálhatjuk oda, ahova csak szeretnénk.
A fenti példák alapján látszódik, hogy a send()
metódus használata is egyszerű, azonban a MessageCreator
megadása sokszor csak boilerplate kódot jelent.
Egyszerű esetben nem szeretnénk ezzel foglalkozni, csak a megadott üzenetet elküldeni.
Az első esetben használt convertAndSend()
pontosan ezt oldja meg, de hogyan is működik ez?
A színfalak mögött egy MessageConverter
végzi el a piszkos munkát (ez a Spring része, nem pedig az artemis-é), mely interface a következőképpen néz ki:
1 2 3 4 5 |
|
Elég egyértelmű ez az interface, azonban a legtöbb esetben van beépített megvalósítása is:
MappingJackson2MessageConverter
: Üzenetek JSON-ből és JSON-ba (Jackson lib segítségével)MarshallingMessageConverter
: Üzenetek XML-ből és XML-be (JAXB segítségével)SimpleMessageConverter
: az alábbi konverziók lehetségesek veleString
\(\leftrightarrow\)TextMessage
byte array
\(\leftrightarrow\)BytesMessage
Map
\(\leftrightarrow\)MapMessage
Serializable
\(\leftrightarrow\)ObjectMessage
Ez utóbbi az alapértelmezett, de szükséges, hogy a küldeni kívánt objektum implementálja a Serializable
interface-t.
Megjegyzés
Példa másik MessageConverter
használatára:
1 2 3 4 5 6 |
|
A setTypeIdPropertyName()
-ra azért van szükség, hogy a fogadó tudja, hogy a megkapott üzenetet milyen típusúra kell alakítania.
Ez alapértelmezetten az elküldött objektum fully qualified neve (package-elt osztálynév).
Ütközések esetén használhatjuk a setTypeIdMappings()
metódust, hogy mesterségesen előállított neveket használjunk.
Például:
1 2 3 |
|
Ilyen módon a fogadó a saját értelmezésében fogadhatja a myclass
típusú üzeneteket (más package, más osztálynév, csak bizonyos property-ket kell átkonvertálni, stb.).
Amennyiben az üzenethez el szeretnénk tárolni további információkat, akkor használhatjuk a message post processor-t. Például egy étteremnél szeretnénk, ha tudnánk, hogy a rendelések weben történtek-e vagy az éttermen belül, helyben rendeltek.
1 2 3 4 |
|
Most, hogy megismertük a különféle küldési lehetőségeket, az üzenet küldésének triggereléséhez készítsünk egy egyszerű REST API végpontot!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
|
A végpont egy egyszerű String
-et kap, melyet a korábban ismertetett SimpleMessageSender
segítségével küldünk el az üzenet sorba (queue).
Üzenetek fogadása¶
A JmsTemplate
lehetőséget ad arra is, hogy üzeneteket fogadjunk.
A küldés mintájára a fogadásra a receive
és a receiveAndConvert
metódusok szolgálnak.
Lássunk egy megvalósítást:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
|
A példa egy úgynevezett pull típusú fogadást végez, amihez meg kell hívnunk a receiveMessage()
metódust, mely egészen addig blokkolja a szálat, ameddig nem kapunk üzenetet.
A példában csak a payload-ot használjuk fel.
Arra is lehetőség van, hogy a header-öket is feldolgozzuk.
Ilyen esetben a receive
metódust kell használnunk, illetve szükséges egy MessageConverter
is.
A pull típus mellett létezik a push típusú üzenetfogadás is, mely nem blokkolja a szálat, hanem egy listener-t használ.
Ez a listener megadása a @JmsListener
használatával történik, melyet az adott komponens tetszőleges metódusára aggathatunk fel.
Ebben az esetben az üzenet fogadóját a következőképpen kell elkészítenünk:
1 2 3 4 5 6 7 8 9 |
|
A használt megközelítés kicsit hasonlít a @RequestMapping
megadásokra.
RabbitMQ¶
Telepítési lépések:
Miután telepítettük a RabbitMQ-t, azt service-ként el tudjuk indítani a Start menüből.
A pom.xml
-be a következőt kell hozzáadnunk!
1 2 3 4 |
|
A fenti dependency jelenléte a classpath-ban triggereli az automatikus konfigurációt, mely így létrehoz egy connection factory és egy RabbitTemplate
bean-t is (de erről lesz szó hamarosan).
Az application.properties
fájlban a következő hasznos property-ket adhatjuk meg:
spring.rabbitmq.addresses
: Vesszővel elválasztott lista, mely a RabbitMQ brokerek címeit tartalmazzaspring.rabbitmq.host
: Broker host (default: localhost)spring.rabbitmq.port
: Broker port (default: 5672)spring.rabbitmq.username
: Broker-hez használt felhasználónévspring.rabbitmq.password
: Broker-hez használt jelszó
Ezután lássuk, hogyan küldhetünk üzeneteket!
A folyamat lelkét a RabbitTemplate
osztály adja, mely nagyban hasonlít a JmsTemplate
-hez.
Például használhatóak a következő metódusok:
void send(String exchange, String routingKey, Message message)
void convertAndSend(String exchange, String routingKey, Object message)
void convertAndSend(String exchange, String routingKey, Object message, MessagePostProcessor messagePostProcessor)
Ezen metódusok a legbővebb paraméterezéssel vannak jelen, ugyanakkor ezek változatai is használhatóak, ahol rendre elhagyjuk az exchange, vagy az exchange és a routingKey paramétereket.
A send
egy raw Message
objektumot tud elküldeni, így a saját objektumainkat át kell alakítani üzenetté (MessageConverter
).
A sendAndConvert()
hasonlóan egyedi objektumokon is működik és a konverziót automatikusan, a színfalak mögött végzi a rendszer.
Rendre megtalálható a MessagePostProcessor
is, mellyel az üzenet elküldése előtt manipulálhatjuk a tartalmat.
A fő különbség (mely magából a protokollból jön), hogy destination helyett itt az exchange és a routing key elemeket adhatjuk meg. Amikor nem adjuk meg az exchange-t, akkor az default exchange-be továbbítódik, illetve ugyanez vonatkozik a routingKey-re is.
Lássunk az üzenetküldő service osztályt:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
|
A fenti példában az egyszerű send
metódust használjuk, melyhez elő kell állítanunk az üzenetet.
A RabbitTemplate
bean-t automatikusan létrehozza a rendszer (Spring Boot autokonfiguráció miatt), melytől el tudjuk kérni az általa használt MessageConverter
objektumot.
A konverter-el egyszerűen előállíthatjuk tetszőleges objektum Message
változatát, melyet a send
-el már el is küldünk.
Az üzenetre vonatkozó property-ket nyilván a MessageProperties
-ben lehet megadni, ha erre szükségünk van (a fenti esetben ez nem kellett, így csak egy üreset adunk át).
A send
-nél nem adtunk meg exchange-et, így a küldés során az a default-nak lesz továbbítva.
A routing key-t a megadott property névvel megadhatjuk az application.properties
-ben.