In spring boot project using kafka template to send the message
import org.apache.kafka.clients.producer.ProducerConfig;
import org.apache.kafka.common.serialization.StringSerializer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.kafka.core.DefaultKafkaProducerFactory;
import org.springframework.kafka.core.KafkaTemplate;
import org.springframework.kafka.core.ProducerFactory;
import org.springframework.kafka.support.SendResult;
import org.springframework.stereotype.Service;
import org.springframework.util.concurrent.ListenableFuture;
import org.springframework.util.concurrent.ListenableFutureCallback;
#Service
#Slf4j
class KafkaMessagePublisherImpl {
#Autowired
private KafkaTemplate kafkaAsyncPublisher;
public void sendMessage() {
ListenableFuture listenableFuture = kafkaAsyncPublisher.send(
"test_topikc",
"key_1",
"{\"greeting\":\"Hello\"}");
listenableFuture.addCallback(new ListenableFutureCallback<SendResult<?, ?>>() {
#Override
public void onSuccess(final SendResult<?, ?> message) {
System.out.println("Sent");
}
#Override
public void onFailure(final Throwable throwable) {
System.out.println("Message sending failed");
}
});
}
public ProducerFactory<String, String> producerFactory() {
Map<String, Object> configProps = new HashMap<>();
configProps.put(
ProducerConfig.BOOTSTRAP_SERVERS_CONFIG,
"brokers1:9092,brokers2:9092");
configProps.put(
ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG,
StringSerializer.class);
configProps.put(
ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG,
StringSerializer.class);
return new DefaultKafkaProducerFactory<>(configProps);
}
#Bean(name = "kafkaAsyncPublisher")
public KafkaTemplate<String, String> kafkaAsyncPublisher() {
return new KafkaTemplate<>(producerFactory());
}
}
While using the sendMessage method in another class, the following error is coming
Caused by: org.springframework.kafka.core.KafkaProducerException: Failed to send; nested exception is org.apache.kafka.common.errors.TimeoutException: Failed to update metadata after 60000 ms.
at org.springframework.kafka.core.KafkaTemplate$1.onCompletion(KafkaTemplate.java:259)
at org.apache.kafka.clients.producer.KafkaProducer.doSend(KafkaProducer.java:760)
at org.apache.kafka.clients.producer.KafkaProducer.send(KafkaProducer.java:701)
at org.springframework.kafka.core.DefaultKafkaProducerFactory$CloseSafeProducer.send(DefaultKafkaProducerFactory.java:170)
at org.springframework.kafka.core.KafkaTemplate.doSend(KafkaTemplate.java:245)
at org.springframework.kafka.core.KafkaTemplate.send(KafkaTemplate.java:157)
at com.tesco.fps.messaging.service.impl.KafkaMessagePublisherImpl.lambda$sendMessage$0(KafkaMessagePublisherImpl.java:52)
... 34 common frames omitted
Caused by: org.apache.kafka.common.errors.TimeoutException: Failed to update metadata after 60000 ms.
Springboot:1.5.10.RELEASE and compile('org.apache.kafka:kafka_2.12:0.11.0.0') are being used as gradle dependency along with spring-cloud-stream
Only minor change was done to make it working
#Service
#Slf4j
class KafkaMessagePublisherImpl {
#Autowired
private KafkaTemplate<String,String> kafkaAsyncPublisher;
//Rest is same.
}
Related
I have an spring boot kafka consumer application. So as per the documentation added factory.getContainerProperties().setDeliveryAttemptHeader(true); in below and still not able to receive the deliveryAttempt header in consumer record at the kafka listener. What am I missing? What changes do I need to get DeliveryAttempt Header at the consumer side?
#Bean
public ConcurrentKafkaListenerContainerFactory<Integer, String> kafkaListenerContainerFactory(
ConsumerFactory<Integer, String> consumerFactory) {
final ConcurrentKafkaListenerContainerFactory<Integer, String> factory = new ConcurrentKafkaListenerContainerFactory<>();
factory.setConsumerFactory(consumerFactory);
factory.setCommonErrorHandler(testErrorHandler);
factory.setConcurrency(10);
factory.getContainerProperties().setDeliveryAttemptHeader(true);
return factory;
}
ErrorHandler:
import org.apache.commons.logging.LogFactory;
import org.apache.kafka.clients.consumer.Consumer;
import org.apache.kafka.clients.consumer.ConsumerRecord;
import org.springframework.kafka.listener.CommonErrorHandler;
import org.springframework.kafka.listener.MessageListenerContainer;
import org.springframework.stereotype.Component;
#Component
public class TestErrorHandler implements CommonErrorHandler {
#Override
public void handleRecord(Exception thrownException, ConsumerRecord<?, ?> record, Consumer<?, ?> consumer,
MessageListenerContainer container) {
LogFactory.getLog(getClass()).error("'handleRecord' is not implemented by this handler", thrownException);
}
}
In the above class, I have implemented CommonErrorHander(which extends DeliveryAttemptAware class internally).
Consumer:
import org.apache.kafka.clients.consumer.ConsumerRecord;
import org.springframework.kafka.annotation.KafkaListener;
import org.springframework.stereotype.Component;
import lombok.extern.slf4j.Slf4j;
#Component
#Slf4j
public class DataConsumer {
#KafkaListener(topics = { "topic1" }, groupId = "test-group-1", containerFactory = "kafkaListenerContainerFactory")
public void listenTestEvents(final ConsumerRecord<Integer, String> message) {
log.info("Received TestEvent message :{}, headers: {}", message, message.headers());
if (true) {
throw new RuntimeException();
}
}
}
application.yml
spring:
kafka:
consumer:
bootstrap-servers: localhost:9092
group-id: test-group-1
auto-offset-reset: earliest
key-deserializer: org.apache.kafka.common.serialization.IntegerDeserializer
value-deserializer: org.apache.kafka.common.serialization.StringDeserializer
AppConfig:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.kafka.KafkaProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.kafka.config.ConcurrentKafkaListenerContainerFactory;
import org.springframework.kafka.core.ConsumerFactory;
import org.springframework.kafka.core.DefaultKafkaConsumerFactory;
import com.tmobile.retrykafkaconsume.kafka.TestErrorHandler;
#Configuration
public class AppConfig {
#Autowired
private TestErrorHandler testErrorHandler;
#Bean
public ConsumerFactory<Integer, String> consumerFactory(KafkaProperties kafkaProperties) {
return new DefaultKafkaConsumerFactory<Integer, String>(kafkaProperties.buildConsumerProperties());
}
#Bean
public ConcurrentKafkaListenerContainerFactory<Integer, String> kafkaListenerContainerFactory(
ConsumerFactory<Integer, String> consumerFactory) {
final ConcurrentKafkaListenerContainerFactory<Integer, String> factory = new ConcurrentKafkaListenerContainerFactory<>();
factory.setConsumerFactory(consumerFactory);
factory.setCommonErrorHandler(testErrorHandler);
factory.setConcurrency(10);
factory.getContainerProperties().setDeliveryAttemptHeader(true);
return factory;
}
}
The error handler must implement DeliveryAttemptAware; see FailedRecordProcessor which is a super class of DefaultErrorHandler.
Show your testErrorHandler code.
We do have following Configuration to build Spring beans.
Built configuration beans in Spring Beans with Connection Pool manager and ClosableHttpClient.
RestClient class autowires RestTemplate objects to make post calls.
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.web.client.ResponseErrorHandler;
import org.springframework.web.client.RestTemplate;
import java.time.Duration;
import java.util.List;
#Configuration
#Data
#Slf4j
public class ApplicationConfig {
#Value("${nexus.security.enabled}")
private boolean securityEnabled;
#Value("${nexus.security.ignored}")
private List<String> securityIgnoredList;
#Value("${external.card.readTimeoutInMillis}")
private int readTimeoutInMillis;
#Value("${external.card.connectionTimeoutInMillis}")
private int connectionTimeoutInMillis;
#Value("${external.card.connectionRequestTimeoutInMillis}")
private int connectionRequestTimeoutInMillis;
#Value("${external.card.maxTotalConnections}")
private int maxTotalConnections;
#Value("${external.card.defaultMaxPerRoute}")
private int defaultMaxPerRoute;
#Bean
#Qualifier(ConfigConstants.CONFIG_NAME_DEFAULT)
public PoolingHttpClientConnectionManager poolingHttpClientConnectionManager()
{
log.info("PoolingHttpClientConnectionManager Build for {}", ConfigConstants.CONFIG_NAME_DEFAULT);
PoolingHttpClientConnectionManager poolingHttpClientConnectionManager = new PoolingHttpClientConnectionManager();
poolingHttpClientConnectionManager.setMaxTotal(maxTotalConnections);
poolingHttpClientConnectionManager.setDefaultMaxPerRoute(defaultMaxPerRoute);
return poolingHttpClientConnectionManager;
}
#Bean(destroyMethod = "close")
#Qualifier(ConfigConstants.CONFIG_NAME_DEFAULT)
public CloseableHttpClient closeableHttpClient(
#Qualifier(ConfigConstants.CONFIG_NAME_DEFAULT) PoolingHttpClientConnectionManager poolingHttpClientConnectionManager
) {
log.info("CloseableHttpClient Build for {}", ConfigConstants.CONFIG_NAME_DEFAULT);
return HttpClientBuilder.create().setConnectionManager(poolingHttpClientConnectionManager).build();
}
#Bean
#Qualifier(ConfigConstants.CONFIG_NAME_DEFAULT)
public HttpComponentsClientHttpRequestFactory requestFactory(
#Qualifier(ConfigConstants.CONFIG_NAME_DEFAULT) CloseableHttpClient httpClient
) {
log.info("HttpComponentsClientHttpRequestFactory Build for {}", ConfigConstants.CONFIG_NAME_DEFAULT);
HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory(httpClient);
requestFactory.setReadTimeout(readTimeoutInMillis);
requestFactory.setConnectTimeout(connectionTimeoutInMillis);
requestFactory.setConnectionRequestTimeout(connectionRequestTimeoutInMillis);
return requestFactory;
}
#Bean
#Qualifier(ConfigConstants.CONFIG_NAME_DEFAULT)
public RestTemplate restTemplate(#Qualifier(ConfigConstants.CONFIG_NAME_DEFAULT) RestTemplateBuilder builder,
ResponseErrorHandler errorHandler
)
{
log.info("RestTemplate Build for {}", ConfigConstants.CONFIG_NAME_DEFAULT);
RestTemplate restTemplate = builder.build();
restTemplate.getMessageConverters().add(new MappingJackson2HttpMessageConverter());
restTemplate.getMessageConverters().add(new StringHttpMessageConverter());
restTemplate.setErrorHandler(errorHandler);
return restTemplate;
}
#Bean
#Qualifier(ConfigConstants.CONFIG_NAME_DEFAULT)
public RestTemplateBuilder restTemplateBuilder(
#Qualifier(ConfigConstants.CONFIG_NAME_DEFAULT) HttpComponentsClientHttpRequestFactory requestFactory
) {
log.info("RestTemplateBuilder Build for {}", ConfigConstants.CONFIG_NAME_DEFAULT);
return new RestTemplateBuilder()
.setReadTimeout(Duration.ofMillis(readTimeoutInMillis))
.setConnectTimeout(Duration.ofMillis(connectionTimeoutInMillis))
.requestFactory(
() -> requestFactory);
}
}
And RestClient autowired into
public RestClient(#Qualifier(ConfigConstants.CONFIG_NAME_DEFAULT) RestTemplate restTemplate) {
this.restTemplate = restTemplate;
}
Following code has been used to make call to
ResponseEntity<String> response = restTemplate.exchange(endpointURL, HttpMethod.POST, httpEntity, String.class);
Often service is working as expected.
But, it throws following error sometimes...
java.net.SocketException: Connection reset
at java.base/java.net.SocketInputStream.read(SocketInputStream.java:186)
at java.base/java.net.SocketInputStream.read(SocketInputStream.java:140)
at java.base/sun.security.ssl.SSLSocketInputRecord.read(SSLSocketInputRecord.java:478)
at java.base/sun.security.ssl.SSLSocketInputRecord.readHeader(SSLSocketInputRecord.java:472)
at java.base/sun.security.ssl.SSLSocketInputRecord.bytesInCompletePacket(SSLSocketInputRecord.java:70)
at java.base/sun.security.ssl.SSLSocketImpl.readApplicationRecord(SSLSocketImpl.java:1364)
at java.base/sun.security.ssl.SSLSocketImpl$AppInputStream.read(SSLSocketImpl.java:973)
at org.apache.http.impl.io.SessionInputBufferImpl.streamRead(SessionInputBufferImpl.java:137)
at org.apache.http.impl.io.SessionInputBufferImpl.fillBuffer(SessionInputBufferImpl.java:153)
at org.apache.http.impl.io.SessionInputBufferImpl.readLine(SessionInputBufferImpl.java:280)
After few minutes, the service is working as expected.
Could you please help me to fix this problem?
Even though the message is received by the MessageListener, I don't want to remove from the Queue, I want to do some processing in onMessage method and based on the result:
I want to commit(); for Success - so the message will be completely removed from the Queue.
For Failures - don't commit - rollback(); so the message will be redelivered (some times by default) and then goes to Dead letter Queue (DLQ). That’s OK for us.
I use: SpringBoot and hornetq (spring-boot-starter-hornetq-1.4.7.RELEASE).
Settings:
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.env.Environment;
import org.springframework.jms.config.DefaultJmsListenerContainerFactory;
import org.springframework.jms.config.JmsListenerContainerFactory;
import org.springframework.jms.connection.UserCredentialsConnectionFactoryAdapter;
import org.springframework.jndi.JndiObjectFactoryBean;
import org.springframework.jndi.JndiTemplate;
import javax.jms.ConnectionFactory;
import javax.naming.Context;
import javax.naming.NamingException;
import java.util.Properties;
import static com.test.hornetq.Receiver.LOG;
import static javax.jms.Session.SESSION_TRANSACTED;
#Configuration
public class JmsConfig {
private String host;
private String port;
private String connectionFactoryJndiName;
private String jndiInit;
private String user;
private String password;
private String jmsReceiverConcurrency;
public JmsConfig(final Environment env) {
host = env.getProperty("host");
port = env.getProperty("port");
connectionFactoryJndiName = env.getProperty("connectionfactory.jndiname");
jndiInit = env.getProperty("jndiInit");
user = env.getProperty("user");
password = env.getProperty("password");
jmsReceiverConcurrency = env.getProperty("jmsReceiverConcurrency");
}
#Bean
public JndiTemplate jndiTemplate() {
final JndiTemplate jndiTemplate = new JndiTemplate();
jndiTemplate.setEnvironment(getProperties());
return jndiTemplate;
}
#Bean
public JndiObjectFactoryBean jmsConnectionFactory() throws NamingException {
final JndiObjectFactoryBean jndiObjectFactoryBean = new JndiObjectFactoryBean();
jndiObjectFactoryBean.setJndiTemplate(jndiTemplate());
jndiObjectFactoryBean.setJndiName(connectionFactoryJndiName);
jndiObjectFactoryBean.afterPropertiesSet();
return jndiObjectFactoryBean;
}
#Bean
#Primary
public ConnectionFactory connectionFactory() throws NamingException {
final UserCredentialsConnectionFactoryAdapter adapter = new UserCredentialsConnectionFactoryAdapter();
adapter.setTargetConnectionFactory((ConnectionFactory) jmsConnectionFactory().getObject());
adapter.setUsername(user);
adapter.setPassword(password);
return adapter;
}
#Bean
public JmsListenerContainerFactory<?> myJmsContainerFactory() throws NamingException {
final DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory();
factory.setConnectionFactory(connectionFactory());
factory.setSubscriptionDurable(false);
factory.setConcurrency(jmsReceiverConcurrency);
factory.setMaxMessagesPerTask(1);
factory.setSessionTransacted(true);
factory.setSessionAcknowledgeMode(SESSION_TRANSACTED);
factory.setErrorHandler(t -> {
LOG.error("Error in listener!", t);
});
return factory;
}
private Properties getProperties() {
final Properties jndiProps = new Properties();
jndiProps.setProperty(Context.INITIAL_CONTEXT_FACTORY, jndiInit);
jndiProps.setProperty(Context.PROVIDER_URL, "http-remoting://" + host + ":" + port);
jndiProps.setProperty(Context.SECURITY_PRINCIPAL, user);
jndiProps.setProperty(Context.SECURITY_CREDENTIALS, password);
return jndiProps;
}
}
And receiver:
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.jms.annotation.JmsListener;
import org.springframework.stereotype.Component;
import javax.jms.JMSException;
import javax.jms.MapMessage;
import javax.jms.Session;
#Component
public class Receiver {
#JmsListener(destination = "${destination.name}", containerFactory = "myJmsContainerFactory")
public void onReceive(final MapMessage message, Session session) throws JMSException {
try {
System.out.println(">>>> " + message);
session.rollback();
} catch (Exception ex) {
System.out.println(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>THROW ");
throw ex;
}
}
}
But when I do rollback(); nothing happen and message doesn't comeback.
The code work. The problem was in hornetq settings in server side.
<pre-acknowledge>true</pre-acknowledge>
Extra Acknowledge Modes
Please note, that if you use pre-acknowledge mode, then you will lose transactional semantics for messages being consumed, since clearly they are being acknowledged first on the server, not when you commit the transaction. This may be stating the obvious but we like to be clear on these things to avoid confusion!
I have 2 applications, the first app starts a ActiveMQ broker ( https://spring.io/guides/gs/messaging-jms/ ).
At the second app I want to subcribe a topic from the first app.
How can I do this without starting a ActiveMQ Server?
Possible solution:
Server Application Project
import java.time.LocalDateTime;
import javax.jms.ConnectionFactory;
import org.apache.activemq.ActiveMQConnectionFactory;
import org.apache.activemq.broker.BrokerService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.jms.annotation.EnableJms;
import org.springframework.jms.config.DefaultJmsListenerContainerFactory;
import org.springframework.jms.core.JmsTemplate;
import org.springframework.jms.support.converter.MappingJackson2MessageConverter;
import org.springframework.jms.support.converter.MessageConverter;
import org.springframework.jms.support.converter.MessageType;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.ui.ModelMap;
#SpringBootApplication
#EnableJms
#EnableScheduling
public class JsmServerApplication {
#Autowired
JmsTemplate jmsTemplate;
#Bean
public BrokerService broker() throws Exception {
BrokerService ret = new BrokerService();
ret.addConnector("tcp://0.0.0.0:4444"); // allow remote connections
ret.setBrokerName("primary-broker");
ret.setUseJmx(true);
return ret;
}
#Bean
public ConnectionFactory connectionFactory() {
return new ActiveMQConnectionFactory("tcp://localhost:4444");
}
#Bean
public DefaultJmsListenerContainerFactory jmsListenerContainerFactory(ConnectionFactory connectionFactory) {
DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory();
factory.setConnectionFactory(connectionFactory);
factory.setMessageConverter(jacksonJmsMessageConverter());
return factory;
}
#Bean
public MessageConverter jacksonJmsMessageConverter() {
MappingJackson2MessageConverter converter = new MappingJackson2MessageConverter();
converter.setTargetType(MessageType.TEXT);
converter.setTypeIdPropertyName("_type");
return converter;
}
public static void main(String[] args) {
SpringApplication.run(JsmServerApplication.class, args);
}
#Scheduled(cron = "*/5 * * * * ?")
public void run() {
ModelMap msg = new ModelMap("now", LocalDateTime.now().toString());
System.out.println("Sending: " + msg);
jmsTemplate.convertAndSend("messages", msg);
}
}
Client Application Project
import javax.jms.ConnectionFactory;
import org.apache.activemq.ActiveMQConnectionFactory;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.jms.annotation.EnableJms;
import org.springframework.jms.annotation.JmsListener;
import org.springframework.jms.config.DefaultJmsListenerContainerFactory;
import org.springframework.jms.support.converter.MappingJackson2MessageConverter;
import org.springframework.jms.support.converter.MessageConverter;
import org.springframework.jms.support.converter.MessageType;
import org.springframework.ui.ModelMap;
#SpringBootApplication
#EnableJms
public class JsmClientApplication {
#Bean
public ConnectionFactory connectionFactory() {
return new ActiveMQConnectionFactory("tcp://localhost:4444");
}
#Bean
public DefaultJmsListenerContainerFactory jmsListenerContainerFactory(ConnectionFactory connectionFactory) {
DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory();
factory.setConnectionFactory(connectionFactory);
factory.setMessageConverter(jacksonJmsMessageConverter());
return factory;
}
#Bean
public MessageConverter jacksonJmsMessageConverter() {
MappingJackson2MessageConverter converter = new MappingJackson2MessageConverter();
converter.setTargetType(MessageType.TEXT);
converter.setTypeIdPropertyName("_type");
return converter;
}
#JmsListener(destination = "messages", containerFactory = "jmsListenerContainerFactory")
public void msg(ModelMap msg) {
System.out.println(msg);
}
public static void main(String[] args) {
SpringApplication.run(JsmClientApplication.class, args);
}
}
Is it a correct approch?
Solved with this:
http://javasampleapproach.com/java-integration/activemq-work-spring-jms-activemq-topic-publisher-subcribers-pattern-using-springboot
You can use the MessageConsumer to consume the data like the code below
public static void main(String[] args) throws JMSException {
// Getting JMS connection from the server
ConnectionFactory connectionFactory = new ActiveMQConnectionFactory(url);
Connection connection = connectionFactory.createConnection();
connection.start();
Session session = connection.createSession(false,
Session.AUTO_ACKNOWLEDGE);
Topic topic = session.createTopic("topic");
MessageConsumer consumer = session.createConsumer(topic);
MessageListener listner = new MessageListener() {
public void onMessage(Message message) {
try {
//do operations
} catch (JMSException e) {
}
}
};
consumer.setMessageListener(listner);
connection.close();
}
Since you are using the ActiveMQConnectionFactory, you can set the broker as below
BrokerService broker = new BrokerService();
broker.addConnector("tcp://localhost:4444);
broker.setPersistent(false);
ActiveMQConnectionFactory cf = new ActiveMQConnectionFactory("vm://localhost?broker.persistent=false");
If you do not have any restrictions in not using ActiveMQ, You can use Kafka for doing the same. Kafka provides you a highly scalable and distributed Message Bus with simple API.
https://kafka.apache.org/quickstart
I am not sure about the constraints but I just wanted to give you a feel of Kafka. However, the above code should help you in understanding the concept of subscribing and consuming messages from a topic.
See this answer for how to listen on a tcp port instead of the vm:// transport.
I am currently facing a problem which only happens sometimes and is very hard to reproduce. Therefore I have problems actually creating a compelling test case.
Our setup looks like this:
spring integration with spring boot
rabbitmq listener
custom bus which manages transaction aware messages
The setup is not the newest anymore and probably a lot of code could be replaced by more idiomatic spring wiring.
Though this is the exception I am getting when the service is started:
Dispatcher has no subscribers, failedMessage=GenericMessage [payload=com.foobar.service.greencheckout.message.GreenOrderPropertyAddedMessage#2ba0efb2, headers={id=f750a792-6b01-16d3-8206-6e553a03f8fa, type=com.foobar.service.greencheckout.message.GreenOrderPropertyAddedMessage, amqp_deliveryMode=PERSISTENT, timestamp=1505797680967}], failedMessage=GenericMessage [payload=com.foobar.service.greencheckout.message.GreenOrderPropertyAddedMessage#2ba0efb2, headers={id=f750a792-6b01-16d3-8206-6e553a03f8fa, type=com.foobar.service.greencheckout.message.GreenOrderPropertyAddedMessage, amqp_deliveryMode=PERSISTENT, timestamp=1505797680967}]
at org.springframework.integration.channel.AbstractSubscribableChannel.doSend(AbstractSubscribableChannel.java:93)
at org.springframework.integration.channel.AbstractMessageChannel.send(AbstractMessageChannel.java:423)
at org.springframework.integration.channel.AbstractMessageChannel.send(AbstractMessageChannel.java:373)
at org.springframework.messaging.core.GenericMessagingTemplate.doSend(GenericMessagingTemplate.java:115)
at org.springframework.messaging.core.GenericMessagingTemplate.doSend(GenericMessagingTemplate.java:45)
at org.springframework.messaging.core.AbstractMessageSendingTemplate.send(AbstractMessageSendingTemplate.java:105)
at org.springframework.messaging.core.AbstractMessageSendingTemplate.convertAndSend(AbstractMessageSendingTemplate.java:143)
at org.springframework.messaging.core.AbstractMessageSendingTemplate.convertAndSend(AbstractMessageSendingTemplate.java:135)
at org.springframework.integration.gateway.MessagingGatewaySupport.send(MessagingGatewaySupport.java:392)
at org.springframework.integration.gateway.GatewayProxyFactoryBean.invokeGatewayMethod(GatewayProxyFactoryBean.java:477)
at org.springframework.integration.gateway.GatewayProxyFactoryBean.doInvoke(GatewayProxyFactoryBean.java:429)
at org.springframework.integration.gateway.GatewayProxyFactoryBean.invoke(GatewayProxyFactoryBean.java:420)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:213)
at com.sun.proxy.$Proxy223.send(Unknown Source)
at com.foobar.library.messaging.bus.TransactionAwareBus.afterCommit(TransactionAwareBus.java:50)
at org.springframework.transaction.support.TransactionSynchronizationUtils.invokeAfterCommit(TransactionSynchronizationUtils.java:133)
at org.springframework.transaction.support.TransactionSynchronizationUtils.triggerAfterCommit(TransactionSynchronizationUtils.java:121)
at org.springframework.transaction.support.AbstractPlatformTransactionManager.triggerAfterCommit(AbstractPlatformTransactionManager.java:958)
at org.springframework.transaction.support.AbstractPlatformTransactionManager.processCommit(AbstractPlatformTransactionManager.java:803)
at org.springframework.transaction.support.AbstractPlatformTransactionManager.commit(AbstractPlatformTransactionManager.java:730)
at org.springframework.transaction.interceptor.TransactionAspectSupport.commitTransactionAfterReturning(TransactionAspectSupport.java:504)
at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:292)
at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:96)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:673)
at com.foobar.service.greencheckout.silo.domain.modifyingservice.TransitionService$$EnhancerBySpringCGLIB$$de36730c.execute(<generated>)
at com.foobar.service.greencheckout.silo.domain.statemachine.OrderFsm.invokeCreationalCommandMethod(OrderFsm.java:380)
at com.foobar.service.greencheckout.silo.domain.statemachine.OrderFsm.lambda$allowCreational$1(OrderFsm.java:345)
at akka.japi.pf.FSMStateFunctionBuilder$2.apply(FSMStateFunctionBuilder.java:80)
at akka.japi.pf.FSMStateFunctionBuilder$2.apply(FSMStateFunctionBuilder.java:77)
at akka.japi.pf.CaseStatement.apply(CaseStatements.scala:18)
at scala.PartialFunction$class.applyOrElse(PartialFunction.scala:123)
at akka.japi.pf.CaseStatement.applyOrElse(CaseStatements.scala:13)
at scala.PartialFunction$OrElse.apply(PartialFunction.scala:167)
at scala.PartialFunction$class.applyOrElse(PartialFunction.scala:123)
at akka.japi.pf.CaseStatement.applyOrElse(CaseStatements.scala:13)
at scala.PartialFunction$OrElse.apply(PartialFunction.scala:167)
at scala.PartialFunction$class.applyOrElse(PartialFunction.scala:123)
at akka.japi.pf.CaseStatement.applyOrElse(CaseStatements.scala:13)
at scala.PartialFunction$OrElse.apply(PartialFunction.scala:167)
at scala.PartialFunction$class.applyOrElse(PartialFunction.scala:123)
at akka.japi.pf.CaseStatement.applyOrElse(CaseStatements.scala:13)
at scala.PartialFunction$OrElse.apply(PartialFunction.scala:167)
at akka.actor.FSM$class.processEvent(FSM.scala:663)
at akka.actor.AbstractFSM.processEvent(AbstractFSM.scala:36)
at akka.actor.FSM$class.akka$actor$FSM$$processMsg(FSM.scala:657)
at akka.actor.FSM$$anonfun$receive$1.applyOrElse(FSM.scala:651)
at akka.actor.Actor$class.aroundReceive(Actor.scala:497)
at akka.actor.AbstractFSM.aroundReceive(AbstractFSM.scala:36)
at akka.actor.ActorCell.receiveMessage(ActorCell.scala:526)
at akka.actor.ActorCell.invoke(ActorCell.scala:495)
at akka.dispatch.Mailbox.processMailbox(Mailbox.scala:257)
at akka.dispatch.Mailbox.run(Mailbox.scala:224)
at akka.dispatch.Mailbox.exec(Mailbox.scala:234)
at scala.concurrent.forkjoin.ForkJoinTask.doExec(ForkJoinTask.java:260)
at scala.concurrent.forkjoin.ForkJoinPool$WorkQueue.runTask(ForkJoinPool.java:1339)
at scala.concurrent.forkjoin.ForkJoinPool.runWorker(ForkJoinPool.java:1979)
at scala.concurrent.forkjoin.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:107)
Caused by: org.springframework.integration.MessageDispatchingException: Dispatcher has no subscribers, failedMessage=GenericMessage [payload=com.foobar.service.greencheckout.message.GreenOrderPropertyAddedMessage#2ba0efb2, headers={id=f750a792-6b01-16d3-8206-6e553a03f8fa, type=com.foobar.service.greencheckout.message.GreenOrderPropertyAddedMessage, amqp_deliveryMode=PERSISTENT, timestamp=1505797680967}]
at org.springframework.integration.dispatcher.UnicastingDispatcher.doDispatch(UnicastingDispatcher.java:154)
at org.springframework.integration.dispatcher.UnicastingDispatcher.dispatch(UnicastingDispatcher.java:121)
at org.springframework.integration.channel.AbstractSubscribableChannel.doSend(AbstractSubscribableChannel.java:89)
... 58 more
The code to set this up looks like:
import org.springframework.amqp.core.AmqpAdmin;
import org.springframework.amqp.core.AmqpTemplate;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.Exchange;
import org.springframework.amqp.core.Queue;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;
import org.springframework.context.annotation.Scope;
import org.springframework.context.annotation.ScopedProxyMode;
import org.springframework.expression.ExpressionParser;
import org.springframework.integration.amqp.outbound.AmqpOutboundEndpoint;
import org.springframework.integration.amqp.support.DefaultAmqpHeaderMapper;
import org.springframework.integration.channel.DirectChannel;
import org.springframework.integration.channel.ExecutorChannel;
import org.springframework.integration.channel.NullChannel;
import org.springframework.integration.endpoint.EventDrivenConsumer;
import org.springframework.integration.gateway.GatewayProxyFactoryBean;
import org.springframework.messaging.SubscribableChannel;
import java.util.concurrent.Executors;
#Configuration
public class MessagingOutboundConfiguration
{
#Autowired
private AmqpTemplate amqpTemplate;
#Autowired
private Exchange publisherExchange;
#Autowired
private AmqpAdmin amqpAdmin;
#Autowired
private Exchange errorExchange;
#Autowired
private Queue errorQueue;
#Autowired
private BeanFactory beanFactory;
#Autowired
private ExpressionParser expressionParser;
#Autowired
private MessagingSettings messagingSettings;
#Bean
#DependsOn({"connectionFactory", "consumer"})
public AsynchronousBus asyncBus(SubscribableChannel amqpOutboundChannel) throws Exception
{
GatewayProxyFactoryBean factoryBean = new GatewayProxyFactoryBean(AsynchronousBus.class);
factoryBean.setBeanFactory(beanFactory);
factoryBean.setDefaultRequestChannel(amqpOutboundChannel);
factoryBean.afterPropertiesSet();
return (AsynchronousBus) factoryBean.getObject();
}
#Bean
#Scope(value = "request", proxyMode = ScopedProxyMode.TARGET_CLASS)
public TransactionAwareBus transactionAwareBus()
{
return new TransactionAwareBus();
}
/**
* Channel from message bus to the outbound channel adapter
*/
#Bean
public SubscribableChannel amqpOutboundChannel(HeaderChannelInterceptor headerChannelInterceptor)
{
DirectChannel channel = new DirectChannel();
channel.setComponentName("amqp-outbound-channel");
channel.addInterceptor(headerChannelInterceptor);
return channel;
}
/**
* Outbound Channel Adapter
*/
#Bean
public AmqpOutboundEndpoint endpoint(SubscribableChannel confirmationChannel)
{
DefaultAmqpHeaderMapper headerMapper = DefaultAmqpHeaderMapper.outboundMapper();
String[] allowedHeaders = new String[]{"*"};
headerMapper.setRequestHeaderNames(allowedHeaders);
headerMapper.setReplyHeaderNames(allowedHeaders);
AmqpOutboundEndpoint endpoint = new PhasedAmqpOutboundEndpoint(amqpTemplate);
endpoint.setHeaderMapper(headerMapper);
endpoint.setExchangeName(publisherExchange.getName());
endpoint.setRoutingKeyExpression(expressionParser.parseExpression("headers.type"));
endpoint.setConfirmCorrelationExpression(expressionParser.parseExpression("#this"));
if (messagingSettings.getPublisherConfirmations()) {
endpoint.setConfirmNackChannel(confirmationChannel);
endpoint.setConfirmAckChannel(new NullChannel());
}
return endpoint;
}
#Bean
public HeaderChannelInterceptor headerChannelInterceptor()
{
return new HeaderChannelInterceptor();
}
/**
* Shovels messages from channel to outbound channel adapter
*/
#Bean
public EventDrivenConsumer consumer(SubscribableChannel amqpOutboundChannel, AmqpOutboundEndpoint endpoint)
{
final EventDrivenConsumer consumer = new EventDrivenConsumer(amqpOutboundChannel, endpoint);
consumer.setBeanName("amqp-outbound-consumer");
return consumer;
}
/**
* Outbound Channel Adapter
*/
#Bean
public AmqpOutboundEndpoint errorEndpoint()
{
amqpAdmin.declareBinding(BindingBuilder.bind(errorQueue).to(errorExchange).with("#").noargs());
DefaultAmqpHeaderMapper headerMapper = DefaultAmqpHeaderMapper.outboundMapper();
String[] allowedHeaders = new String[1];
allowedHeaders[0] = "*";
headerMapper.setRequestHeaderNames(allowedHeaders);
AmqpOutboundEndpoint endpoint = new PhasedAmqpOutboundEndpoint(amqpTemplate);
endpoint.setHeaderMapper(headerMapper);
endpoint.setExchangeName(errorExchange.getName());
endpoint.setRoutingKeyExpression(expressionParser.parseExpression("headers.routing"));
return endpoint;
}
#Bean
public EventDrivenConsumer errorConsumer(SubscribableChannel errorChannel, AmqpOutboundEndpoint errorEndpoint)
{
final EventDrivenConsumer consumer = new EventDrivenConsumer(errorChannel, errorEndpoint);
consumer.setBeanName("amqp-error-consumer");
return consumer;
}
#Bean
public SubscribableChannel errorChannel()
{
DirectChannel channel = new DirectChannel();
channel.setComponentName("amqp-error-channel");
return channel;
}
#Bean
public SubscribableChannel confirmationChannel()
{
ExecutorChannel channel = new ExecutorChannel(Executors.newSingleThreadExecutor());
channel.setComponentName("amqp-confirmation-channel");
return channel;
}
}
From my understanding:
This message normally means (especially in the shutdown case) that beans are not correctly wired or that the context does not know the best order for stopping them.
What I do not understand: What's wrong with my setup? :(
UPDATE:
As you can see the code is called from inside an actor. The actor system is configured this way:
#Bean
#DependsOn({"asyncBus", "prototypedTransactionAwareBus", "transactionAwareBus", "syncBus"})
public SpringActorSystem actorSystem() throws Exception
{
String profile = environment.getActiveProfiles()[0];
ActorSystem system = ActorSystem.create(akkaSettings.getSystemName(), akkaConfiguration(profile));
if ("testing".equals(profile)) {
Cluster.get(system).joinSeedNodes(Lists.newArrayList(Cluster.get(system).selfAddress()));
}
if ("kubernetes".equals(profile)) {
joinKubernetesSeedNodes(system);
}
SpringExtension.SpringExtProvider.get(system).initialize(context);
return new SpringActorSystem(system);
}
And the SpringActorSystem:
public class SpringActorSystem implements ApplicationListener<ContextClosedEvent>
{
private static final Logger LOGGER = LoggerFactory.getLogger(SpringActorSystem.class);
private final ActorSystem actorSystem;
public SpringActorSystem(ActorSystem actorSystem)
{
this.actorSystem = actorSystem;
}
public ActorSystem system()
{
return actorSystem;
}
#Override
public void onApplicationEvent(ContextClosedEvent event)
{
final Cluster cluster = Cluster.get(this.actorSystem);
cluster.leave(cluster.selfAddress());
LOGGER.info("SpringActorSystem shutdown initiated");
this.actorSystem.terminate();
try {
Await.result(actorSystem.whenTerminated(), Duration.create(10, TimeUnit.SECONDS));
} catch (Exception e) {
LOGGER.info("Exception while waiting for termination", e);
}
LOGGER.info("SpringActorSystem shutdown finished");
}
}
Well, I think
SpringExtension.SpringExtProvider.get(system).initialize(context);
in the initializing phase is really too early.
Consider to implement SmartLifecycle for your SpringActorSystem to mover that initialize(context) to the start() method.