What is idea of bindings in spring boot rabbitmq? - spring-boot

I need to bind several exchanges with several routing keys to one single queue and be able to send messages by exchange and routing key and receive it by listening to queue by queue-name.
my code:
#Configuration
#RequiredArgsConstructor
#EnableConfigurationProperties(ExchangeConfig.class)
public class RabbitConfig {
private final ExchangeConfig exchangeConfig;
#Bean
public List<Binding> bindings() {
List<Binding> bindings = new ArrayList<>();
exchangeConfig.getExchangesWithKeys()
.forEach(exchangeWithKeys -> exchangeWithKeys.getRoutingKeys()
.forEach(key -> {
Exchange exchange = ExchangeBuilder.directExchange(exchangeWithKeys.getExchange()).build();
Queue queue = QueueBuilder.durable(exchangeConfig.getLogsQueue()).build();
Binding binding = BindingBuilder.bind(queue).to(exchange)
.with(key).noargs();
bindings.add(binding);
}));
return bindings;
}
}
config:
spring:
rabbitmq:
host: localhost
port: 5672
rabbitmq:
exchanges-with-keys:
- exchange: exchange1
routing-keys: exchange1.live, exchange1.after
- exchange: exchange2
routing-keys: exchange2.live, exchange2.after
- exchange: exchange3
routing-keys: exchange3.live, exchange3.after
logs-queue: log-messages_q
props:
#Data
#Component
#ConfigurationProperties(prefix = "rabbitmq")
public class ExchangeConfig {
private String logsQueue;
private List<ExchangeWithKeys> exchangesWithKeys;
#Data
public static class ExchangeWithKeys {
private String exchange;
private List<String> routingKeys;
}
}
listener:
#Component
#Slf4j
#RequiredArgsConstructor
public class LogsListener {
private final LogMessageEventProcessor logMessageEventProcessor;
#RabbitListener(queues = "${rabbitmq.logs-queue}")
public void onLiveEvent(LogMessageEvent event) {
log.info("Received log event message [{}]", event.getBody());
logMessageEventProcessor.processLogMessageEvent(event);
}
}
test:
#SpringBootTest
#ContextConfiguration(initializers = LogsListenerTest.Initializer.class)
class LogsListenerTest {
#Autowired
private RabbitTemplate template;
#ClassRule
private static final RabbitMQContainer container = new RabbitMQContainer("rabbitmq:3.7.25-management-alpine")
.withExposedPorts(5672, 15672).withQueue("log-messages_q");
#BeforeAll
private static void startRabbit() {container.start();}
#AfterAll
private static void stopRabbit() {
container.stop();
}
#Test
public void test() {
template.convertAndSend("exchange1", "exchange1.live", new LogMessageEvent());
template.receiveAndConvert("log-messages_q");
}
public static class Initializer implements
ApplicationContextInitializer<ConfigurableApplicationContext> {
#Override
public void initialize(#NotNull ConfigurableApplicationContext configurableApplicationContext) {
val values = TestPropertyValues.of(
"spring.rabbitmq.host=" + container.getContainerIpAddress(),
"spring.rabbitmq.port=" + container.getMappedPort(5672)
);
values.applyTo(configurableApplicationContext);
}
}
}
Everything above does not working.
So where should i put these bindings to make it work? Thanks.

What version are you using? The use of List<Binding> has been replaced by Declarables.
See https://docs.spring.io/spring-amqp/docs/current/reference/html/#collection-declaration
The documentation is a bit out of date, the admin declareCollections property was removed in 2.2.

Related

#Transactional issue in Spring Boot with Kafka and Mongo Integration

I've the following kafka consumer
#KafkaListener(topics = "#{'${bpi.kafka.topic.topicname}'}",
groupId = "#{'${bpi.kafka.group-id}'}",
properties = {"auto.offset.reset:${bpi.kafka.consumer.auto-offset-reset}"})
public void consumeOverdueEvents(Event event) {
myinterface.handleEvent(Event);
}
My Service looks like the following
#Override
#Transactional(value = "mongoTransactionManager")
public void handleEvent(Event event) {
eventProducer.publishEvent(event.consolidateNewEvent(event));
eventDataGateway.saveEvent(event);
}
}
/*#Component
#RequiredArgsConstructor
public class KafkaEventProducer implements .. {
private final KafkaTemplate<String, Event> kafkaTemplate;
#Value("${bpi.kafka.topic.second_topic_name}")
private String topic;
#Override
public void publishEvent(Event2 event) {
kafkaTemplate.send(topic, "", Event2.create(event));
}
}*/
/*#Component
#RequiredArgsConstructor
public class eventAdapter implements EventDataGateway {
private final MyRepository repository;
#Override
public void saveEvent(Event event) {
repository.save(..);
}
}*/
In order to test the #Transactional, I purposely dropped the mongo db, When I receive one new event it will not be saved but I got 10 published events
PS: The retry is due to the transactional behavior, but the intended behavior is to not publish anything if the database operation fails

Springboot websocket give 404 error in postman

I am writing a chatroom service by springboot websocket.And i want to build multiple chatrooms for the clients base on the url.But it fail when testing postman with 404 not found
My controller :
#Controller
public class ChatroomController {
private final ChatroomService chatroomService;
private final SimpMessageSendingOperations messagingTemplate;
public ChatroomController(ChatroomService chatroomService, SimpMessageSendingOperations messagingTemplate) {
this.chatroomService = chatroomService;
this.messagingTemplate = messagingTemplate;
}
//send chat
#MessageMapping("/chat/{roomId}/sendMessage")
public ChatMessage sendMessage(#DestinationVariable String roomId, #Payload ChatMessage chatMessage) {
return chatroomService.sendMessage(roomId,chatMessage);
}
My service:
#Service
#Slf4j
public class ChatroomService {
private final ChatroomRepository chatroomRepository;
private final SimpMessageSendingOperations messagingTemplate;
public ChatroomService(ChatroomRepository chatroomRepository, SimpMessageSendingOperations messagingTemplate) {
this.chatroomRepository = chatroomRepository;
this.messagingTemplate = messagingTemplate;
}
public ChatMessage sendMessage(String roomId, ChatMessage chatMessage) {
//check chatroom is existed
chatMessage.setDateTime(Instant.now());
chatMessage.setOrder_id(roomId);
messagingTemplate.convertAndSend(format("/channel/%s", roomId), chatMessage);
ChatMessage savedchat=chatroomRepository.save(chatMessage);
return savedchat;
}
My config:
#Configuration
#EnableWebSocketMessageBroker
public class WebsocketConfig implements WebSocketMessageBrokerConfigurer {
#Override //register the endpoint
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/ws").withSockJS();
registry.addEndpoint("/ws");
//sockJs is for setting the STOMP =>send message to who(subscribe)
}
#Override //control with "/app" can access
public void configureMessageBroker(MessageBrokerRegistry registry) {
registry.setApplicationDestinationPrefixes("/Chatroom");
// '/topic' is access the broker
registry.enableSimpleBroker("/channel");
}
When i test with: ws://localhost:8084/ws/chat/12/sendMessage, it give the 404 error, but when i test with ws://localhost:8084/ws, it connected.Is there any problem on my url?
Error :
Invalid SockJS path '/chat/12' - required to have 3 path segments"
try configuring your application to run on a different port by adding this to your application.properties
server.port = 8081

#RabbitListener for multiple object types

In SpringBoot I use RabbitTemplate and #RabbitListener to produce and receive Messages.
I have an abstract class to send and receive RabbitMQ message of a specific type and send those messages to a STOMP web socket.
I use one RabbitMQ topic exchange which is bind to one RabbitMQ queue. In the topic-exchange I send messages (Java objects) of 2 different types and I am using 2 #RabbitListener to consume these messages.
#Configuration
public class WsRabbitMqConfiguration {
public static final String WS_EXCHANGE_NAME = "websocket.replication";
#Getter
#Value(value = "${spring.application.name}")
private String routingKey;
#Bean
TopicExchange wsReplicationExchange() {
return new TopicExchange(
WS_EXCHANGE_NAME,
true,
false,
null);
}
#Bean
public Queue wsReplicationQueue() {
return new AnonymousQueue();
}
#Bean
public Binding bindingWsAttributeValueQueue(TopicExchange wsReplicationExchange,
Queue wsReplicationQueue) {
return BindingBuilder
.bind(wsReplicationQueue)
.to(wsReplicationExchange)
.with(routingKey);
}
}
#RequiredArgsConstructor
public abstract class AbstractWebsocketService<T> implements WebSocketService {
public static final String HEADER_WS_DESTINATION = "x-Websocket-Destination";
private final RabbitTemplate rabbitTemplate;
private final WsRabbitMqConfiguration wsRabbitMqConfiguration;
#Override
public final <T> void send(String destination, T payload) {
rabbitTemplate.convertAndSend(WsRabbitMqConfiguration.WS_EXCHANGE_NAME, wsRabbitMqConfiguration.getRoutingKey(), payload,
message -> putDestination(message, destination));
}
protected abstract void handleWebsocketSending(T payload, String destination);
#Service
public class FooWebSocketService extends AbstractWebsocketService<Foo> {
public FooWebSocketService(RabbitTemplate rabbitTemplate,
WsRabbitMqConfiguration rabbitMqConfiguration) {
super(rabbitTemplate, rabbitMqConfiguration);
}
#RabbitListener(queues = "#{wsReplicationQueue.name}", ackMode = "NONE")
protected void handleWebsocketSending(#Payload Foo payload, #Header(HEADER_WS_DESTINATION) String destination) {
// logic here
}
}
#Service
public class BarWebSocketService extends AbstractWebsocketService<Bar> {
public BarWebSocketService(RabbitTemplate rabbitTemplate,
WsRabbitMqConfiguration rabbitMqConfiguration) {
super(rabbitTemplate, rabbitMqConfiguration);
}
#RabbitListener(queues = "#{wsReplicationQueue.name}", ackMode = "NONE")
protected void handleWebsocketSending(#Payload Bar payload, #Header(HEADER_WS_DESTINATION) String destination) {
// logic here
}
}
From another service class I want to send RMQ messages, but randomly wrong #RabbitListener is activated.
Eg.
#Service
#RequiredArgsConstructor
public class TestService {
private final BarWebSocketService barWebSocketService;
public void sendMessage(Bar bar) {
// 1st message
barWebSocketService.send("bar-destination", bar);
// 2nd message
barWebSocketService.send("bar-destination2", bar);
}
}
For the 1st message #RabbitListener in FooWebSocketService is activated (which is wrong) and for the 2nd message #RabbitListener in BarWebSocketService (which is right).
Any suggestions what I am doing wrong? Thank you!

No bean found for definition [SpyDefinition... when using #SpyBean

I have an application that listens for Kafka messages using #KafkaListener inside of a #Component. Now I'd like to make an integration test with a Kafka test container (which spins up Kafka in the background). In my test I want to verify that the listener method was called and finished, however when I use #SpyBean in my test I get:
No bean found for definition [SpyDefinition#7a939c9e name = '', typeToSpy = com.demo.kafka.MessageListener, reset = AFTER]
I'm using Kotling, important classes:
Class to test
#Component
class MessageListener(private val someRepository: SomeRepository){
#KafkaListener
fun listen(records: List<ConsumerRecord<String, String>>) {
// do something with someRepository
}
}
Base test class
#ExtendWith(SpringExtension::class)
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.MOCK)
#TestInstance(TestInstance.Lifecycle.PER_CLASS)
class KafkaContainerTests {
// some functionality to spin up kafka testcontainer
}
Test class
class MessageListenerTest #Autowired constructor(
private val someRepository: SomeRepository
) : KafkaContainerTests() {
#SpyBean
private lateinit var messageListenerSpy: MessageListener
private var messageListenerLatch = CountDownLatch(1)
#BeforeAll
fun setupLatch() {
logger.debug("setting up latch")
doAnswer {
it.callRealMethod()
messageListenerLatch.count
}.whenever(messageListenerSpy).listen(any())
}
#Test
fun testListener(){
sendKafkaMessage(someValidKafkaMessage)
// assert that the listen method is being called & finished
assertTrue(messageListenerLatch.await(20, TimeUnit.SECONDS))
// and assert someRepository is called
}
}
The reason I am confused is that when I add the MessageListener to the #Autowired constructor of the MessageListenerTest it does get injected successfully.
Why is the test unable to find the bean when using #SpyBean?
It works fine for me with Java:
#SpringBootTest
class So58184716ApplicationTests {
#SpyBean
private Listener listener;
#Test
void test(#Autowired KafkaTemplate<String, String> template) throws InterruptedException {
template.send("so58184716", "foo");
CountDownLatch latch = new CountDownLatch(1);
willAnswer(inv -> {
inv.callRealMethod();
latch.countDown();
return null;
}).given(this.listener).listen("foo");
assertThat(latch.await(10, TimeUnit.SECONDS)).isTrue();
verify(this.listener).listen("foo");
}
}
#SpringBootApplication
public class So58184716Application {
public static void main(String[] args) {
SpringApplication.run(So58184716Application.class, args);
}
#Bean
public NewTopic topic() {
return TopicBuilder.name("so58184716").partitions(1).replicas(1).build();
}
}
#Component
class Listener {
#KafkaListener(id = "so58184716", topics = "so58184716")
public void listen(String in) {
System.out.println(in);
}
}

Field created in spring component in not initialized with new keyword

I have spring component class annotated with #Component and in it I have field ConcurrentHashMap map, which is init in constructor of component and used in spring stream listener:
#Component
public class FooService {
private ConcurrentHashMap<Long, String> fooMap;
public FooService () {
fooMap = new ConcurrentHashMap<>();
}
#StreamListener(value = Sink.INPUT)
private void handler(Foo foo) {
fooMap.put(foo.id, foo.body);
}
}
Listener handle messages sent by rest controller. Can you tell me why I always got there fooMap.put(...) NullPointerException because fooMap is null and not initialzied.
EDIT:
After #OlegZhurakousky answer I find out problem is with async method. When I add #Async on some method and add #EnableAsync I can't anymore use private modificator for my #StreamListener method. Do you have idea why and how to fix it?
https://github.com/schwantner92/spring-cloud-stream-issue
Thanks.
Could you try using #PostConstruct instead of constructor?
#PostConstruct
public void init(){
this.fooMap = new ConcurrentHashMap<>();
}
#Denis Stephanov
When I say bare minimum, here is what I mean. So try this as a start, you'll see that the map is not null and start evolving your app from there.
#SpringBootApplication
#EnableBinding(Processor.class)
public class DemoApplication {
private final Map<String, String> map;
public static void main(String[] args) {
SpringApplication.run(DemoRabbit174Application.class, args);
}
public DemoApplication() {
this.map = new HashMap<>();
}
#StreamListener(Processor.INPUT)
public void sink(String string) {
System.out.println(string);
}
}
With Spring everything has to be injected.
You need to declare a #Bean for the ConcurrentHashMap, that will be injected in you Component. So create a Configuration class like:
#Configuration
public class FooMapConfiguration {
#Bean("myFooMap")
public ConcurrentHashMap<Long, String> myFooMap() {
return new ConcurrentHashMap<>();
}
}
Then modify your Component:
#Component
public class FooService {
#Autowired
#Qualifier("myFooMap")
private ConcurrentHashMap<Long, String> fooMap;
public FooService () {
}
#StreamListener(value = Sink.INPUT)
private void handler(Foo foo) {
fooMap.put(foo.id, foo.body); // <= No more NPE here
}
}

Resources