Na początek uruchomimy serwer “lokalnie” w aplikacji Spring Boot. Dodajmy zależności do pom.xml
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-artemis</artifactId> </dependency> <dependency> <groupId>org.apache.activemq</groupId> <artifactId>artemis-server</artifactId> <version>2.10.0</version> </dependency> <dependency> <groupId>org.apache.activemq</groupId> <artifactId>artemis-jms-server</artifactId> <version>2.10.0</version> </dependency>
Model przesyłanej wiadomości:
import java.io.Serializable; import java.util.UUID; @Data @Builder @AllArgsConstructor @NoArgsConstructor public class HelloWorldMessage implements Serializable { static final long serialVersionUID = 7991414071806858377L; private UUID id; private String message; }
Klasa powinna implementować interfejs Serializable
. Podczas generowania serialVersionUID możesz skorzystać z pomocy IntelijIDEA
Dobrą praktyką jest przesyłanie zakodowanego obiektu w postaci JSON zamiast korzystania z mechanizmu Javy serializacji obiektów. W tym celu utwórzmy konwerter, który wykorzystamy podczas przesyłania obiektów. Zdefiniujemy również nazwę kolejki MY_QUEUE
do której trafią wysyłane komunikaty.
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.jms.support.converter.MappingJackson2MessageConverter; import org.springframework.jms.support.converter.MessageConverter; import org.springframework.jms.support.converter.MessageType; @Configuration public class JmsConfig { public static final String MY_QUEUE = "my-hello-world"; @Bean public MessageConverter messageConverter() { MappingJackson2MessageConverter converter = new MappingJackson2MessageConverter(); converter.setTargetType(MessageType.TEXT); converter.setTypeIdPropertyName("_type"); return converter; } }
Uwaga: jeśli komunikaty będą odbierane w aplikacji, w której klasa HelloWorldMessage
znajduje się w innym pakiecie wystąpi problem z konwersją klas
org.springframework.jms.listener.adapter.ListenerExecutionFailedException: Listener method 'public void pl.javascratches.app.config.listener.HelloMessageListener.listen(pl.javascratches.app.domain.HelloWorldMessage,org.springframework.messaging.MessageHeaders,javax.jms.Message)' threw exception; nested exception is org.springframework.jms.support.converter.MessageConversionException: Failed to resolve type id [pl.javascratches.jms.model.HelloWorldMessage]; nested exception is java.lang.ClassNotFoundException: pl.javascratches.jms.model.HelloWorldMessage
Problem można rozwiązać wykorzystując metodę setTypeIdMappings
konwertera MappingJackson2MessageConverter
w aplikacji odbierającej komunikaty.
HashMap<String, Class<?>> idMapping = new HashMap<String, Class<?>>(); idMapping.put("pl.javascratches.jms.model.HelloWorldMessage", HelloWorldMessage.class); converter.setTypeIdMappings(idMapping);
Wykorzystamy metodę convertAndSend
aby wysyłać komunikaty HelloWorldMessage
co 2 sek.
import lombok.RequiredArgsConstructor; import org.springframework.jms.core.JmsTemplate; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; import pl.javascratches.jms.config.JmsConfig; import pl.javascratches.jms.model.HelloWorldMessage; import java.util.UUID; @Component @RequiredArgsConstructor @EnableScheduling public class HelloSender { private final JmsTemplate jmsTemplate; @Scheduled(fixedRate = 2000) public void sendMessage() { System.out.println("Sending message"); HelloWorldMessage message = HelloWorldMessage.builder() .id(UUID.randomUUID()) .message("Hello World!") .build(); jmsTemplate.convertAndSend(JmsConfig.MY_QUEUE, message); System.out.println("Message sent!"); } }
Metoda convertAndSend
wykorzysta zdefiniowany uprzednio konwerter.
Stwórzmy obiekt listenera odbierającego komunikaty z kolejki MY_QUEUE
@Component @RequiredArgsConstructor public class HelloMessageListener { private final JmsTemplate jmsTemplate; @JmsListener(destination = JmsConfig.MY_QUEUE) public void listen(@Payload HelloWorldMessage helloWorldMessage, @Headers MessageHeaders messageHeaders, Message message) { System.out.println("I Got a message!"); System.out.println(helloWorldMessage); } }
Gotowe.
Message sent! I Got a message! HelloWorldMessage(id=66741ad8-3bee-4535-9a00-b6c3b0276c67, message=Hello World!)
W środowisku produkcyjnym najpewniej będziemy wykorzystywać istniejący serwera ActiveMQ zamiast uruchamiać go w aplikacji SpringBoot.
Możemy uruchomić serwer JMS w Dockerze. Może to być kontener ActiveMQ Artemis.
docker run -it --rm -p 8161:8161 -p 61616:61616 -e ARTEMIS_USERNAME=myuser -e ARTEMIS_PASSWORD=otherpassword vromero/activemq-artemis
W RedHat8 korzystam z podman
sudo podman run -it --rm -p 8161:8161 -p 61616:61616 -e ARTEMIS_USERNAME=myuser -e ARTEMIS_PASSWORD=otherpassword vromero/activemq-artemis:2.10.1
ActiveMQ udostępnia konsolę webową, dostępną na porcie 8161
Aby podłączyć się do serwera w aplikacji, należy ustawić parametry połączenia w pliku application.properties
. Usuń również uprzednio dodane zależności do pom.xml
które odpowiadały za uruchomienie serwera ActiveMQ bezpośrednio w SpringBoot).
spring.artemis.user=myuser spring.artemis.password=otherpassword spring.artemis.host=192.168.1.152