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)