Add support for Spring Rabbit (via Spring AMQP) to Boot
- If RabbitTemplate is on the classpath, turn on autodetection. - Create a RabbitTemplate, a Rabbit ConnectionFactory, and a RabbitAdmin is spring.rabbitmq.dynamic:true - Enable some **spring.rabbitmq** properties like host, port, username, password, and dynamic - Add tests to verify functionality - Add Groovy CLI functionality. Base it on @EnableRabbitMessaging. Add spring-amqp to the path. - Create rabbit.groovy test to prove it all works. - Make Queue and TopicExchange top-level Spring beans in rabbit.groovy test script
This commit is contained in:
committed by
Dave Syer
parent
fa6e6fde6c
commit
941d163709
@@ -116,6 +116,11 @@
|
||||
<artifactId>spring-security-acl</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.amqp</groupId>
|
||||
<artifactId>spring-rabbit</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.thymeleaf</groupId>
|
||||
<artifactId>thymeleaf</artifactId>
|
||||
|
||||
@@ -0,0 +1,140 @@
|
||||
/*
|
||||
* Copyright 2012-2013 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.boot.autoconfigure.amqp;
|
||||
|
||||
import org.springframework.amqp.core.AmqpAdmin;
|
||||
import org.springframework.amqp.rabbit.connection.CachingConnectionFactory;
|
||||
import org.springframework.amqp.rabbit.core.RabbitAdmin;
|
||||
import org.springframework.amqp.rabbit.core.RabbitTemplate;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
/**
|
||||
* {@link EnableAutoConfiguration Auto-configuration} for {@link RabbitTemplate}.
|
||||
*
|
||||
* @author Greg Turnquist
|
||||
*/
|
||||
@Configuration
|
||||
@ConditionalOnClass({ RabbitTemplate.class })
|
||||
@EnableConfigurationProperties
|
||||
public class RabbitTemplateAutoConfiguration {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnExpression("${spring.rabbitmq.dynamic:true}")
|
||||
@ConditionalOnMissingBean(AmqpAdmin.class)
|
||||
public AmqpAdmin amqpAdmin(CachingConnectionFactory connectionFactory) {
|
||||
return new RabbitAdmin(connectionFactory);
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@ConditionalOnMissingBean(RabbitTemplate.class)
|
||||
protected static class RabbitTemplateCreator {
|
||||
|
||||
@Autowired
|
||||
CachingConnectionFactory connectionFactory;
|
||||
|
||||
@Bean
|
||||
public RabbitTemplate rabbitTemplate() {
|
||||
return new RabbitTemplate(this.connectionFactory);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@ConditionalOnMissingBean(CachingConnectionFactory.class)
|
||||
@EnableConfigurationProperties(RabbitConnectionFactoryProperties.class)
|
||||
protected static class RabbitConnectionFactoryCreator {
|
||||
|
||||
@Autowired
|
||||
private RabbitConnectionFactoryProperties config;
|
||||
|
||||
@Bean
|
||||
public CachingConnectionFactory connectionFactory() {
|
||||
CachingConnectionFactory connectionFactory = new CachingConnectionFactory(this.config.getHost());
|
||||
connectionFactory.setPort(this.config.getPort());
|
||||
if (this.config.getUsername() != null) {
|
||||
connectionFactory.setUsername(this.config.getUsername());
|
||||
}
|
||||
if (this.config.getPassword() != null) {
|
||||
connectionFactory.setPassword(this.config.getPassword());
|
||||
}
|
||||
return connectionFactory;
|
||||
}
|
||||
}
|
||||
|
||||
@ConfigurationProperties(name = "spring.rabbitmq")
|
||||
public static class RabbitConnectionFactoryProperties {
|
||||
|
||||
private String host = "localhost";
|
||||
|
||||
private int port = 5672;
|
||||
|
||||
private String username;
|
||||
|
||||
private String password;
|
||||
|
||||
private boolean dynamic = true;
|
||||
|
||||
public String getHost() {
|
||||
return host;
|
||||
}
|
||||
|
||||
public void setHost(String host) {
|
||||
this.host = host;
|
||||
}
|
||||
|
||||
public int getPort() {
|
||||
return port;
|
||||
}
|
||||
|
||||
public void setPort(int port) {
|
||||
this.port = port;
|
||||
}
|
||||
|
||||
public String getUsername() {
|
||||
return username;
|
||||
}
|
||||
|
||||
public void setUsername(String username) {
|
||||
this.username = username;
|
||||
}
|
||||
|
||||
public String getPassword() {
|
||||
return password;
|
||||
}
|
||||
|
||||
public void setPassword(String password) {
|
||||
this.password = password;
|
||||
}
|
||||
|
||||
public boolean isDynamic() {
|
||||
return dynamic;
|
||||
}
|
||||
|
||||
public void setDynamic(boolean dynamic) {
|
||||
this.dynamic = dynamic;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
# Auto Configure
|
||||
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
|
||||
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
|
||||
org.springframework.boot.autoconfigure.amqp.RabbitTemplateAutoConfiguration,\
|
||||
org.springframework.boot.autoconfigure.MessageSourceAutoConfiguration,\
|
||||
org.springframework.boot.autoconfigure.PropertyPlaceholderAutoConfiguration,\
|
||||
org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration,\
|
||||
|
||||
@@ -0,0 +1,106 @@
|
||||
/*
|
||||
* Copyright 2012-2013 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.boot.autoconfigure.amqp;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.springframework.amqp.core.AmqpAdmin;
|
||||
import org.springframework.amqp.rabbit.connection.CachingConnectionFactory;
|
||||
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
|
||||
import org.springframework.amqp.rabbit.core.RabbitAdmin;
|
||||
import org.springframework.amqp.rabbit.core.RabbitTemplate;
|
||||
import org.springframework.boot.TestUtils;
|
||||
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
import static junit.framework.Assert.assertEquals;
|
||||
import static junit.framework.Assert.assertNotNull;
|
||||
import static junit.framework.Assert.fail;
|
||||
|
||||
/**
|
||||
* Tests for {@link RabbitTemplateAutoConfiguration}.
|
||||
*
|
||||
* @author Greg Turnquist
|
||||
*/
|
||||
public class RabbitTemplateAutoconfigurationTests {
|
||||
|
||||
private AnnotationConfigApplicationContext context;
|
||||
|
||||
@Test
|
||||
public void testDefaultRabbitTemplate() {
|
||||
this.context = new AnnotationConfigApplicationContext();
|
||||
this.context.register(TestConfiguration.class, RabbitTemplateAutoConfiguration.class);
|
||||
this.context.refresh();
|
||||
RabbitTemplate rabbitTemplate = this.context.getBean(RabbitTemplate.class);
|
||||
CachingConnectionFactory connectionFactory = this.context.getBean(CachingConnectionFactory.class);
|
||||
RabbitAdmin amqpAdmin = this.context.getBean(RabbitAdmin.class);
|
||||
assertNotNull(rabbitTemplate);
|
||||
assertNotNull(connectionFactory);
|
||||
assertNotNull(amqpAdmin);
|
||||
assertEquals(rabbitTemplate.getConnectionFactory(), connectionFactory);
|
||||
assertEquals(connectionFactory.getHost(), "localhost");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRabbitTemplateWithOverrides() {
|
||||
this.context = new AnnotationConfigApplicationContext();
|
||||
this.context.register(TestConfiguration.class, RabbitTemplateAutoConfiguration.class);
|
||||
TestUtils.addEnviroment(this.context, "spring.rabbitmq.host:remote-server",
|
||||
"spring.rabbitmq.port:9000", "spring.rabbitmq.username:alice", "spring.rabbitmq.password:secret");
|
||||
this.context.refresh();
|
||||
CachingConnectionFactory connectionFactory = this.context.getBean(CachingConnectionFactory.class);
|
||||
assertEquals(connectionFactory.getHost(), "remote-server");
|
||||
assertEquals(connectionFactory.getPort(), 9000);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testConnectionFactoryBackoff() {
|
||||
this.context = new AnnotationConfigApplicationContext();
|
||||
this.context.register(TestConfiguration2.class, RabbitTemplateAutoConfiguration.class);
|
||||
this.context.refresh();
|
||||
RabbitTemplate rabbitTemplate = this.context.getBean(RabbitTemplate.class);
|
||||
CachingConnectionFactory connectionFactory = this.context.getBean(CachingConnectionFactory.class);
|
||||
assertEquals(rabbitTemplate.getConnectionFactory(), connectionFactory);
|
||||
assertEquals(connectionFactory.getHost(), "otherserver");
|
||||
assertEquals(connectionFactory.getPort(), 8001);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStaticQueues() {
|
||||
this.context = new AnnotationConfigApplicationContext();
|
||||
this.context.register(TestConfiguration.class, RabbitTemplateAutoConfiguration.class);
|
||||
TestUtils.addEnviroment(this.context, "spring.rabbitmq.dynamic:false");
|
||||
this.context.refresh();
|
||||
try {
|
||||
this.context.getBean(AmqpAdmin.class);
|
||||
fail("There should NOT be an AmqpAdmin bean when dynamic is switch to false");
|
||||
} catch (Exception e) {}
|
||||
}
|
||||
|
||||
@Configuration
|
||||
protected static class TestConfiguration {
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
protected static class TestConfiguration2 {
|
||||
@Bean
|
||||
ConnectionFactory aDifferentConnectionFactory() {
|
||||
return new CachingConnectionFactory("otherserver", 8001);
|
||||
}
|
||||
}
|
||||
}
|
||||
64
spring-boot-cli/samples/rabbit.groovy
Normal file
64
spring-boot-cli/samples/rabbit.groovy
Normal file
@@ -0,0 +1,64 @@
|
||||
package org.test
|
||||
|
||||
import java.util.concurrent.CountDownLatch
|
||||
|
||||
@Log
|
||||
@Configuration
|
||||
@EnableRabbitMessaging
|
||||
class RabbitExample implements CommandLineRunner {
|
||||
|
||||
private CountDownLatch latch = new CountDownLatch(1)
|
||||
|
||||
@Autowired
|
||||
RabbitTemplate rabbitTemplate
|
||||
|
||||
private String queueName = "spring-boot"
|
||||
|
||||
@Bean
|
||||
Queue queue() {
|
||||
new Queue(queueName, false)
|
||||
}
|
||||
|
||||
@Bean
|
||||
TopicExchange exchange() {
|
||||
new TopicExchange("spring-boot-exchange")
|
||||
}
|
||||
|
||||
/**
|
||||
* The queue and topic exchange cannot be inlined inside this method and have
|
||||
* dynamic creation with Spring AMQP work properly.
|
||||
*/
|
||||
@Bean
|
||||
Binding binding(Queue queue, TopicExchange exchange) {
|
||||
BindingBuilder
|
||||
.bind(queue)
|
||||
.to(exchange)
|
||||
.with("spring-boot")
|
||||
}
|
||||
|
||||
@Bean
|
||||
SimpleMessageListenerContainer container(CachingConnectionFactory connectionFactory) {
|
||||
return new SimpleMessageListenerContainer(
|
||||
connectionFactory: connectionFactory,
|
||||
queueNames: [queueName],
|
||||
messageListener: new MessageListenerAdapter(new Receiver(latch:latch), "receive")
|
||||
)
|
||||
}
|
||||
|
||||
void run(String... args) {
|
||||
log.info "Sending RabbitMQ message..."
|
||||
rabbitTemplate.convertAndSend(queueName, "Greetings from Spring Boot via RabbitMQ")
|
||||
latch.await()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Log
|
||||
class Receiver {
|
||||
CountDownLatch latch
|
||||
|
||||
def receive(String message) {
|
||||
log.info "Received ${message}"
|
||||
latch.countDown()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
* Copyright 2012-2013 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.boot.cli.compiler.autoconfigure;
|
||||
|
||||
import org.codehaus.groovy.ast.ClassNode;
|
||||
import org.codehaus.groovy.control.CompilationFailedException;
|
||||
import org.codehaus.groovy.control.customizers.ImportCustomizer;
|
||||
import org.springframework.boot.cli.compiler.AstUtils;
|
||||
import org.springframework.boot.cli.compiler.CompilerAutoConfiguration;
|
||||
import org.springframework.boot.cli.compiler.DependencyCustomizer;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
|
||||
/**
|
||||
* {@link CompilerAutoConfiguration} for Spring Rabbit.
|
||||
*
|
||||
* @author Greg Turnquist
|
||||
*/
|
||||
public class RabbitCompilerAutoConfiguration extends CompilerAutoConfiguration {
|
||||
|
||||
@Override
|
||||
public boolean matches(ClassNode classNode) {
|
||||
// Slightly weird detection algorithm because there is no @Enable annotation for
|
||||
// Integration
|
||||
return AstUtils.hasAtLeastOneAnnotation(classNode, "EnableRabbitMessaging");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void applyDependencies(DependencyCustomizer dependencies)
|
||||
throws CompilationFailedException {
|
||||
dependencies.add("org.springframework.amqp", "spring-rabbit",
|
||||
dependencies.getProperty("spring-rabbit.version"));
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void applyImports(ImportCustomizer imports) throws CompilationFailedException {
|
||||
imports.addStarImports("org.springframework.amqp.rabbit.core",
|
||||
"org.springframework.amqp.rabbit.connection",
|
||||
"org.springframework.amqp.rabbit.listener",
|
||||
"org.springframework.amqp.rabbit.listener.adapter",
|
||||
"org.springframework.amqp.core").addImports(EnableRabbitMessaging.class.getCanonicalName());
|
||||
}
|
||||
|
||||
@Target(ElementType.TYPE)
|
||||
@Documented
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public static @interface EnableRabbitMessaging {
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
org.springframework.boot.cli.compiler.autoconfigure.SpringBootCompilerAutoConfiguration
|
||||
org.springframework.boot.cli.compiler.autoconfigure.SpringMvcCompilerAutoConfiguration
|
||||
org.springframework.boot.cli.compiler.autoconfigure.SpringBatchCompilerAutoConfiguration
|
||||
org.springframework.boot.cli.compiler.autoconfigure.RabbitCompilerAutoConfiguration
|
||||
org.springframework.boot.cli.compiler.autoconfigure.ReactorCompilerAutoConfiguration
|
||||
org.springframework.boot.cli.compiler.autoconfigure.JdbcCompilerAutoConfiguration
|
||||
org.springframework.boot.cli.compiler.autoconfigure.JmsCompilerAutoConfiguration
|
||||
|
||||
@@ -4,6 +4,7 @@ reactor.version: ${reactor.version}
|
||||
spring.version: ${spring.version}
|
||||
spring-batch.version: ${spring-batch.version}
|
||||
spring-boot.version: ${project.version}
|
||||
spring-rabbit.version: ${spring-rabbit.version}
|
||||
spring-security.version: ${spring-security.version}
|
||||
spring-integration.version: ${spring-integration.version}
|
||||
spring-integration-groovydsl.version: ${spring-integration-groovydsl.version}
|
||||
|
||||
@@ -24,11 +24,7 @@ import java.util.concurrent.Future;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.apache.ivy.util.FileUtil;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.*;
|
||||
import org.springframework.boot.OutputCapture;
|
||||
import org.springframework.boot.cli.command.CleanCommand;
|
||||
import org.springframework.boot.cli.command.RunCommand;
|
||||
@@ -196,4 +192,13 @@ public class SampleIntegrationTests {
|
||||
FileUtil.forceDelete(new File("activemq-data")); // cleanup ActiveMQ cruft
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore // this test requires RabbitMQ to be run, so disable it be default
|
||||
public void rabbitSample() throws Exception {
|
||||
start("samples/rabbit.groovy");
|
||||
String output = this.outputCapture.getOutputAndRelease();
|
||||
assertTrue("Wrong output: " + output,
|
||||
output.contains("Received Greetings from Spring Boot via RabbitMQ"));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -39,6 +39,7 @@
|
||||
<spring-batch.version>2.2.0.RELEASE</spring-batch.version>
|
||||
<spring-data-jpa.version>1.4.1.RELEASE</spring-data-jpa.version>
|
||||
<spring-data-mongo.version>1.3.1.RELEASE</spring-data-mongo.version>
|
||||
<spring-rabbit.version>1.2.0.RELEASE</spring-rabbit.version>
|
||||
<thymeleaf.version>2.0.16</thymeleaf.version>
|
||||
<thymeleaf-extras-springsecurity3.version>2.0.0</thymeleaf-extras-springsecurity3.version>
|
||||
<thymeleaf-layout-dialect.version>1.1.1</thymeleaf-layout-dialect.version>
|
||||
@@ -420,6 +421,11 @@
|
||||
<artifactId>spring-security-acl</artifactId>
|
||||
<version>${spring-security.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.amqp</groupId>
|
||||
<artifactId>spring-rabbit</artifactId>
|
||||
<version>${spring-rabbit.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.thymeleaf</groupId>
|
||||
<artifactId>thymeleaf</artifactId>
|
||||
|
||||
Reference in New Issue
Block a user