Перейти к основному содержимому

Весеннее удаленное взаимодействие с AMQP

· 6 мин. чтения

1. Обзор

В предыдущих частях этой серии мы видели , как мы можем использовать Spring Remoting и связанные с ним технологии для включения синхронных удаленных вызовов процедур поверх HTTP-канала между сервером и клиентом.

В этой статье мы рассмотрим Spring Remoting поверх AMQP , который позволяет выполнять синхронный RPC , используя среду, которая по своей сути является асинхронной .

2. Установка RabbitMQ

Существуют различные системы обмена сообщениями, совместимые с AMQP , которые мы могли бы использовать, и мы выбираем RabbitMQ , потому что это проверенная платформа и она полностью поддерживается в Spring — оба продукта управляются одной и той же компанией (Pivotal).

Если вы не знакомы с AMQP или RabbitMQ , вы можете прочитать наше краткое введение .

Итак, первый шаг — установить и запустить RabbitMQ . Существуют различные способы его установки — просто выберите предпочтительный способ, следуя шагам, указанным в официальном руководстве .

3. Зависимости Maven

Мы собираемся настроить серверные и клиентские приложения 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>

Мы явно исключили spring-boot-starter-tomcat , потому что нам не нужен какой-либо встроенный HTTP - сервер — вместо этого он будет запускаться автоматически, если мы позволим Maven импортировать все транзитивные зависимости в пути к классам.

4. Серверное приложение

4.1. Разоблачить сервис

Как мы показали в предыдущих статьях, мы предоставим CabBookingService , который имитирует вероятный удаленный сервис.

Начнем с объявления bean-компонента, реализующего интерфейс службы, которую мы хотим сделать доступной для удаленного вызова. Это bean-компонент, который фактически выполнит вызов службы на стороне сервера:

@Bean 
CabBookingService bookingService() {
return new CabBookingServiceImpl();
}

Давайте затем определим очередь, из которой сервер будет извлекать вызовы. В этом случае достаточно указать для него имя, предоставив его в конструкторе:

@Bean 
Queue queue() {
return new Queue("remotingQueue");
}

Как мы уже знаем из предыдущих статей, одна из основных концепций Spring Remoting — это Service Exporter , компонент, который фактически собирает запросы на вызов из какого-то источника — в данном случае из очереди RabbitMQ — и вызывает нужный метод в сервисе. реализация .

В этом случае мы определяем AmqpInvokerServiceExporter , который, как вы видите, нуждается в ссылке на AmqpTemplate . Класс AmqpTemplate предоставляется Spring Framework и упрощает работу с AMQP - совместимыми системами обмена сообщениями так же, как JdbcTemplate упрощает работу с базами данных.

Мы не будем явно определять такой bean-компонент AmqpTemplate , потому что он будет автоматически предоставлен модулем автоконфигурации 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;
}

Наконец, нам нужно определить контейнер , который отвечает за получение сообщений из очереди и пересылку их определенному слушателю .

Затем мы подключим этот контейнер к экспортеру служб, созданному на предыдущем шаге, чтобы он мог получать сообщения из очереди . Здесь 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. Конфигурация

Не забудьте настроить файл application.properties , чтобы Spring Boot мог настраивать основные объекты. Очевидно, что значения параметров также будут зависеть от способа установки RabbitMQ .

Например, следующая конфигурация может быть разумной, если RabbitMQ запускает ее на той же машине, на которой выполняется этот пример:

spring.rabbitmq.dynamic=true
spring.rabbitmq.port=5672
spring.rabbitmq.username=guest
spring.rabbitmq.password=guest
spring.rabbitmq.host=localhost

5. Клиентское приложение

5.1. Вызвать удаленную службу

Теперь займемся клиентом. Опять же, нам нужно определить очередь, в которую будут записываться сообщения о вызовах . Нам нужно перепроверить, что и клиент, и сервер используют одно и то же имя.

@Bean 
Queue queue() {
return new Queue("remotingQueue");
}

На стороне клиента нам нужна немного более сложная настройка, чем на стороне сервера. На самом деле нам нужно определить Exchange с соответствующим Binding :

@Bean 
Exchange directExchange(Queue someQueue) {
DirectExchange exchange = new DirectExchange("remoting.exchange");
BindingBuilder
.bind(someQueue)
.to(exchange)
.with("remoting.binding");
return exchange;
}

Хорошее введение в основные понятия RabbitMQ как Exchanges и Bindings доступно здесь .

Поскольку Spring Boot не настраивает AmqpTemplate автоматически , мы должны настроить его самостоятельно, указав ключ ar outing . При этом нам нужно дважды проверить, совпадают ли ключ маршрутизации и биржа с той, которая использовалась для определения биржи на предыдущем шаге:

@Bean RabbitTemplate amqpTemplate(ConnectionFactory factory) {
RabbitTemplate template = new RabbitTemplate(factory);
template.setRoutingKey("remoting.binding");
template.setExchange("remoting.exchange");
return template;
}

Затем, как и в случае с другими реализациями Spring Remoting , мы определяем FactoryBean , который будет создавать локальные прокси-серверы удаленно предоставляемой службы . Здесь нет ничего особенного, нам просто нужно предоставить интерфейс удаленного сервиса:

@Bean AmqpProxyFactoryBean amqpFactoryBean(AmqpTemplate amqpTemplate) {
AmqpProxyFactoryBean factoryBean = new AmqpProxyFactoryBean();
factoryBean.setServiceInterface(CabBookingService.class);
factoryBean.setAmqpTemplate(amqpTemplate);
return factoryBean;
}

Теперь мы можем использовать удаленную службу, как если бы она была объявлена как локальный компонент:

CabBookingService service = context.getBean(CabBookingService.class);
out.println(service.bookRide("13 Seagate Blvd, Key Largo, FL 33037"));

5.2. Настраивать

Также для клиентского приложения мы должны правильно выбрать значения в файле application.properties . В обычной настройке они будут точно соответствовать тем, которые используются на стороне сервера.

5.3. Запустите пример

Этого должно быть достаточно, чтобы продемонстрировать удаленный вызов через RabbitMQ . Затем запустим RabbitMQ, серверное приложение и клиентское приложение, которое вызывает удаленную службу.

Что происходит за кулисами, так это то, что AmqpProxyFactoryBean создаст прокси, который реализует CabBookingService .

Когда метод вызывается на этом прокси, он ставит сообщение в очередь на RabbitMQ , указывая в нем все параметры вызова и имя очереди, которая будет использоваться для отправки обратно результата.

Сообщение потребляется из AmqpInvokerServiceExporter , который вызывает реальную реализацию. Затем он собирает результат в сообщении и помещает его в очередь, имя которой было указано во входящем сообщении.

AmqpProxyFactoryBean получает результат и, наконец, возвращает значение, которое было первоначально создано на стороне сервера.

6. Заключение

В этой статье мы увидели, как мы можем использовать Spring Remoting для предоставления RPC поверх системы обмена сообщениями.

Вероятно, это не лучший способ для основных сценариев, в которых мы, вероятно, предпочитаем использовать асинхронность RabbitMQ , но в некоторых избранных и ограниченных сценариях синхронный вызов может быть проще для понимания, быстрее и проще в разработке.

Как обычно, вы найдете исходники на GitHub .