领先一步
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://localhost");
}
}
}
我的 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: 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://localhost: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://localhost 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://localhost 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 及其工作原理的深入探讨。希望您也能编写自己的补丁。