1. 概要

場合によっては、システムをいくつかのプロセスに分解する必要があり、それぞれがアプリケーションのさまざまな側面に責任を負います。 これらのシナリオでは、プロセスの1つが別のプロセスから同期的にデータを取得する必要があることは珍しくありません。

Spring Frameworkは、 Spring Remoting と包括的に呼ばれるさまざまなツールを提供します。これにより、リモートサービスを、少なくともある程度はローカルで利用できるかのように呼び出すことができます。

この記事では、Springの HTTP呼び出し側に基づくアプリケーションをセットアップします。これは、ネイティブJavaシリアル化とHTTPを利用して、クライアントとサーバーアプリケーション間のリモートメソッド呼び出しを提供します。

2. サービス定義

ユーザーがタクシーの乗車を予約できるシステムを実装する必要があるとしましょう。

また、この目標を達成するために、2つの異なるアプリケーションを構築することを選択したとします。

  • タクシーのリクエストに対応できるかどうかを確認するための予約エンジンアプリケーション、および
  • 顧客が乗車を予約できるフロントエンドのWebアプリケーションで、タクシーの空き状況が確認されていることを確認します

2.1. サービスインターフェース

Spring RemotingHTTP呼び出し側で使用する場合、リモートの技術をカプセル化するプロキシをクライアント側とサーバー側の両方で作成できるように、インターフェイスを介してリモートで呼び出し可能なサービスを定義する必要があります電話。 それでは、タクシーを予約できるサービスのインターフェースから始めましょう。

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

サービスがタクシーを割り当てることができる場合、予約コードを含むBookingオブジェクトを返します。 SpringのHTTP呼び出し側はインスタンスをサーバーからクライアントに転送する必要があるため、Bookingはシリアル化可能である必要があります。

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がスローされます。 この場合、 Exception はすでにクラスを実装しているため、クラスをSerializableとしてマークする必要はありません。

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

2.2. サービスのパッケージ化

サービスインターフェイスは、引数、リターンタイプ、および例外として使用されるすべてのカスタムクラスとともに、クライアントとサーバーの両方のクラスパスで使用できる必要があります。 これを行う最も効果的な方法の1つは、それらすべてを .jar ファイルにパックすることです。このファイルは、後でサーバーとクライアントのpom.xmlに依存関係として含めることができます。

したがって、すべてのコードを「api」と呼ばれる専用のMavenモジュールに入れましょう。 この例では、次のMaven座標を使用します。

<groupId>com.baeldung</groupId>
<artifactId>api</artifactId>
<version>1.0-SNAPSHOT</version>

3. サーバーアプリケーション

Spring Bootを使用してサービスを公開する予約エンジンアプリケーションを構築しましょう。

3.1. Mavenの依存関係

まず、プロジェクトでSpringBootが使用されていることを確認する必要があります。

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.6.1</version>
</parent>

最新のSpringBootバージョンはここにあります。 次に、Webスターターモジュールが必要です。

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

そして、前のステップで組み立てたサービス定義モジュールが必要です。

<dependency>
    <groupId>com.baeldung</groupId>
    <artifactId>api</artifactId>
    <version>1.0-SNAPSHOT</version>
</dependency>

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. サービスの公開

次に、コンテキスト内でタイプHttpInvokerServiceExporterのBeanを使用してアプリケーションを定義する必要があります。 後でクライアントによって呼び出されるWebアプリケーションの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);
    }
}

SpringのHTTP呼び出し側は、HTTPエンドポイントURLの相対パスとしてHttpInvokerServiceExporterBeanの名前を使用することに注意してください。

これで、サーバーアプリケーションを起動し、クライアントアプリケーションをセットアップしている間も実行を続けることができます。

4. クライアントアプリケーション

それでは、クライアントアプリケーションを作成しましょう。

4.1. Mavenの依存関係

サーバー側で使用したものと同じサービス定義と同じSpring Bootバージョンを使用します。 Webスターターの依存関係は引き続き必要ですが、埋め込みコンテナーを自動的に開始する必要がないため、Tomcatスターターを依存関係から除外できます。

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <exclusions>
        <exclusion>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-tomcat</artifactId>
        </exclusion>
    </exclusions>
</dependency>

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のインスタンスを作成します。 setServiceUrl()メソッドを介してリモートサーバーが応答するURLを提供する必要があります。

サーバーに対して行ったのと同様に、 setServiceInterface()メソッドを介してリモートで呼び出すサービスのインターフェイスも提供する必要があります。

HttpInvokerProxyFactoryBean は、SpringのFactoryBeanを実装します。 FactoryBean はBeanとして定義されていますが、Spring IoCコンテナーは、ファクトリ自体ではなく、作成したオブジェクトを注入します。 FactoryBean の詳細については、ファクトリBeanの記事を参照してください。

main()メソッドは、スタンドアロンアプリケーションをブートストラップし、コンテキストからCabBookingServiceのインスタンスを取得します。 内部的には、このオブジェクトは HttpInvokerProxyFactoryBean によって作成されたプロキシであり、リモート呼び出しの実行に関連するすべての技術を処理します。 そのおかげで、サービスの実装がローカルで利用可能だった場合と同じように、プロキシを簡単に使用できるようになりました。

アプリケーションを複数回実行して複数のリモート呼び出しを実行し、タクシーが利用可能な場合と利用できない場合のクライアントの動作を確認してみましょう。

5. 買い手責任負担

リモート呼び出しを可能にするテクノロジーを使用する場合、注意が必要ないくつかの落とし穴があります。

5.1. ネットワーク関連の例外に注意してください

信頼性の低いリソースをネットワークとして使用する場合は、常に予期しない事態が発生する可能性があります。

ネットワークの問題またはサーバーがダウンしているために、クライアントがサーバーに到達できないときにサーバーを呼び出していると仮定すると、SpringRemotingはRemoteAccessExceptionを発生させます。これはRuntimeExceptionです。

コンパイラは、try-catchブロックに呼び出しを含めるように強制しませんが、ネットワークの問題を適切に管理するために、常にそれを行うことを検討する必要があります。

5.2. オブジェクトは参照ではなく値で転送されます

Spring Remoting HTTP は、メソッド引数と戻り値をマーシャリングして、ネットワーク上で送信します。 これは、サーバーが提供された引数のコピーに基づいて動作し、クライアントがサーバーによって作成された結果のコピーに基づいて動作することを意味します。

したがって、たとえば、結果のオブジェクトでメソッドを呼び出すと、クライアントとサーバーの間に共有オブジェクトがないため、サーバー側の同じオブジェクトのステータスが変更されることは期待できません。

5.3. きめの細かいインターフェースに注意してください

ネットワークの境界を越えてメソッドを呼び出すのは、同じプロセスのオブジェクトでメソッドを呼び出すよりも大幅に遅くなります。

このため、通常は、より面倒なインターフェイスを犠牲にしても、より少ない対話でビジネストランザクションを完了できる、より粗いインターフェイスでリモートで呼び出す必要のあるサービスを定義することをお勧めします。

6. 結論

この例では、SpringRemotingを使用してリモートプロセスを簡単に呼び出すことができることを確認しました。

このソリューションは、RESTやWebサービスなどの他の広く普及しているメカニズムよりも少しオープンではありませんが、すべてのコンポーネントがSpringで開発されているシナリオでは、実行可能ではるかに迅速な代替手段となります。

いつものように、ソースはGitHubにあります。