1. Overview
1.概述
We saw in the previous installments of the series how we can leverage Spring Remoting and the related technologies to enable synchronous Remote Procedure Calls on top of an HTTP channel between a server and a client.
我们在该系列的前几期中看到,我们可以利用Spring Remoting和相关技术,在服务器和客户端之间的HTTP通道之上实现同步的远程过程调用。
In this article, we’ll explore Spring Remoting on top of AMQP, which makes it possible to execute synchronous RPC while leveraging a medium that is inherently asynchronous.
在这篇文章中,我们将探讨Spring Remoting在AMQP之上的情况,这使得执行同步RPC同时利用固有的异步媒介成为可能。
2. Installing RabbitMQ
2.安装RabbitMQ[/strong
There are various messaging systems that are compatible with AMQP that we could use, and we choose RabbitMQ because it’s a proven platform and it’s fully supported in Spring – both products are managed by the same company (Pivotal).
有各种与AMQP兼容的消息传递系统可以使用,我们选择了RabbitMQ,因为它是一个成熟的平台,而且在Spring中得到了完全的支持–这两个产品都由同一家公司(Pivotal)管理。
If you’re not acquainted with AMQP or RabbitMQ you can read our quick introduction.
如果你对AMQP或RabbitMQ不熟悉,你可以阅读我们的快速介绍。
So, the first step is to install and start RabbitMQ. There are various ways to install it – just choose your preferred method following the steps mentioned in the official guide.
因此,第一步是安装并启动RabbitMQ。有多种方法来安装它 – 只要按照官方指南中提到的步骤选择你喜欢的方法即可。
3. Maven Dependencies
3.Maven的依赖性
We are going to set up server and client Spring Boot applications to show how AMQP Remoting works. As is often the case with Spring Boot, we just have to choose and import the correct starter dependencies, as explained here:
我们将设置服务器和客户端Spring Boot应用程序,以展示AMQP Remoting如何工作。与Spring Boot的情况一样,我们只需选择并导入正确的启动器依赖项,如这里所解释的。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
</dependency>
We explicitly excluded the spring-boot-starter-tomcat because we don’t need any embedded HTTP server – that would be automatically started instead if we allowed Maven to import all the transitive dependencies in the classpath.
我们明确排除了spring-boot-starter-tomcat,因为我们不需要任何嵌入式HTTP服务器–如果我们允许Maven导入classpath中的所有横向依赖,它就会自动启动。
4. Server Application
4.服务器应用程序
4.1. Expose the Service
4.1.暴露服务
As we showed in the previous articles, we’ll expose a CabBookingService that simulates a likely remote service.
正如我们在以前的文章中所展示的,我们将暴露一个CabBookingService,模拟一个可能的远程服务。
Let’s start by declaring a bean that implements the interface of the service we want to make remotely callable. This is the bean that will actually execute the service call at server-side:
我们首先声明一个Bean,它实现了我们想要远程调用的服务的接口。这个Bean将在服务器端实际执行服务调用。
@Bean
CabBookingService bookingService() {
return new CabBookingServiceImpl();
}
Let’s then define the queue from which the server will retrieve invocations. It’s enough, in this case, to specify a name for it, providing it in the constructor:
然后让我们定义队列,服务器将从该队列中检索调用。在这种情况下,只需为其指定一个名称,在构造函数中提供该名称即可。
@Bean
Queue queue() {
return new Queue("remotingQueue");
}
As we already know from the previous articles, one of the main concepts of Spring Remoting is the Service Exporter, the component that actually collects the invocation requests from some source ─ in this case, a RabbitMQ queue ─ and invokes the desired method on the service implementation.
正如我们在之前的文章中所知,Spring Remoting的主要概念之一是Service Exporter,这个组件实际上是从某个来源(在这里是RabbitMQ队列)收集调用请求,并在服务实现上调用所需方法。
In this case, we define an AmqpInvokerServiceExporter that ─ as you can see ─ needs a reference to an AmqpTemplate. The AmqpTemplate class is provided by the Spring Framework and eases the handling of AMQP-compatible messaging systems the same way the JdbcTemplate makes easier to deal with databases.
在这种情况下,我们定义了一个AmqpInvokerServiceExporter,正如你所看到的,它需要一个对AmqpTemplate的引用。AmqpTemplate类由Spring Framework提供,它简化了对AMQP-兼容的消息系统的处理,就像JdbcTemplate简化了对数据库的处理。
We won’t explicitly define such AmqpTemplate bean because it will be automatically provided by Spring Boot‘s auto-configuration module:
我们不会明确定义这种AmqpTemplateBean,因为它将由Spring Boot的自动配置模块自动提供。
@Bean AmqpInvokerServiceExporter exporter(
CabBookingService implementation, AmqpTemplate template) {
AmqpInvokerServiceExporter exporter = new AmqpInvokerServiceExporter();
exporter.setServiceInterface(CabBookingService.class);
exporter.setService(implementation);
exporter.setAmqpTemplate(template);
return exporter;
}
Finally, we need to define a container that has the responsibility to consume messages from the queue and forward them to some specified listener.
最后,我们需要定义一个容器,该容器负责从队列中消费消息,并将其转发给一些指定的听众。
We’ll then connect this container to the service exporter, we created in the previous step, to allow it to receive the queued messages. Here the ConnectionFactory is automatically provided by Spring Boot the same way the AmqpTemplate is:
然后,我们将将这个容器连接到我们在上一步创建的服务输出器,以使其能够接收排队的消息。这里的ConnectionFactory是由Spring Boot自动提供的,与AmqpTemplate的方式相同。
@Bean
SimpleMessageListenerContainer listener(
ConnectionFactory facotry,
AmqpInvokerServiceExporter exporter,
Queue queue) {
SimpleMessageListenerContainer container
= new SimpleMessageListenerContainer(facotry);
container.setMessageListener(exporter);
container.setQueueNames(queue.getName());
return container;
}
4.2. Configuration
4.2.配置
Let’s remember to set up the application.properties file to allow Spring Boot to configure the basic objects. Obviously, the values of the parameters will also depend on the way RabbitMQ has been installed.
让我们记得设置application.properties文件,以便让Spring Boot配置基本对象。显然,参数的值也将取决于RabbitMQ的安装方式。
For instance, the following one could be a reasonable configuration when RabbitMQ runs it the same machine where this example runs:
例如,当RabbitMQ在运行本例的同一台机器上运行时,下面的配置可能是一个合理的配置。
spring.rabbitmq.dynamic=true
spring.rabbitmq.port=5672
spring.rabbitmq.username=guest
spring.rabbitmq.password=guest
spring.rabbitmq.host=localhost
5. Client Application
5.客户端应用
5.1. Invoke the Remote Service
5.1.调用远程服务
Let’s tackle the client now. Again, we need to define the queue where invocation messages will be written to. We need to double-check that both client and server use the same name.
现在我们来解决客户端的问题。同样,我们需要定义调用信息将被写入的队列。我们需要仔细检查客户端和服务器是否使用相同的名称。
@Bean
Queue queue() {
return new Queue("remotingQueue");
}
At client-side, we need a slightly more complex setup than on the server side. In fact, we need to define an Exchange with the related Binding:
在客户端,我们需要一个比服务器端更复杂的设置。事实上,我们需要定义一个Exchange与相关Binding。
@Bean
Exchange directExchange(Queue someQueue) {
DirectExchange exchange = new DirectExchange("remoting.exchange");
BindingBuilder
.bind(someQueue)
.to(exchange)
.with("remoting.binding");
return exchange;
}
A good intro on the main concepts of RabbitMQ as Exchanges and Bindings is available here.
关于RabbitMQ的主要概念如Exchanges和Bindings的良好介绍可在这里获得。
Since Spring Boot does not auto-configure the AmqpTemplate, we must set one up ourselves, specifying a routing key. In doing so, we need to double-check that the routing key and the exchange match with the one used to define the Exchange in the previous step:
由于Spring Boot不会自动配置AmqpTemplate,我们必须自己设置一个,指定一个routing key。在这样做的时候,我们需要仔细检查routing key和exchange是否与上一步中用来定义Exchange的匹配。
@Bean RabbitTemplate amqpTemplate(ConnectionFactory factory) {
RabbitTemplate template = new RabbitTemplate(factory);
template.setRoutingKey("remoting.binding");
template.setExchange("remoting.exchange");
return template;
}
Then, as we did with other Spring Remoting implementations, we define a FactoryBean that will produce local proxies of the service that is remotely exposed. Nothing too fancy here, we just need to provide the interface of the remote service:
然后,正如我们对其他Spring Remoting实现所做的那样,我们定义一个FactoryBean,它将产生远程暴露的服务的本地代理。这里没有太多花哨的东西,我们只需要提供远程服务的接口。
@Bean AmqpProxyFactoryBean amqpFactoryBean(AmqpTemplate amqpTemplate) {
AmqpProxyFactoryBean factoryBean = new AmqpProxyFactoryBean();
factoryBean.setServiceInterface(CabBookingService.class);
factoryBean.setAmqpTemplate(amqpTemplate);
return factoryBean;
}
We can now use the remote service as if it was declared as a local bean:
我们现在可以使用远程服务,就像它被声明为一个本地Bean一样:。
CabBookingService service = context.getBean(CabBookingService.class);
out.println(service.bookRide("13 Seagate Blvd, Key Largo, FL 33037"));
5.2. Setup
5.2.设置
Also for the client application, we have to properly choose the values in the application.properties file. In a common setup, those would exactly match the ones used on the server side.
另外,对于客户端应用程序,我们必须在application.properties文件中正确选择数值。在一个普通的设置中,这些值将与服务器端使用的值完全一致。
5.3. Run the Example
5.3.运行实例
This should be enough to demonstrate the remote invocation through RabbitMQ. Let’s then start RabbitMQ, the server application, and the client application that invokes the remote service.
这应该足以证明通过 RabbitMQ 进行的远程调用。然后让我们启动 RabbitMQ、服务器应用程序和调用远程服务的客户端应用程序。
What happens behind the scenes is that the AmqpProxyFactoryBean will build a proxy that implements the CabBookingService.
幕后发生的事情是,AmqpProxyFactoryBean将建立一个实现CabBookingService的代理。
When a method is invoked on that proxy, it queues a message on the RabbitMQ, specifying in it all the parameters of the invocation and a name of a queue to be used to send back the result.
当一个方法在该代理上被调用时,它在RabbitMQ上排一个消息,在其中指定调用的所有参数和要用来送回结果的队列名称。
The message is consumed from the AmqpInvokerServiceExporter that invokes the actual implementation. It then collects the result in a message and places it on the queue which name was specified in the incoming message.
消息从调用实际实现的AmqpInvokerServiceExporter中被消耗。然后,它在消息中收集结果,并将其放在队列中,队列的名称是在传入消息中指定的。
The AmqpProxyFactoryBean receives back the result and, finally, returns the value that has been originally produced at the server side.
AmqpProxyFactoryBean接收返回的结果,最后,返回最初在服务器端产生的值。
6. Conclusion
6.结论
In this article, we saw how we can use Spring Remoting to provide RPC on top of a messaging system.
在这篇文章中,我们看到了如何使用Spring Remoting来在消息系统之上提供RPC。
It’s probably not the way to go for the main scenarios where we probably prefer to leverage the asynchronicity of RabbitMQ, but in some selected and limited scenarios, a synchronous call can be easier to understand and quicker and simpler to develop.
对于我们可能更喜欢利用RabbitMQ的异步性的主要场景来说,这可能不是一种方式,但在一些选定的和有限的场景中,同步调用可能更容易理解,开发起来也更快更简单。
As usual, you’ll find the sources on over on GitHub.
像往常一样,你可以在GitHub上找到源代码。