領先一步
VMware 提供培訓和認證,助您快速進步。
瞭解更多如果您錯過了今年的 SpringOne 2GX 大會,其中一個熱門的主題演講內容就是 Spring Boot 的釋出。Dave Syer 展示瞭如何使用一段僅夠一條 推文 的程式碼快速建立 Spring MVC 應用。在這篇博文中,我將揭秘 Spring Boot 的內部,並透過提交一個 拉取請求 來向您展示它是如何工作的。
Spring Boot 有一個強大的自動配置功能。當它檢測到 classpath 上存在某些事物時,它會自動建立 beans。但是它還沒有的一項功能是對 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 beans 的來源,供 Spring 拾取並放入 應用上下文 中。它利用了 Spring 4 的 @Conditional
註解,限制僅在 classpath 上存在 JmsTemplate
時才新增這組 beans。這是 spring-jms 存在於 classpath 上的一個明確跡象。完美!
我的新類包含兩個內部類,它們也被標記為 Spring Java Configuration,並附加了額外條件。這使得我可以輕鬆地整合所有配置需求,從而自動化 Spring JMS 配置。
JmsTemplateCreator
建立一個 JmsTemplate
。它僅在其他地方尚未定義 JmsTemplate
時生效。這是 Spring Boot 如何對建立 JmsTemplate
擁有自己的觀點,但如果您提供自己的 JmsTemplate
,它會迅速讓步的方式。ActiveMQConnectionFactoryCreator
建立一個 ActiveMQConnectionFactory
,但前提是它檢測到 classpath 上存在 ActiveMQ,並且在所有 Spring beans 中沒有定義其他 ConnectionFactory
。這個工廠對於建立 JmsTemplate
是必需的。它設定為記憶體模式,這意味著您甚至無需安裝獨立的 broker 即可開始使用 JMS。但是您可以輕鬆替換自己的 ConnectionFactory
,無論哪種方式,Spring Boot 都會將其自動裝配到 JmsTemplate
中。如果我沒有正確註冊我的新 JmsTemplateAutoConfiguration
,所有這些自動配置都將無效。我透過將 FQDN 新增到 Spring Boot 的 spring.factories 檔案來完成此操作。
. . .
org.springframework.boot.autoconfigure.jms.JmsTemplateAutoConfiguration,\
. . .
當然,沒有自動化單元測試的拉取請求是不完整的。我不會將我編寫的所有測試都放在這篇博文中,但您可以檢視 我隨拉取請求提交的測試。只是在提交拉取請求之前,請準備好編寫您自己的測試集!
這就是向 Spring Boot 新增自動配置的全部內容!它並不複雜。事實上,您可以檢視現有的自動配置類以獲取更多示例。
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)
}
}
為了新增 Spring JMS 支援,我需要向 Boot 的 CLI 新增類似的自動配置,以便無論何時有人使用 JmsTemplate
、DefaultMessageListenerContainer
或 SimpleMessageListenerContainer
,它都會新增正確的依賴。
在編寫程式碼之前,我首先編寫了一個簡單的 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()
}
}
這個測試指令碼期望 JmsTemplate
和 ConnectionFactory
由 Spring Boot 自動提供。注意,除了引入 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()
允許您定義哪些符號會觸發此行為。對於此項,如果存在 JmsTemplate
、DefaultMessageListenerContainer
或 SimpleMessageListenerContainer
,它將觸發該操作。applyDependencies()
精確指定透過 Maven 座標新增到 classpath 的庫。這類似於嚮應用程式新增 @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: 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 的編碼標準,然後提交我的 拉取請求。歡迎檢視我的貢獻和後續評論。(附註:經過一些微調後,它被接受了。)
希望您喜歡這次對 Spring Boot 及其工作原理的深入探討。希望您也能編寫自己的補丁。