Example code
Example code is from here with some modification. As of writing Spring Boot 3.3.0 (Spring Framework 6.1.8) is used.
complete/pom.xml
Switch to ActiveMQ embedded broker
<dependency> <groupId>org.springframework.boot</groupId> <!--<artifactId>spring-boot-starter-artemis</artifactId>--> <artifactId>spring-boot-starter-activemq</artifactId> </dependency> <dependency> <groupId>org.apache.activemq</groupId> <!--<artifactId>artemis-jakarta-server</artifactId>--> <artifactId>activemq-broker</artifactId> <scope>runtime</scope> </dependency> <!-- ... -->
complete/src/main/resources/application.properties
Switch on debug logging and setup embedded broker url
#spring.artemis.mode=embedded debug=true spring.activemq.broker-url=vm://localhost?broker.persistent=false
complete/src/main/java/hello/Application.java
Use JmsListenerContainerFactory bean created by Spring Boot rather than build by our own
@SpringBootApplication @EnableJms public class Application { /*@Bean public JmsListenerContainerFactory<?> myFactory(ConnectionFactory connectionFactory, DefaultJmsListenerContainerFactoryConfigurer configurer) { DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory(); // This provides all auto-configured defaults to this factory, including the message converter configurer.configure(factory, connectionFactory); // You could still override some settings if necessary. return factory; }*/ //... }
complete/src/main/java/hello/Receiver.java
Specify default JmsListenerContainerFactory
@Component public class Receiver { //@JmsListener(destination = "mailbox", containerFactory = "myFactory") @JmsListener(destination = "mailbox") public void receiveMessage(Email email) { System.out.println("Received <" + email + ">"); } }
Spring Boot auto configuration log
Only JMS related configuration is shown.
ActiveMQAutoConfiguration matched: - @ConditionalOnClass found required classes 'jakarta.jms.ConnectionFactory', 'org.apache.activemq.ActiveMQConnectionFactory' (OnClassCondition) - @ConditionalOnMissingBean (types: jakarta.jms.ConnectionFactory; SearchStrategy: all) did not find any beans (OnBeanCondition) ActiveMQAutoConfiguration#activemqConnectionDetails matched: - @ConditionalOnMissingBean (types: org.springframework.boot.autoconfigure.jms.activemq.ActiveMQConnectionDetails; SearchStrategy: all) did not find any beans (OnBeanCondition) ActiveMQConnectionFactoryConfiguration matched: - @ConditionalOnMissingBean (types: jakarta.jms.ConnectionFactory; SearchStrategy: all) did not find any beans (OnBeanCondition) ActiveMQConnectionFactoryConfiguration.SimpleConnectionFactoryConfiguration matched: - @ConditionalOnProperty (spring.activemq.pool.enabled=false) matched (OnPropertyCondition) ActiveMQConnectionFactoryConfiguration.SimpleConnectionFactoryConfiguration.CachingConnectionFactoryConfiguration matched: - @ConditionalOnClass found required class 'org.springframework.jms.connection.CachingConnectionFactory' (OnClassCondition) - @ConditionalOnProperty (spring.jms.cache.enabled=true) matched (OnPropertyCondition) JmsAnnotationDrivenConfiguration matched: - @ConditionalOnClass found required class 'org.springframework.jms.annotation.EnableJms' (OnClassCondition) JmsAnnotationDrivenConfiguration#jmsListenerContainerFactory matched: - @ConditionalOnSingleCandidate (types: jakarta.jms.ConnectionFactory; SearchStrategy: all) found a single bean 'jmsConnectionFactory'; @ConditionalOnMissingBean (names: jmsListenerContainerFactory; SearchStrategy: all) did not find any beans (OnBeanCondition) JmsAnnotationDrivenConfiguration#jmsListenerContainerFactoryConfigurer matched: - @ConditionalOnMissingBean (types: org.springframework.boot.autoconfigure.jms.DefaultJmsListenerContainerFactoryConfigurer; SearchStrategy: all) did not find any beans (OnBeanCondition) JmsAutoConfiguration matched: - @ConditionalOnClass found required classes 'jakarta.jms.Message', 'org.springframework.jms.core.JmsTemplate' (OnClassCondition) - @ConditionalOnBean (types: jakarta.jms.ConnectionFactory; SearchStrategy: all) found bean 'jmsConnectionFactory' (OnBeanCondition) JmsAutoConfiguration.JmsTemplateConfiguration#jmsTemplate matched: - @ConditionalOnSingleCandidate (types: jakarta.jms.ConnectionFactory; SearchStrategy: all) found a single bean 'jmsConnectionFactory'; @ConditionalOnMissingBean (types: org.springframework.jms.core.JmsOperations; SearchStrategy: all) did not find any beans (OnBeanCondition) JmsAutoConfiguration.MessagingTemplateConfiguration matched: - @ConditionalOnClass found required class 'org.springframework.jms.core.JmsMessagingTemplate' (OnClassCondition) JmsAutoConfiguration.MessagingTemplateConfiguration#jmsMessagingTemplate matched: - @ConditionalOnSingleCandidate (types: org.springframework.jms.core.JmsTemplate; SearchStrategy: all) found a single bean 'jmsTemplate'; @ConditionalOnMissingBean (types: org.springframework.jms.core.JmsMessageOperations; SearchStrategy: all) did not find any beans (OnBeanCondition)
Related interface
Interface | Function |
---|---|
org.springframework.jms.support.destination.DestinationResolver | lookup jakarta.jms.Destination instance by String name |
org.springframework.transaction.jta.JtaTransactionManager | control transaction by JTA |
org.springframework.jms.support.converter.MessageConverter | serialize/deserialize DTO instance |
jakarta.jms.ExceptionListener | processor when jakarta.jms.JMSException throws. One implementation is SingleConnectionFactory, connection managed by that class will be restarted once exception is catched |
io.micrometer.observation.ObservationRegistry | for statistics |
About ConnectionFactory
The implementation of ActiveMQ is org.apache.activemq.ActiveMQConnectionFactory, but Spring Framework does not use it directly. The class is wrapped by org.springframework.jms.connection.CachingConnectionFactory for following
- only one Connection is created and this will be reused
- cache MessageProducer and MessageConsumer (In this example only MessageProducer is cached)
Publisher
Sending message through org.springframework.jms.core.JmsTemplate
jmsTemplate.convertAndSend("mailbox", new Email("info@example.com", "Hello"));
JmsTemplate bean is built by org.springframework.boot.autoconfigure.jms.JmsAutoConfiguration.JmsTemplateConfiguration#jmsTemplate. A MessageConverter bean is necessary for deserializing the DTO.
DestinationResolver used is org.springframework.jms.support.destination.DynamicDestinationResolver, the class is just get jakarta.jms.Destination instance by calling jakarta.jms.Session#createTopic or jakarta.jms.Session#createQueue.
Subscriber
org.springframework.jms.annotation.JmsListener annotation
attribute | Function |
---|---|
id | prefix of thread name which run listener |
containerFactory | bean name of JmsListenerContainerFactory instance |
destination | the destination name for this listener |
subscription | the name of the durable subscription, if any |
selector | an optional message selector for this listener |
concurrency | number of thread running listener |
org.springframework.jms.listener.DefaultMessageListenerContainer class
In JMS specification, asynchronous message processing is supported and the listener is running under threads of the JMS provider.
MessageConsumer consumer; MessageListener listener = new MyListener(); consumer.setMessageListener(listener);
But asynchronous approaches are not used in Spring Framework, synchronous API (polling) is used. The actual code is in org.springframework.jms.support.destination.JmsDestinationAccessor#receiveFromConsumer.
@Nullable protected Message receiveFromConsumer(MessageConsumer consumer, long timeout) throws JMSException { if (timeout > 0) { return consumer.receive(timeout); } else if (timeout < 0) { return consumer.receiveNoWait(); } else { return consumer.receive(); } }
org.springframework.jms.listener.DefaultMessageListenerContainer.AsyncMessageListenerInvoker class is for performing periodically poll jobs. This is scheduled in org.springframework.core.task.SimpleAsyncTaskExecutor.
One DefaultMessageListenerContainer instance is created for one @JmsListener annotated function. This is produced by org.springframework.jms.config.DefaultJmsListenerContainerFactory.
Of course MessageConverter and ExceptionListener instance are necessary.
Top comments (0)