/* * Copyright 2002-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.springframework.amqp.rabbit.annotation; import static org.hamcrest.Matchers.allOf; import static org.hamcrest.Matchers.contains; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.hasSize; import static org.hamcrest.Matchers.instanceOf; import static org.hamcrest.Matchers.startsWith; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.Iterator; import java.util.List; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.stream.Collectors; import org.junit.Ignore; import org.junit.Test; import org.springframework.amqp.core.Binding; import org.springframework.amqp.core.CustomExchange; import org.springframework.amqp.core.Queue; import org.springframework.amqp.rabbit.config.MessageListenerTestContainer; import org.springframework.amqp.rabbit.config.RabbitListenerContainerTestFactory; import org.springframework.amqp.rabbit.listener.AbstractRabbitListenerEndpoint; import org.springframework.amqp.rabbit.listener.MethodRabbitListenerEndpoint; import org.springframework.amqp.rabbit.listener.RabbitListenerEndpoint; import org.springframework.amqp.rabbit.listener.RabbitListenerEndpointRegistry; import org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer; import org.springframework.beans.factory.BeanCreationException; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.PropertySource; import org.springframework.context.support.PropertySourcesPlaceholderConfigurer; import org.springframework.stereotype.Component; /** * @author Stephane Nicoll * @author Juergen Hoeller * @author Alex Panchenko */ public class RabbitListenerAnnotationBeanPostProcessorTests { @Test public void simpleMessageListener() { ConfigurableApplicationContext context = new AnnotationConfigApplicationContext( Config.class, SimpleMessageListenerTestBean.class); RabbitListenerContainerTestFactory factory = context.getBean(RabbitListenerContainerTestFactory.class); assertEquals("One container should have been registered", 1, factory.getListenerContainers().size()); MessageListenerTestContainer container = factory.getListenerContainers().get(0); RabbitListenerEndpoint endpoint = container.getEndpoint(); assertEquals("Wrong endpoint type", MethodRabbitListenerEndpoint.class, endpoint.getClass()); MethodRabbitListenerEndpoint methodEndpoint = (MethodRabbitListenerEndpoint) endpoint; assertNotNull(methodEndpoint.getBean()); assertNotNull(methodEndpoint.getMethod()); SimpleMessageListenerContainer listenerContainer = new SimpleMessageListenerContainer(); methodEndpoint.setupListenerContainer(listenerContainer); assertNotNull(listenerContainer.getMessageListener()); assertTrue("Should have been started " + container, container.isStarted()); context.close(); // Close and stop the listeners assertTrue("Should have been stopped " + container, container.isStopped()); } @Test public void simpleMessageListenerWithMixedAnnotations() { ConfigurableApplicationContext context = new AnnotationConfigApplicationContext( Config.class, SimpleMessageListenerWithMixedAnnotationsTestBean.class); RabbitListenerContainerTestFactory factory = context.getBean(RabbitListenerContainerTestFactory.class); assertEquals("One container should have been registered", 1, factory.getListenerContainers().size()); MessageListenerTestContainer container = factory.getListenerContainers().get(0); RabbitListenerEndpoint endpoint = container.getEndpoint(); assertEquals("Wrong endpoint type", MethodRabbitListenerEndpoint.class, endpoint.getClass()); MethodRabbitListenerEndpoint methodEndpoint = (MethodRabbitListenerEndpoint) endpoint; assertNotNull(methodEndpoint.getBean()); assertNotNull(methodEndpoint.getMethod()); Iterator<String> iterator = ((MethodRabbitListenerEndpoint) endpoint).getQueueNames().iterator(); assertEquals("testQueue", iterator.next()); assertEquals("secondQueue", iterator.next()); SimpleMessageListenerContainer listenerContainer = new SimpleMessageListenerContainer(); methodEndpoint.setupListenerContainer(listenerContainer); assertNotNull(listenerContainer.getMessageListener()); assertTrue("Should have been started " + container, container.isStarted()); context.close(); // Close and stop the listeners assertTrue("Should have been stopped " + container, container.isStopped()); } @Test public void metaAnnotationIsDiscovered() { ConfigurableApplicationContext context = new AnnotationConfigApplicationContext( Config.class, MetaAnnotationTestBean.class); RabbitListenerContainerTestFactory factory = context.getBean(RabbitListenerContainerTestFactory.class); assertEquals("one container should have been registered", 1, factory.getListenerContainers().size()); RabbitListenerEndpoint endpoint = factory.getListenerContainers().get(0).getEndpoint(); assertEquals("metaTestQueue", ((AbstractRabbitListenerEndpoint) endpoint).getQueueNames().iterator().next()); context.close(); } @Test public void multipleQueueNamesTestBean() { ConfigurableApplicationContext context = new AnnotationConfigApplicationContext( Config.class, MultipleQueueNamesTestBean.class); RabbitListenerContainerTestFactory factory = context.getBean(RabbitListenerContainerTestFactory.class); assertEquals("one container should have been registered", 1, factory.getListenerContainers().size()); RabbitListenerEndpoint endpoint = factory.getListenerContainers().get(0).getEndpoint(); final Iterator<String> iterator = ((AbstractRabbitListenerEndpoint) endpoint).getQueueNames().iterator(); assertEquals("metaTestQueue", iterator.next()); assertEquals("testQueue", iterator.next()); context.close(); } @Test public void multipleQueuesTestBean() { ConfigurableApplicationContext context = new AnnotationConfigApplicationContext( Config.class, MultipleQueuesTestBean.class); RabbitListenerContainerTestFactory factory = context.getBean(RabbitListenerContainerTestFactory.class); assertEquals("one container should have been registered", 1, factory.getListenerContainers().size()); RabbitListenerEndpoint endpoint = factory.getListenerContainers().get(0).getEndpoint(); final Iterator<String> iterator = ((AbstractRabbitListenerEndpoint) endpoint).getQueueNames().iterator(); assertEquals("testQueue", iterator.next()); assertEquals("secondQueue", iterator.next()); context.close(); } @Test public void mixedQueuesAndQueueNamesTestBean() { ConfigurableApplicationContext context = new AnnotationConfigApplicationContext( Config.class, MixedQueuesAndQueueNamesTestBean.class); RabbitListenerContainerTestFactory factory = context.getBean(RabbitListenerContainerTestFactory.class); assertEquals("one container should have been registered", 1, factory.getListenerContainers().size()); RabbitListenerEndpoint endpoint = factory.getListenerContainers().get(0).getEndpoint(); final Iterator<String> iterator = ((AbstractRabbitListenerEndpoint) endpoint).getQueueNames().iterator(); assertEquals("metaTestQueue", iterator.next()); assertEquals("testQueue", iterator.next()); assertEquals("secondQueue", iterator.next()); context.close(); } @Test public void propertyResolvingToExpressionTestBean() { ConfigurableApplicationContext context = new AnnotationConfigApplicationContext( Config.class, PropertyResolvingToExpressionTestBean.class); RabbitListenerContainerTestFactory factory = context.getBean(RabbitListenerContainerTestFactory.class); assertEquals("one container should have been registered", 1, factory.getListenerContainers().size()); RabbitListenerEndpoint endpoint = factory.getListenerContainers().get(0).getEndpoint(); final Iterator<String> iterator = ((AbstractRabbitListenerEndpoint) endpoint).getQueueNames().iterator(); assertEquals("testQueue", iterator.next()); assertEquals("secondQueue", iterator.next()); context.close(); } @Test public void invalidValueInAnnotationTestBean() { try { new AnnotationConfigApplicationContext(Config.class, InvalidValueInAnnotationTestBean.class).close(); } catch (BeanCreationException e) { assertThat(e.getCause(), instanceOf(IllegalArgumentException.class)); assertThat(e.getMessage(), allOf( containsString("@RabbitListener can't resolve"), containsString("as either a String or a Queue") )); } } @Test public void multipleRoutingKeysTestBean() { ConfigurableApplicationContext context = new AnnotationConfigApplicationContext(Config.class, MultipleRoutingKeysTestBean.class); RabbitListenerContainerTestFactory factory = context.getBean(RabbitListenerContainerTestFactory.class); assertThat("one container should have been registered", factory.getListenerContainers(), hasSize(1)); RabbitListenerEndpoint endpoint = factory.getListenerContainers().get(0).getEndpoint(); assertEquals(Collections.singletonList("my_queue"), ((AbstractRabbitListenerEndpoint) endpoint).getQueueNames()); final List<Queue> queues = new ArrayList<>(context.getBeansOfType(Queue.class).values()); queues.sort(Comparator.comparing(Queue::getName)); assertThat(queues.stream().map(Queue::getName).collect(Collectors.toList()), contains("my_queue", "secondQueue", "testQueue")); assertEquals(Collections.singletonMap("foo", "bar"), queues.get(0).getArguments()); assertThat(context.getBeansOfType(org.springframework.amqp.core.Exchange.class).values(), hasSize(1)); final List<Binding> bindings = new ArrayList<>(context.getBeansOfType(Binding.class).values()); assertThat(bindings, hasSize(2)); bindings.sort(Comparator.comparing(Binding::getRoutingKey)); assertEquals("Binding [destination=my_queue, exchange=my_exchange, routingKey=red]", bindings.get(0).toString()); assertEquals("Binding [destination=my_queue, exchange=my_exchange, routingKey=yellow]", bindings.get(1).toString()); context.close(); } @Test public void customExhangeTestBean() { ConfigurableApplicationContext context = new AnnotationConfigApplicationContext(Config.class, CustomExchangeTestBean.class); final Collection<CustomExchange> exchanges = context.getBeansOfType(CustomExchange.class).values(); assertThat(exchanges, hasSize(1)); final CustomExchange exchange = exchanges.iterator().next(); assertEquals("my_custom_exchange", exchange.getName()); assertEquals("custom_type", exchange.getType()); context.close(); } @Test public void queuesToDeclare() { ConfigurableApplicationContext context = new AnnotationConfigApplicationContext(Config.class, QueuesToDeclareTestBean.class); final List<Queue> queues = new ArrayList<>(context.getBeansOfType(Queue.class).values()); assertThat(queues, hasSize(4)); queues.sort(Comparator.comparing(Queue::getName)); final Queue queue0 = queues.get(0); assertEquals("my_declared_queue", queue0.getName()); assertTrue(queue0.isDurable()); assertFalse(queue0.isAutoDelete()); assertFalse(queue0.isExclusive()); final Queue queue2 = queues.get(2); assertThat(queue2.getName(), startsWith("spring.gen-")); assertFalse(queue2.isDurable()); assertTrue(queue2.isAutoDelete()); assertTrue(queue2.isExclusive()); context.close(); } @Test @Ignore("To slow and doesn't have 100% confirmation") public void concurrency() throws InterruptedException, ExecutionException { final int concurrencyLevel = 8; final ExecutorService executorService = Executors.newFixedThreadPool(concurrencyLevel); try { for (int i = 0; i < 1000; ++i) { final ConfigurableApplicationContext context = new AnnotationConfigApplicationContext(Config.class); try { final Callable<?> task = () -> context.getBeanFactory().createBean(BeanForConcurrencyTesting.class); final List<? extends Future<?>> futures = executorService .invokeAll(Collections.nCopies(concurrencyLevel, task)); for (Future<?> future : futures) { future.get(); } } finally { context.close(); } } } finally { executorService.shutdown(); } } static class BeanForConcurrencyTesting { public void a() { } public void b() { } public void c() { } } @Component static class SimpleMessageListenerTestBean { @RabbitListener(queues = "testQueue") public void handleIt(String body) { } } @Component static class SimpleMessageListenerWithMixedAnnotationsTestBean { @RabbitListener(queues = {"testQueue", "#{mySecondQueue}"}) public void handleIt(String body) { } } @Component static class MetaAnnotationTestBean { @FooListener public void handleIt(String body) { } } @RabbitListener(queues = "metaTestQueue") @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) static @interface FooListener { } @Component static class MultipleQueueNamesTestBean { @RabbitListener(queues = {"metaTestQueue", "#{@myTestQueue.name}"}) public void handleIt(String body) { } } @Component static class MultipleQueuesTestBean { @RabbitListener(queues = {"#{@myTestQueue}", "#{@mySecondQueue}"}) public void handleIt(String body) { } } @Component static class MixedQueuesAndQueueNamesTestBean { @RabbitListener(queues = {"metaTestQueue", "#{@myTestQueue}", "#{@mySecondQueue.name}"}) public void handleIt(String body) { } } @Component static class PropertyResolvingToExpressionTestBean { @RabbitListener(queues = {"${myQueueExpression}", "#{@mySecondQueue}"}) public void handleIt(String body) { } } @Component static class InvalidValueInAnnotationTestBean { @RabbitListener(queues = "#{@testFactory}") public void handleIt(String body) { } } @Component static class MultipleRoutingKeysTestBean { @RabbitListener(bindings = @QueueBinding(exchange = @Exchange("my_exchange"), value = @org.springframework.amqp.rabbit.annotation.Queue(value = "my_queue", arguments = @Argument(name = "foo", value = "bar")), key = {"${xxxxxxx:red}", "yellow"})) public void handleIt(String body) { } } @Component static class CustomExchangeTestBean { @RabbitListener(bindings = @QueueBinding(exchange = @Exchange(value = "my_custom_exchange", type = "custom_type"), value = @org.springframework.amqp.rabbit.annotation.Queue, key = "test")) public void handleIt(String body) { } } @Component static class QueuesToDeclareTestBean { @RabbitListener(queuesToDeclare = @org.springframework.amqp.rabbit.annotation.Queue) public void handleAnonymous(String body) { } @RabbitListener(queuesToDeclare = @org.springframework.amqp.rabbit.annotation.Queue("my_declared_queue")) public void handleName(String body) { } } @Configuration @PropertySource("classpath:/org/springframework/amqp/rabbit/annotation/queue-annotation.properties") static class Config { @Bean public RabbitListenerAnnotationBeanPostProcessor postProcessor() { RabbitListenerAnnotationBeanPostProcessor postProcessor = new RabbitListenerAnnotationBeanPostProcessor(); postProcessor.setEndpointRegistry(rabbitListenerEndpointRegistry()); postProcessor.setContainerFactoryBeanName("testFactory"); return postProcessor; } @Bean public RabbitListenerEndpointRegistry rabbitListenerEndpointRegistry() { return new RabbitListenerEndpointRegistry(); } @Bean public RabbitListenerContainerTestFactory testFactory() { return new RabbitListenerContainerTestFactory(); } @Bean public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() { return new PropertySourcesPlaceholderConfigurer(); } @Bean public Queue myTestQueue() { return new Queue("testQueue"); } @Bean public Queue mySecondQueue() { return new Queue("secondQueue"); } } }