通过拉取请求为 Spring Boot 贡献代码

工程 | Greg L. Turnquist | 2013 年 9 月 20 日 | ...

如果您错过了今年的 SpringOne 2GX 大会,那么主题演讲中的一大亮点就是 Spring Boot 的发布。Dave Syer 展示了如何快速创建一个 Spring MVC 应用程序,其代码可以 压缩到一条推文中。在本篇博文中,我将揭开 Spring Boot 的面纱,并通过创建一个 拉取请求 来展示其工作原理。

自动配置

Spring Boot 拥有强大的自动配置功能。当它在类路径上检测到某些内容时,就会自动创建 Bean。但它目前还没有一个功能是支持 Spring JMS。我需要这个功能!

第一步是编写一个自动配置类

package org.springframework.boot.autoconfigure.jms;

. . .some import statements. . .

@Configuration
@ConditionalOnClass(JmsTemplate.class)
public class JmsTemplateAutoConfiguration {

	@Configuration
	@ConditionalOnMissingBean(JmsTemplate.class)
	protected static class JmsTemplateCreator {

		@Autowired
		ConnectionFactory connectionFactory;

		@Bean
		public JmsTemplate jmsTemplate() {
			JmsTemplate jmsTemplate = new JmsTemplate(connectionFactory);
			jmsTemplate.setPubSubDomain(true);
			return jmsTemplate;
		}

	}

	@Configuration
	@ConditionalOnClass(ActiveMQConnectionFactory.class)
	@ConditionalOnMissingBean(ConnectionFactory.class)
	protected static class ActiveMQConnectionFactoryCreator {
		@Bean
		ConnectionFactory connectionFactory() {
			return new ActiveMQConnectionFactory("vm://127.0.0.1");
		}
	}

}

我的 Spring JMS 自动配置类使用 Spring 的 @Configuration 注解进行标记,将其标记为 Spring Bean 的来源,这些 Bean 将被拾取并放入 应用程序上下文 中。它利用了 Spring 4 的 @Conditional 注解,仅在类路径上存在 JmsTemplate 时才添加此 Bean 集。这是一个明显的迹象,表明 spring-jms 位于类路径上。完美!

我的新类有两个内部类,也使用 Spring Java 配置进行标记,并带有额外的条件。这使得整合所有配置需求以自动化 Spring JMS 配置变得非常容易。

  • JmsTemplateCreator 创建一个 JmsTemplate。它仅在其他地方没有定义 JmsTemplate 时才有效。这就是 Spring Boot 如何对如何创建 JmsTemplate 有自己的见解,但如果您提供自己的 JmsTemplate,它会很快退让。
  • ActiveMQConnectionFactoryCreator 创建一个 ActiveMQConnectionFactory,但前提是它在类路径上检测到 ActiveMQ,并且在所有 Spring Bean 中没有定义其他 ConnectionFactory。此工厂对于创建 JmsTemplate 是必要的。它设置为内存模式,这意味着您甚至不需要安装独立的代理即可开始使用 JMS。但是您可以轻松地替换您自己的 ConnectionFactory,无论哪种方式,Spring Boot 都会将其自动连接到 JmsTemplate 中。

如果我不正确地注册我的新 JmsTemplateAutoConfiguration,那么所有这些自动配置都将毫无用处。我通过将 FQDN 添加到 Spring Boot 的 spring.factories 文件中来做到这一点。

. . .
org.springframework.boot.autoconfigure.jms.JmsTemplateAutoConfiguration,\
. . .

当然,没有一些自动化的单元测试,拉取请求是不完整的。我不会在本篇博文中列出我编写的全部测试,但您可以查看 我随拉取请求提交的测试。在提交拉取请求之前,请准备好编写自己的测试集!

这就是向 Spring Boot 添加自动配置的全部内容!它并不复杂。实际上,您可以 浏览现有的自动配置类 以获取更多示例。

Spring Boot 的 Groovy 支持

Spring Boot 最受关注的功能之一就是它对 Groovy 的强大支持。这在主题演讲中赢得了许多掌声,并在 Dave 和 Phil 次日的演讲中得到了广泛关注。如果您错过了它,以下是 Dave Syer 演示的 Spring Boot REST 服务

@RestController
class ThisWillActuallyRun {
    @RequestMapping("/")
    String home() {
        "Hello World!"
    }
}

将该代码放入 app.groovy 后,Dave 通过输入以下命令启动它:

$ spring run app.groovy

Spring Boot 的 命令行工具 使用嵌入式 Groovy 编译器并查看所有符号(如 RestController)。然后它自动添加 @Grab 和导入语句。它基本上将前面的代码片段扩展为以下内容:

@Grab("org.springframework.boot:spring-boot-starter-web:0.5.0.BUILD-SNAPSHOT")

import org.springframework.web.bind.annotation.*
import org.springframework.web.servlet.config.annotation.*
import org.springframework.web.servlet.*
import org.springframework.web.servlet.handler.*
import org.springframework.http.*
static import org.springframework.boot.cli.template.GroovyTemplate.template
import org.springframework.boot.cli.compiler.autoconfigure.WebConfiguration

@RestController
class ThisWillActuallyRun {
    @RequestMapping("/")
    String home() {
        "Hello World!"
    }
    
	public static void main(String[] args) {
		SpringApplication.run(ThisWillActuallyRun, args)
	}
}

添加您自己的 Groovy 集成

要添加 Spring JMS 支持,我需要向 Boot 的 CLI 添加类似的自动配置,以便每当有人使用 JmsTemplateDefaultMessageListenerContainerSimpleMessageListenerContainer 时,它都会添加正确的部分。

在编写该代码之前,我首先编写了一个简单的 Groovy 脚本,它在 jms.groovy 中使用 Spring JMS 功能

package org.test

@Grab("org.apache.activemq:activemq-all:5.2.0")

import java.util.concurrent.CountDownLatch

@Configuration
@Log
class JmsExample implements CommandLineRunner {

	private CountDownLatch latch = new CountDownLatch(1)

	@Autowired
	JmsTemplate jmsTemplate

	@Bean
	DefaultMessageListenerContainer jmsListener(ConnectionFactory connectionFactory) {
		new DefaultMessageListenerContainer([
			connectionFactory: connectionFactory,
			destinationName: "spring-boot",
			pubSubDomain: true,
			messageListener: new MessageListenerAdapter(new Receiver(latch:latch)) {{
				defaultListenerMethod = "receive"
			}}
		])
	}

	void run(String... args) {	
		def messageCreator = { session ->
			session.createObjectMessage("Greetings from Spring Boot via ActiveMQ")
		} as MessageCreator
		log.info "Sending JMS message..."
		jmsTemplate.send("spring-boot", messageCreator)
		latch.await()
	}

}

@Log
class Receiver {
	CountDownLatch latch

    def receive(String message) {
        log.info "Received ${message}"
        latch.countDown()
    }
}

此测试脚本期望 Spring Boot 自动提供 JmsTemplateConnectionFactory。请注意,除了引入 activemq-all 之外,没有导入语句或任何 @Grab。它使用 Spring Boot 的 CommandLineRunner 接口启动 run() 方法,该方法依次通过 JmsTemplate 发送消息。然后它使用 CountDownLatch 等待来自消费者的信号。

另一端是一个 DefaultMessageListener,它在收到消息后进行倒计时。为了从 Spring Boot 的测试套件内部调用我的脚本,我向 SampleIntegrationTests 添加了以下测试方法以调用 jms.groovy

	@Test
	public void jmsSample() throws Exception {
		start("samples/jms.groovy");
		String output = this.outputCapture.getOutputAndRelease();
		assertTrue("Wrong output: " + output,
				output.contains("Received Greetings from Spring Boot via ActiveMQ"));
		FileUtil.forceDelete(new File("activemq-data")); // cleanup ActiveMQ cruft
	}

为了测试我的新补丁,我发现运行特定的测试更容易。这绝对加快了速度。

$ mvn clean -Dtest=SampleIntegrationTests#jmsSample test

注意:我必须首先运行 mvn -DskipTests install 以将我的新 JMS 自动配置功能部署到我的本地 Maven 存储库中。

由于我还没有编写任何 Groovy 自动配置,因此测试将失败。是时候编写 CLI 自动配置了!

package org.springframework.boot.cli.compiler.autoconfigure;

. . .import statements. . .

public class JmsCompilerAutoConfiguration extends CompilerAutoConfiguration {

	@Override
	public boolean matches(ClassNode classNode) {
		return AstUtils.hasAtLeastOneFieldOrMethod(classNode, "JmsTemplate",
				"DefaultMessageListenerContainer", "SimpleMessageListenerContainer");
	}

	@Override
	public void applyDependencies(DependencyCustomizer dependencies)
			throws CompilationFailedException {
		dependencies.add("org.springframework", "spring-jms",
				dependencies.getProperty("spring.version")).add(
				"org.apache.geronimo.specs", "geronimo-jms_1.1_spec", "1.1");

	}

	@Override
	public void applyImports(ImportCustomizer imports) throws CompilationFailedException {
		imports.addStarImports("javax.jms", "org.springframework.jms.core",
				"org.springframework.jms.listener",
				"org.springframework.jms.listener.adapter");
	}

}

这些回调钩子使与 Spring Boot 的 CLI 工具集成变得非常容易。

  • matches() 允许您定义触发此行为的符号。对于此示例,如果存在 JmsTemplateDefaultMessageListenerContainerSimpleMessageListenerContainer,它将触发操作。
  • applyDependencies() 指定通过 Maven 坐标将哪些库添加到类路径中。这类似于向应用程序添加 @Grab 注解。对于此示例,我们需要 spring-jms 用于 JmsTemplate,以及 geronimo-jms 用于 JMS API 规范类。
  • applyImports() 将导入语句添加到代码的顶部。我基本上查看了自动配置测试代码中的 Spring JMS 导入,并将它们添加到此处。

这次,如果您运行测试套件,它应该会通过。

$ mvn clean -Dtest=SampleIntegrationTests#jmsSample test
  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::  (v0.5.0.BUILD-SNAPSHOT)

2013-09-18 11:47:03.800  INFO 22969 --- [       runner-0] o.s.boot.SpringApplication               : Starting application on retina with PID 22969 (/Users/gturnquist/.groovy/grapes/org.springframework.boot/spring-boot/jars/spring-boot-0.5.0.BUILD-SNAPSHOT.jar started by gturnquist)
2013-09-18 11:47:03.825  INFO 22969 --- [       runner-0] s.c.a.AnnotationConfigApplicationContext : Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@4670f288: startup date [Wed Sep 18 11:47:03 CDT 2013]; root of context hierarchy
2013-09-18 11:47:04.428  INFO 22969 --- [       runner-0] o.s.c.support.DefaultLifecycleProcessor  : Starting beans in phase 2147483647
2013-09-18 11:47:04.498  INFO 22969 --- [       runner-0] o.apache.activemq.broker.BrokerService   : Using Persistence Adapter: AMQPersistenceAdapter(activemq-data/localhost)
2013-09-18 11:47:04.501  INFO 22969 --- [       runner-0] o.a.a.store.amq.AMQPersistenceAdapter    : AMQStore starting using directory: activemq-data/localhost
2013-09-18 11:47:04.515  INFO 22969 --- [       runner-0] org.apache.activemq.kaha.impl.KahaStore  : Kaha Store using data directory activemq-data/localhost/kr-store/state
2013-09-18 11:47:04.541  INFO 22969 --- [       runner-0] o.a.a.store.amq.AMQPersistenceAdapter    : Active data files: []
2013-09-18 11:47:04.586  INFO 22969 --- [       runner-0] o.apache.activemq.broker.BrokerService   : ActiveMQ null JMS Message Broker (localhost) is starting
2013-09-18 11:47:04.587  INFO 22969 --- [       runner-0] o.apache.activemq.broker.BrokerService   : For help or more information please see: https://activemq.apache.ac.cn/
2013-09-18 11:47:04.697  INFO 22969 --- [  JMX connector] o.a.a.broker.jmx.ManagementContext       : JMX consoles can connect to service:jmx:rmi:///jndi/rmi://127.0.0.1:1099/jmxrmi
2013-09-18 11:47:04.812  INFO 22969 --- [       runner-0] org.apache.activemq.kaha.impl.KahaStore  : Kaha Store using data directory activemq-data/localhost/kr-store/data
2013-09-18 11:47:04.814  INFO 22969 --- [       runner-0] o.apache.activemq.broker.BrokerService   : ActiveMQ JMS Message Broker (localhost, ID:retina-51737-1379522824687-0:0) started
2013-09-18 11:47:04.817  INFO 22969 --- [       runner-0] o.a.activemq.broker.TransportConnector   : Connector vm://127.0.0.1 Started
2013-09-18 11:47:04.867  INFO 22969 --- [       runner-0] o.s.boot.SpringApplication               : Started application in 1.218 seconds
2013-09-18 11:47:04.874  INFO 22969 --- [       runner-0] org.test.JmsExample                      : Sending JMS message...
2013-09-18 11:47:04.928  INFO 22969 --- [  jmsListener-1] org.test.Receiver                        : Received Greetings from Spring Boot via ActiveMQ
2013-09-18 11:47:04.931  INFO 22969 --- [           main] s.c.a.AnnotationConfigApplicationContext : Closing org.springframework.context.annotation.AnnotationConfigApplicationContext@4670f288: startup date [Wed Sep 18 11:47:03 CDT 2013]; root of context hierarchy
2013-09-18 11:47:04.932  INFO 22969 --- [           main] o.s.c.support.DefaultLifecycleProcessor  : Stopping beans in phase 2147483647
2013-09-18 11:47:05.933  INFO 22969 --- [           main] o.a.activemq.broker.TransportConnector   : Connector vm://127.0.0.1 Stopped
2013-09-18 11:47:05.933  INFO 22969 --- [           main] o.apache.activemq.broker.BrokerService   : ActiveMQ Message Broker (localhost, ID:retina-51737-1379522824687-0:0) is shutting down
2013-09-18 11:47:05.944  INFO 22969 --- [           main] o.apache.activemq.broker.BrokerService   : ActiveMQ JMS Message Broker (localhost, ID:retina-51737-1379522824687-0:0) stopped
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 4.432 sec - in org.springframework.boot.cli.SampleIntegrationTests

太棒了!

在此阶段,我需要做的就是查看 贡献指南,以确保我遵循 Spring Boot 的编码标准,然后提交我的 拉取请求。请随时查看我的贡献和后续评论。(附言:经过一些微调后,它被接受了。)

希望您喜欢这次对 Spring Boot 及其工作原理的深入探究。希望您能够编写自己的补丁。

获取 Spring 新闻通讯

通过 Spring 新闻通讯保持联系

订阅

领先一步

VMware 提供培训和认证,以加速您的进步。

了解更多

获取支持

Tanzu Spring 在一个简单的订阅中提供对 OpenJDK™、Spring 和 Apache Tomcat® 的支持和二进制文件。

了解更多

即将举行的活动

查看 Spring 社区中所有即将举行的活动。

查看全部