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
