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

}

我的 Spring JMS 自動配置類別使用 Spring 的 @Configuration 註解標記,將其標記為 Spring bean 的來源,以便拾取並放入 應用程式內容中。它利用 Spring 4 的 @Conditional 註解,限制它僅在 JmsTemplate 位於類別路徑上時才新增這組 bean。這是 spring-jms 位於類別路徑上的一個明顯標誌。完美!

我的新類別有兩個內部類別,也標記為 Spring Java Configuration 並具有額外的條件。這使得可以輕鬆地整合所有配置需求,以自動化 Spring JMS 配置。

  • JmsTemplateCreator 建立一個 JmsTemplate。它僅在其他地方尚未定義 JmsTemplate 時才有效。這就是 Spring Boot 如何對如何建立 JmsTemplate 抱持自己的觀點,但如果您提供自己的 JmsTemplate,它會很快退讓。
  • ActiveMQConnectionFactoryCreator 建立一個 ActiveMQConnectionFactory,但僅當它偵測到 ActiveMQ 位於類別路徑上,並且在所有 Spring bean 中沒有定義其他 ConnectionFactory 時。這個 factory 是建立 JmsTemplate 所必需的。它被設定為記憶體模式,這意味著您甚至不需要安裝獨立的 broker 即可開始使用 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 自動提供 JmsTemplate 以及 ConnectionFactory。請注意,除了引入 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 import,並將它們新增到此處。

這次,如果您運行測試套件,它應該會通過。

$ 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.dev.org.tw/
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

Ta dah!

在這個階段,我所要做的就是查看 貢獻指南 以確保我遵循 Spring Boot 的程式碼編寫標準,然後提交我的 pull request。隨時查看我的貢獻和後續評論。(附註:經過一些微調後,它被接受了。)

我希望您喜歡對 Spring Boot 及其工作原理的深入研究。希望您能夠編寫自己的修補程式。

取得 Spring 電子報

隨時掌握 Spring 電子報

訂閱

搶先一步

VMware 提供培訓和認證,以加速您的進度。

了解更多

取得支援

Tanzu Spring 在一個簡單的訂閱中提供對 OpenJDK™、Spring 和 Apache Tomcat® 的支援和二進位檔。

了解更多

即將舉辦的活動

查看 Spring 社群中所有即將舉辦的活動。

查看全部