透過 Pull Request 為 Spring Boot 貢獻內容

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

如果您錯過了今年的 SpringOne 2GX 大會,那麼一個熱門的專題是釋出 Spring Boot。Dave Syer 展示瞭如何透過能夠容納在 一條推文 中的程式碼來快速建立一個 Spring MVC 應用。在這篇博文裡,我將揭開 Spring Boot 的神秘面紗,並透過提交一個 Pull Request 來向您展示它是如何工作的。

自動配置

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://");
		}
	}

}

我的 Spring JMS 自動配置類用 Spring 的 @Configuration 註解標記,將其標記為將被拾取並放入 應用程式上下文 的 Spring bean 源。它利用 Spring 4 的 @Conditional 註解,將其限制為僅當類路徑上有 JmsTemplate 時才新增這組 bean。這是 spring-jms 在類路徑上的一個明顯標誌。太棒了!

我的新類有兩個內部類,也都標記了 Spring Java 配置和附加條件。這使得將我所有的配置需求整合起來以自動化 Spring JMS 配置變得很容易。

  • JmsTemplateCreator 建立一個 JmsTemplate。它只在沒有其他地方定義 JmsTemplate 時才工作。這就是 Spring Boot 如何對如何建立 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,\
. . .

當然,沒有任何 Pull Request 會在沒有一些自動化單元測試的情況下是完整的。我不會把所有寫的測試都放在這篇博文裡,但您可以檢視 我與我的 Pull Request 一起提交的測試。在提交 Pull Request 之前,請準備好編寫您自己的測試套件!

這就是為 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 和 import 語句。它基本上將前面的片段擴充套件為

@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 之外,沒有 import 語句也沒有 @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() 將 import 語句新增到您程式碼的頂部。我基本上查看了自動配置測試程式碼中的 Spring JMS imports,並將它們新增到了這裡。

這一次,如果您執行測試套件,它應該會透過。

$ 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: http://activemq.apache.org/
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://: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:// 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:// 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 的編碼標準,然後提交我的 Pull Request。隨時檢視我的貢獻和後續評論。(附註:經過一些微調後,它被接受了。)

我希望您喜歡這篇對 Spring Boot 及其工作原理的深入探討。希望您也能編寫自己的補丁。

獲取 Spring 新聞通訊

透過 Spring 新聞通訊保持聯絡

訂閱

領先一步

VMware 提供培訓和認證,助您加速進步。

瞭解更多

獲得支援

Tanzu Spring 提供 OpenJDK™、Spring 和 Apache Tomcat® 的支援和二進位制檔案,只需一份簡單的訂閱。

瞭解更多

即將舉行的活動

檢視 Spring 社群所有即將舉行的活動。

檢視所有