JMS – ActiveMQ

  • Post author:
  • Post category:Java

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