Введение в Spring Remoting с помощью HTTP Invokers

Введение в Spring Remoting с помощью HTTP Invokers

1. обзор

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

Spring Framework предлагает ряд инструментов под общим названиемSpring Remoting, которые позволяют нам вызывать удаленные службы, как если бы они были, по крайней мере, в некоторой степени, доступны локально.

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

2. Определение услуги

Предположим, нам нужно реализовать систему, которая позволяет пользователям заказывать поездку в такси.

Также предположим, что мы решили построитьtwo distinct applications для достижения этой цели:

  • приложение системы бронирования, чтобы проверить, можно ли подать запрос на такси, и

  • интерфейсное веб-приложение, которое позволяет клиентам бронировать поездки, что подтверждает наличие кабины

2.1. Сервисный интерфейс

Когда мы используемSpring Remoting сHTTP invoker,, мы должны определить нашу удаленно вызываемую службу через интерфейс, чтобы позволить Spring создавать прокси на стороне клиента и сервера, которые инкапсулируют технические детали удаленного вызова. Итак, начнем с интерфейса сервиса, который позволяет нам бронировать такси:

public interface CabBookingService {
    Booking bookRide(String pickUpLocation) throws BookingException;
}

Когда служба может выделить кабину, она возвращает объектBooking с кодом бронирования. Booking должен быть сериализуемым, потому что HTTP-инициатор Spring должен передать свои экземпляры с сервера клиенту:

public class Booking implements Serializable {
    private String bookingCode;

    @Override public String toString() {
        return format("Ride confirmed: code '%s'.", bookingCode);
    }

    // standard getters/setters and a constructor
}

Если служба не может забронировать такси, выдаетсяBookingException. В этом случае нет необходимости отмечать класс какSerializable, потому чтоException уже реализует его:

public class BookingException extends Exception {
    public BookingException(String message) {
        super(message);
    }
}

2.2. Упаковка услуги

Интерфейс службы вместе со всеми настраиваемыми классами, используемыми в качестве аргументов, возвращаемых типов и исключений, должен быть доступен как в пути к классам клиента, так и сервера. Один из наиболее эффективных способов сделать это - упаковать их все в файл.jar, который впоследствии можно будет включить в качестве зависимости в файлыpom.xml сервера и клиента.

Таким образом, поместим весь код в специальный модуль Maven под названием «api»; в этом примере мы будем использовать следующие координаты Maven:

com.example
api
1.0-SNAPSHOT

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

Давайте создадим приложение механизма бронирования, чтобы предоставлять сервис с помощью Spring Boot.

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

Во-первых, вам нужно убедиться, что ваш проект использует Spring Boot:


    org.springframework.boot
    spring-boot-starter-parent
    1.4.3.RELEASE

Вы можете найти последнюю версию Spring Boothere. Затем нам нужен модуль веб-стартера:


    org.springframework.boot
    spring-boot-starter-web

И нам нужен модуль определения сервиса, который мы собрали на предыдущем шаге:


    com.example
    api
    1.0-SNAPSHOT

3.2. Внедрение сервиса

Сначала мы определяем класс, реализующий интерфейс службы:

public class CabBookingServiceImpl implements CabBookingService {

    @Override public Booking bookPickUp(String pickUpLocation) throws BookingException {
        if (random() < 0.3) throw new BookingException("Cab unavailable");
        return new Booking(randomUUID().toString());
    }
}

Давайте представим, что это вероятная реализация. Используя тест со случайным значением, мы сможем воспроизвести как успешные сценарии - когда доступная кабина была найдена и код бронирования был возвращен, так и неудачные сценарии - когда создается исключение BookingException, указывающее на отсутствие доступной кабины.

3.3. Разоблачение службы

Затем нам нужно определить приложение с bean-компонентом типаHttpInvokerServiceExporter в контексте. Он позаботится о раскрытии в веб-приложении точки входа HTTP, которая впоследствии будет вызываться клиентом:

@Configuration
@ComponentScan
@EnableAutoConfiguration
public class Server {

    @Bean(name = "/booking") HttpInvokerServiceExporter accountService() {
        HttpInvokerServiceExporter exporter = new HttpInvokerServiceExporter();
        exporter.setService( new CabBookingServiceImpl() );
        exporter.setServiceInterface( CabBookingService.class );
        return exporter;
    }

    public static void main(String[] args) {
        SpringApplication.run(Server.class, args);
    }
}

Стоит отметить, чтоHTTP invoker Spring использует имя bean-компонентаHttpInvokerServiceExporter в качестве относительного пути для URL-адреса конечной точки HTTP.

Теперь мы можем запустить серверное приложение и продолжать его работу, пока мы настраиваем клиентское приложение.

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

Теперь напишем клиентское приложение.

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

Мы будем использовать то же определение службы и ту же версию Spring Boot, что и на стороне сервера. Нам все еще нужна зависимость веб-стартера, но, поскольку нам не нужно автоматически запускать встроенный контейнер, мы можем исключить стартер Tomcat из зависимости:


    org.springframework.boot
    spring-boot-starter-web
    
        
            org.springframework.boot
            spring-boot-starter-tomcat
        
    

4.2. Реализация клиента

Реализуем клиента:

@Configuration
public class Client {

    @Bean
    public HttpInvokerProxyFactoryBean invoker() {
        HttpInvokerProxyFactoryBean invoker = new HttpInvokerProxyFactoryBean();
        invoker.setServiceUrl("http://localhost:8080/booking");
        invoker.setServiceInterface(CabBookingService.class);
        return invoker;
    }

    public static void main(String[] args) throws BookingException {
        CabBookingService service = SpringApplication
          .run(Client.class, args)
          .getBean(CabBookingService.class);
        out.println(service.bookRide("13 Seagate Blvd, Key Largo, FL 33037"));
    }
}

Аннотированный метод() вызывающего объекта@Bean создает экземплярHttpInvokerProxyFactoryBean. Нам нужно предоставить URL-адрес, на который удаленный сервер отвечает через методsetServiceUrl().

Подобно тому, что мы сделали для сервера, мы также должны предоставить интерфейс службы, которую мы хотим вызывать удаленно, с помощью методаsetServiceInterface().

HttpInvokerProxyFactoryBean реализуетFactoryBean Spring. FactoryBean определяется как bean-компонент, но контейнер Spring IoC будет внедрять создаваемый им объект, а не саму фабрику. Вы можете найти более подробную информацию оFactoryBean в нашемfactory bean article.

Методmain() загружает автономное приложение и получает экземплярCabBookingService из контекста. Под капотом этот объект представляет собой просто прокси, созданныйHttpInvokerProxyFactoryBean, который заботится обо всех технических деталях, связанных с выполнением удаленного вызова. Благодаря этому теперь мы можем легко использовать прокси, как если бы реализация службы была доступна локально.

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

5. Пусть покупатель будет бдителен

Когда мы работаем с технологиями, которые позволяют удаленные вызовы, есть некоторые подводные камни, о которых мы должны знать.

Мы всегда должны ожидать неожиданного, когда работаем с ненадежным ресурсом в сети.

Предположим, что клиент вызывает сервер, когда он не может быть достигнут - либо из-за сетевой проблемы, либо из-за того, что сервер не работает - тогда Spring Remoting подниметRemoteAccessException, то есть_ a _RuntimeException.

Компилятор не будет заставлять нас включать вызов в блок try-catch, но мы всегда должны подумать об этом, чтобы должным образом решать проблемы с сетью.

5.2. Объекты передаются по стоимости, а не по ссылке

Spring Remoting HTTP маршалирует аргументы метода и возвращаемые значения для передачи их по сети. Это означает, что сервер действует на основе копии предоставленного аргумента, а клиент действует на копии результата, созданного сервером.

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

5.3. Остерегайтесь мелкозернистых интерфейсов

Вызов метода через границы сети значительно медленнее, чем вызов его для объекта в том же процессе.

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

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

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

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

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