How to implement jms queue in spring boot
Asked Answered
X

1

10

I have created a Spring Boot project using initializer and I am trying to create my first message but I have no idea where to start. I am familiar with the same process using JEE so I guess I need to create a factory, a sender and a consumer.

Can someone help me out?

Xantho answered 2/12, 2016 at 12:33 Comment(0)
C
18

The best place to start is the projects getting started guide

Your approach is correct in general terms but he is what the skeleton looks like.

First spring-boot gives you a perfect configuration file structure and if you are using a smart ide like Netbeans then by adding the spring-boot plugin will give you autocomplete in the properties file too. Since Spring acts a bit differently with each broker, in my examples I will use ActiveMQ

By just having ActiveMQ on our build path, Spring Boot will automatically set up a ActiveMQ broker. We need to set a couple properties to make it an in memory broker, without connection pooling. We can do this by setting two properties for Spring Boot.

spring.activemq.in-memory=true
spring.activemq.pooled=false
jms.bookmgrqueue.name=book-mgr-queue #queue name

Similar configurations can be done for other brokers as well.

First you start with the setup of the Spring application. You should place the @EnableJms annotation to enable Jms support and then setup a new queue.

Example

@EnableJms
@Configuration
public class JmsConfiguration {

    @Autowired
    private BeanFactory springContextBeanFactory;

    @Bean
    public DefaultJmsListenerContainerFactory containerFactory(ConnectionFactory connectionFactory) {
        DefaultJmsListenerContainerFactory factory =
                new DefaultJmsListenerContainerFactory();
        factory.setConnectionFactory(connectionFactory);
        factory.setDestinationResolver(new BeanFactoryDestinationResolver(springContextBeanFactory));
        factory.setConcurrency("3-10");
        return factory;
    }

    @Bean
    public JmsTemplate jmsTemplate(ConnectionFactory connectionFactory) throws JMSException {
        return new JmsTemplate(connectionFactory);
    }

}

Listening to queue messages

The listener component (BookMgrQueueListener.java) is using Spring’s @JmsListener annotation with selectors to read the messages with a given Operation header.

@Component
public class BookMgrQueueListener implements Loggable{

    private final BookService bookService;

    @Autowired
    public BookMgrQueueListener(BookService bookService) {
        this.bookService = bookService;
    }

    @JmsListener(containerFactory = "containerFactory",
                 destination = "bookMgrQueueDestination",
                 selector = "Operation = 'Create'")
    public void processCreateBookMessage(BookDTO book) throws JMSException{
        bookService.createNew(book);
    }

    @JmsListener(containerFactory = "containerFactory",
                 destination = "bookMgrQueueDestination",
                 selector = "Operation = 'Update'")
    public void processUpdateBookMessage(BookDTO book) throws JMSException{
        bookService.update(book.getIsbn(), book);
    }

    @JmsListener(containerFactory = "containerFactory",
                 destination = "bookMgrQueueDestination",
                 selector = "Operation = 'Delete'")
    public void processDeleteBookMessage(BookDTO book) throws JMSException{
        bookService.delete(book.getIsbn());
    }

}

Active MQ for test

To test the configuration we are setting up activeMq broker in a new configuration file, ActiveMqConfiguration.java.

@Configuration
public class ActiveMqConfiguration {

    public static final String ADDRESS = "vm://localhost";

    private BrokerService broker;

    @Bean(name="bookMgrQueueDestination")
    public Destination bookMgrQueueDestination(@Value("${jms.bookmgrqueue.name}") String bookMgrQueueName)
            throws JMSException {
        return new ActiveMQQueue(bookMgrQueueName);
    }

    @PostConstruct
    public void startActiveMQ() throws Exception {
        broker = new BrokerService();
        // configure the broker
        broker.setBrokerName("activemq-broker");
        broker.setDataDirectory("target");
        broker.addConnector(ADDRESS);
        broker.setUseJmx(false);
        broker.setUseShutdownHook(false);
        broker.start();
    }

    @PreDestroy
    public void stopActiveMQ() throws Exception {
        broker.stop();
    }

    @Bean
    public ConnectionFactory connectionFactory() {
        return new ActiveMQConnectionFactory(ADDRESS + "?broker.persistent=false");
    }
}

We are setting up a full application context in the testcase but we are replacing the BookService reference in the listener to a MockedBookService which we will use to verify whether the correct calls were executed.

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = Application.class, loader = SpringApplicationContextLoader.class)
@WebAppConfiguration
public class BookMgrQueueListenerIntegrationTest {

    @Autowired(required = false)
    private JmsTemplate jmsTemplate;

    @Autowired
    private BookMgrQueueListener bookMgrQueueListener;


    @Autowired(required = false)
    @Qualifier("bookMgrQueueDestination")
    private Destination bookMgrQueueDestination;

    @Mock
    private BookService mockBookService;

    @Captor
    private ArgumentCaptor<BookDTO> bookArgumentCaptor;

    @Before
    public void setUp(){
        MockitoAnnotations.initMocks(this);
        ReflectionTestUtils.setField(bookMgrQueueListener, "bookService", mockBookService);
    }

    /* ... tests */
}

Finally we add tests for all operations and verify whether the service layer was called with the correct operations and parameters.

/* ... */
public class BookMgrQueueListenerIntegrationTest {
    /* ... */
    @Test
    public void testSendCreateBookMessage(){
        BookDTO book =  new BookDTO("isbn", "title", "author");
        jmsTemplate.convertAndSend(bookMgrQueueDestination, book, Message -> {
            return OperationHeader.CREATE.applyToMessage(Message);
        });
        // verify
        verify(mockBookService).createNew(bookArgumentCaptor.capture());
        assertEquals(book.getIsbn(), bookArgumentCaptor.getValue().getIsbn());
        assertEquals(book.getTitle(), bookArgumentCaptor.getValue().getTitle());
        assertEquals(book.getAuthor(), bookArgumentCaptor.getValue().getAuthor());
    }

    @Test
    public void testSendUpdateBookMessage(){
        BookDTO book =  new BookDTO("isbn", "title", "author");
        jmsTemplate.convertAndSend(bookMgrQueueDestination, book, Message -> {
            return OperationHeader.UPDATE.applyToMessage(Message);
        });
        // verify
        verify(mockBookService).update(eq(book.getIsbn()), bookArgumentCaptor.capture());
        assertEquals(book.getIsbn(), bookArgumentCaptor.getValue().getIsbn());
        assertEquals(book.getTitle(),bookArgumentCaptor.getValue().getTitle());
        assertEquals(book.getAuthor(),bookArgumentCaptor.getValue().getAuthor());
    }

    @Test
    public void testSendDeleteBookMessage(){
        BookDTO book =  new BookDTO("isbn", "title", "author");
        jmsTemplate.convertAndSend(bookMgrQueueDestination, book, Message -> {
            return OperationHeader.DELETE.applyToMessage(Message);
        });
        // verify
        verify(mockBookService).delete(book.getIsbn());
    }

And we are good to go!

References Integrate JMS queue into a Spring Application

Cuisine answered 2/12, 2016 at 12:57 Comment(2)
Thanks a lot! This was very helpful!Xantho
Great answer and references. Just a warning for those would like to Mockito.spy() on the bean with the jmslistener annotated method: the spying keeps it from actually listening on the queue.Loot

© 2022 - 2024 — McMap. All rights reserved.