Spring Cloud Stream: Global errorChannel does not work - spring-boot

According to this documentation, it should be possible to subscribe to global error channel provided by Spring Integration - "errorChannel".
In my very simple case it does not work:
Application:
#SpringBootApplication
#EnableBinding({MySink.class})
public class LoggingConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(LoggingConsumerApplication.class, args);
}
#StreamListener(target = MySink.INPUT_ONE)
public void handle(Person person) {
System.out.println("Received: " + person);
if(StringUtils.isEmpty(person.getName())){
throw new RuntimeException("Wrong person name!");
}
}
#ServiceActivator(inputChannel = "mySink.mySink-group.errors")
public void error(Message<?> message) {
System.out.println("Handling ERROR: " + message);
}
#ServiceActivator(inputChannel = "errorChannel")
public void errorGlobal(ErrorMessage message) {
System.out.println("Handling ERROR GLOBAL SA: " + message);
}
#StreamListener("errorChannel")
public void errorGlobalListener(ErrorMessage message) {
System.out.println("Handling ERROR GLOBAL Listener: " + message);
}
public static class Person {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String toString() {
return this.name;
}
}
}
MySink
public interface MySink {
/**
* Input channel name.
*/
String INPUT_ONE = "inputOne";
/**
* #return input channel.
*/
#Input(MySink.INPUT_ONE)
SubscribableChannel inputOne();
}
properties
spring.rabbitmq.host=192.168.0.100
spring.cloud.stream.bindings.inputOne.destination=mySink
spring.cloud.stream.bindings.inputOne.group=mySink-group
The destination-specific handler works (mySink.mySink-group.errors), but two other handlers never get called.
What is wrong here?
Tried with Spring Boot 2.1.6

There is nothing wrong and it is working as expected. From the doc: "The handle(..) method, which subscribes to the channel named input, throws an exception. Given there is also a subscriber to the error channel input.myGroup.errors all error messages are handled by this subscriber." So, what this means is that your error is handled by either binding specific error handler (mySink.mySink-group.errors) or global (errorChannel).
https://cloud.spring.io/spring-cloud-static/spring-cloud-stream/2.2.0.RELEASE/spring-cloud-stream.html#spring-cloud-stream-overview-error-handling

Related

MessageMapping in WebSockets Spring is not being called even though stomp sent is working

Message Mapping Controller
#Controller
public class MessageControl {
#MessageMapping("/message")
#SendTo("/topic/return")
public Message getContent(Message message) {
//to call this method we call /app/message
// try {
// //processing
//// Thread.sleep(2000);
//
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
System.out.println("Returning message to subscribing items");
return message;
}
}
Send function in JavaScript from client side
function sendMessage(){
let jsonOb={
name:localStorage.getItem("name"),
content:$("#message-value").val()
}
stompClient.send("/app/message",{},JSON.stringify(jsonOb));
}
Configuration Class
#Configuration
#EnableWebSocketMessageBroker
public class Config implements WebSocketMessageBrokerConfigurer {
#Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/server1").withSockJS();
//this is the link where the client connects to the backend server
}
#Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
registry.enableSimpleBroker("/topic","/queue");
registry.setApplicationDestinationPrefixes("/app");
//any request that is being sent to the broker needs to be through /app/topic
}
}
Message class
public class Message {
private String name;
private String content;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getContent() {
return content;
}
public Message(String name, String content) {
super();
this.name = name;
this.content = content;
}
public void setContent(String content) {
this.content = content;
}
}
The browser console shows that the message is being sent to the server, but the server side Method defined for the message mapping does not get called, the system.out.println("") print statement does not get printed. So it means the mapping is not working for some reason.

Stop RabbitMQ-Connection in Spring-Boot

I have a spring-boot application that pulls all the messages from a RabbitMQ-queue and then terminates. I use rabbitTemplate from the package spring-boot-starter-amqp (version 2.4.0), namely receiveAndConvert(). Somehow, I cannot get my application to start and stop again. When the rabbitConnectionFactory is created, it will never stop.
According to Google and other stackoverflow-questions, calling stop() or destroy() on the rabbitTemplate should do the job, but that doesn't work.
The rabbitTemplate is injected in the constructor.
Here is some code:
rabbitTemplate.setMessageConverter(new Jackson2JsonMessageConverter());
Object msg = getMessage();
while (msg != null) {
try {
String name = ((LinkedHashMap) msg).get(propertyName).toString();
//business logic
logger.debug("added_" + name);
} catch (Exception e) {
logger.error("" + e.getMessage());
}
msg = getMessage();
}
rabbitTemplate.stop();
private Object getMessage() {
try {
return rabbitTemplate.receiveAndConvert(queueName);
} catch (Exception e) {
logger.error("" + e.getMessage());
return null;
}
}
So, how do you terminate the connection to RabbitMQ properly?
Thanks for your inquiry.
You can call resetConnection() on the CachingConnectionFactory to close the connection.
Or close() the application context.
If I were to do it , I would use #RabbitListener to receive the messages and RabbitListenerEndpointRegistry to start and stop the listener. Sample Code is given below
#EnableScheduling
#SpringBootApplication
public class Application implements ApplicationRunner {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
public static final String queueName = "Hello";
#Bean
public Queue hello() {
return new Queue(queueName);
}
#Autowired
private RabbitTemplate template;
#Scheduled(fixedDelay = 1000, initialDelay = 500)
public void send() {
String message = "Hello World!";
this.template.convertAndSend(queueName, message);
System.out.println(" [x] Sent '" + message + "'");
}
#Autowired
RabbitListenerEndpointRegistry registry;
#Override
public void run(ApplicationArguments args) throws Exception {
registry.getListenerContainer( Application.queueName).start();
Thread.sleep(10000L);
registry.getListenerContainer( Application.queueName).stop();
}
}
#Component
class Receiver {
#RabbitListener(id= Application.queueName,queues = Application.queueName)
public void receive(String in) {
System.out.println(" [x] Received '" + in + "'");
}
}

Maybe not public or not valid? Using Spring's Websocket and Kafka

As I am trying to consume data from a topic (the topic name is based on user) and during runtime I am trying to consume message from the topic but I am getting the following error.
Caused by:
org.springframework.expression.spel.SpelEvaluationException: EL1008E:
Property or field 'consumerProperties' cannot be found on object of
type 'org.springframework.beans.factory.config.BeanExpressionContext'
- maybe not public or not valid?
Here is my code
#Service
public class kafkaConsumerService {
private SimpMessagingTemplate template;
KafkaConsumerProperties consumerProperties;
#Autowired
public kafkaConsumerService(KafkaConsumerProperties consumerProperties, SimpMessagingTemplate template) {
this.consumerProperties=consumerProperties;
this.template=template;
}
#KafkaListener(topics = {"#{consumerProperties.getTopic()}"})
// #KafkaListener(topics="Chandan3706")
public void consume(#Payload Message message) {
System.out.println("from kafka topic::" + message);
template.convertAndSend("/chat/getMessage", message);
}
}
My KafkaConsumerProperties.class
#Component
#ConfigurationProperties(prefix="kafka.consumer")
public class KafkaConsumerProperties {
private String bootStrap;
private String group;
private String topic;
public String getBootStrap() {
return bootStrap;
}
public void setBootStrap(String bootStrap) {
this.bootStrap = bootStrap;
}
public String getGroup() {
return group;
}
public void setGroup(String group) {
this.group = group;
}
public String getTopic() {
return topic;
}
public void setTopic(String topic) {
this.topic = topic;
}
#Override
public String toString() {
return "KafkaConsumerProperties [bootStrap=" + bootStrap + ", group=" + group + ", topic=" + topic + "]";
}
}
Thanks in advance
Since you don’t provide any bean name for your KafkaConsumerProperties component, the default one is de-capitalized class name. That’s one.
The expression you use in the #KafkaListener is regular bean definition phase expression, therefore a root object is some BeanExpressionContext , but not your listener bean as you try to get access through the property.
Not sure if you need that KafkaConsumerProperties property in this listener, but expression must ask for the kafkaConsumerProperties bean:
#Service
public class kafkaConsumerService {
private SimpMessagingTemplate template;
#Autowired
public kafkaConsumerService(SimpMessagingTemplate template) {
this.template=template;
}
#KafkaListener(topics = {"#{kafkaConsumerProperties.topic}"})
// #KafkaListener(topics="Chandan3706")
public void consume(#Payload Message message) {
System.out.println("from kafka topic::" + message);
template.convertAndSend("/chat/getMessage", message);
}
}
The following code worked for me, notice to #DependsOn("KafkaConsumerProperties") and #Component("KafkaConsumerProperties") annotations.
KafkaConsumerService class:
#Service
#DependsOn("KafkaConsumerProperties")
public class KafkaConsumerService {
#KafkaListener(topics = "#{#KafkaConsumerProperties.getTopic()}")
public void consume(#Payload Message message) {
System.out.println("from kafka topic::" + message);
template.convertAndSend("/chat/getMessage", message);
}
}
KafkaConsumerProperties class:
#Component("KafkaConsumerProperties")
#ConfigurationProperties(prefix="kafka.consumer")
public class KafkaConsumerProperties {
private String topic;
public String getTopic() {
return topic;
}
}

RabbitMQ separate listeners by type

I have POJO which represents a message to Rabbit MQ. There is an integer which is responsible for the type of the message(whether it's update, remove, add and so on):
public class Message {
private String field1;
private String field2;
private Integer type;
...
<some other fields>
}
I have a consumer which accepts such messages in my spring boot app. So in order to handle each type separately, I have to add some switch/case construction in my code.
Are there any more clear solutions for such case?
You can use Spring Integration with a router...
Rabbit Inbound channel adapter -> router ->
Where the router routes to a different service activator (method) based on the type.
EDIT
Here's an example:
#SpringBootApplication
public class So47272336Application {
public static void main(String[] args) {
SpringApplication.run(So47272336Application.class, args);
}
#Bean
public ApplicationRunner runner(RabbitTemplate rabbitTemplate) {
return args -> {
rabbitTemplate.convertAndSend("my.queue", new Domain(1, "foo"));
rabbitTemplate.convertAndSend("my.queue", new Domain(2, "bar"));
rabbitTemplate.convertAndSend("my.queue", new Domain(3, "baz"));
};
}
#Bean
public Queue queue() {
return new Queue("my.queue");
}
#Bean
public IntegrationFlow flow(ConnectionFactory connectionFactory) {
return IntegrationFlows.from(Amqp.inboundAdapter(connectionFactory, "my.queue"))
.route("payload.type", r -> r
.subFlowMapping("1", f -> f.handle("bean", "add"))
.subFlowMapping("2", f -> f.handle("bean", "remove"))
.subFlowMapping("3", f -> f.handle("bean", "update")))
.get();
}
#Bean
public MyBean bean() {
return new MyBean();
}
public static class MyBean {
public void add(Domain object) {
System.out.println("Adding " + object);
}
public void remove(Domain object) {
System.out.println("Removing " + object);
}
public void update(Domain object) {
System.out.println("Updating " + object);
}
}
public static class Domain implements Serializable {
private final Integer type;
private final String info;
public Domain(Integer type, String info) {
this.type = type;
this.info = info;
}
public Integer getType() {
return this.type;
}
public String getInfo() {
return this.info;
}
#Override
public String toString() {
return "Domain [type=" + this.type + ", info=" + this.info + "]";
}
}
}

Jackson configuration to consume list of records in rabbitmq

I am using spring boot amqp in which I will be consuming a list of Employee objects from a queue. My listener method looks like this:
#RabbitListener(queues = "emp_queue")
public void processAndPortEmployeeData(List<Employee> empList) {
empList.forEach(emp -> { some logic })
}
However, when I try to consume the message, I get a class cast exception: For some reason, I'm getting a LinkedHashMap.
Caused by: java.lang.ClassCastException: java.util.LinkedHashMap cannot be cast to com.integration.domain.Employee
If I change my listener method to consume a single employee object, it works fine and I'm using the following jackson configurations for it:
#Configuration
#EnableRabbit
public class RabbitConfiguration implements RabbitListenerConfigurer {
#Bean
public MappingJackson2MessageConverter jackson2Converter() {
return new MappingJackson2MessageConverter();
}
#Bean
public DefaultMessageHandlerMethodFactory handlerMethodFactory() {
DefaultMessageHandlerMethodFactory factory = new DefaultMessageHandlerMethodFactory();
factory.setMessageConverter(jackson2Converter());
return factory;
}
#Override
public void configureRabbitListeners(RabbitListenerEndpointRegistrar registrar) {
registrar.setMessageHandlerMethodFactory(handlerMethodFactory());
}
}
Is there some other jackson configuration that I need to do to consume the list of employee objects?
Thanks a lot!
Sample Input Json message which I will be consuming:
[
{
"name" : "Jasmine",
"age" : "24",
"emp_id" : 1344
},
{
"name" : "Mark",
"age" : "32",
"emp_id" : 1314
}
]
What version of Spring AMQP are you using?
If 1.6 or greater, the framework passes the argument type to the message converter.
Before 1.6 you either need type information in the message headers, or you need to configure the converter with type information.
That said, since the converter created a map, it implies that was what received (not a list).
Please show a sample of the JSON in a message.
EDIT
Note that boot auto-configures the message converter if there's a single bean of that type...
#SpringBootApplication
public class So40491628Application {
public static void main(String[] args) throws Exception {
ConfigurableApplicationContext context = SpringApplication.run(So40491628Application.class, args);
Resource resource = new ClassPathResource("data.json");
ByteArrayOutputStream baos = new ByteArrayOutputStream();
FileCopyUtils.copy(resource.getInputStream(), baos);
context.getBean(RabbitTemplate.class).send("foo", MessageBuilder.withBody(baos.toByteArray())
.andProperties(MessagePropertiesBuilder.newInstance().setContentType("application/json").build()).build());
Thread.sleep(10000);
context.close();
}
#Bean
public Jackson2JsonMessageConverter converter() {
return new Jackson2JsonMessageConverter();
}
#Bean
public Queue foo() {
return new Queue("foo");
}
#RabbitListener(queues = "foo")
public void listen(List<Employee> emps) {
System.out.println(emps);
}
public static class Employee {
private String name;
private String age;
private int empId;
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
public String getAge() {
return this.age;
}
public void setAge(String age) {
this.age = age;
}
public int getEmpId() {
return this.empId;
}
public void setEmpId(int empId) {
this.empId = empId;
}
#Override
public String toString() {
return "Employee [name=" + this.name + ", age=" + this.age + ", empId=" + this.empId + "]";
}
}
}
Result:
[Employee [name=Jasmine, age=24, empId=0], Employee [name=Mark, age=32, empId=0]]

Resources