HTTP Invokerを使用したSpring Remotingの概要
1. 概要
場合によっては、システムをいくつかのプロセスに分解する必要があり、各プロセスはアプリケーションのさまざまな側面を担当します。 これらのシナリオでは、プロセスの1つが別のプロセスからデータを同期的に取得する必要があることは珍しくありません。
Spring Frameworkは、Spring Remotingと包括的に呼ばれる一連のツールを提供します。これにより、リモートサービスを、少なくともある程度はローカルで利用できるかのように呼び出すことができます。
この記事では、SpringのHTTP invokerに基づいてアプリケーションをセットアップします。これは、ネイティブJavaシリアル化とHTTPを利用して、クライアントとサーバーアプリケーション間のリモートメソッド呼び出しを提供します。
2. サービス定義
ユーザーがタクシーの乗車を予約できるシステムを実装する必要があるとしましょう。
また、この目標を達成するためにtwo distinct applicationsを構築することを選択したとしましょう。
-
タクシーリクエストを処理できるかどうかを確認する予約エンジンアプリケーション
-
顧客が乗車を予約できるようにするフロントエンドWebアプリケーション。タクシーの可用性が確認されていることを確認します。
2.1. サービスインターフェース
Spring RemotingをHTTP invoker,とともに使用する場合、リモート呼び出しの技術をカプセル化するプロキシをクライアント側とサーバー側の両方でSpringに作成させるために、インターフェイスを介してリモート呼び出し可能なサービスを定義する必要があります。 それでは、タクシーを予約できるサービスのインターフェースから始めましょう。
public interface CabBookingService {
Booking bookRide(String pickUpLocation) throws BookingException;
}
サービスがcabを割り当てることができる場合、予約コードを含む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座標を使用します。
com.example
api
1.0-SNAPSHOT
3. サーバーアプリケーション
SpringBootを使用してサービスを公開する予約エンジンアプリケーションを構築しましょう。
3.1. Mavenの依存関係
まず、プロジェクトでSpring Bootを使用していることを確認する必要があります。
org.springframework.boot
spring-boot-starter-parent
1.4.3.RELEASE
最後のSpringBootバージョンhereを見つけることができます。 次に、Webスターターモジュールが必要です。
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. サービスの公開
次に、コンテキスト内でタイプ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 invokerは、HTTPエンドポイントURLの相対パスとしてHttpInvokerServiceExporterBeanの名前を使用していることに注意してください。
これで、サーバーアプリケーションを起動して、クライアントアプリケーションのセットアップ中に実行を継続できます。
4. クライアントアプリケーション
それでは、クライアントアプリケーションを作成しましょう。
4.1. Mavenの依存関係
サーバー側で使用したものと同じサービス定義と同じSpringBootバージョンを使用します。 Webスターターの依存関係は引き続き必要ですが、埋め込みコンテナーを自動的に開始する必要がないため、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のインスタンスを作成します。 リモートサーバーがsetServiceUrl()メソッドを介して応答するURLを提供する必要があります。
サーバーに対して行ったのと同様に、setServiceInterface()メソッドを介してリモートで呼び出すサービスのインターフェイスも提供する必要があります。
HttpInvokerProxyFactoryBeanは、SpringのFactoryBeanを実装します。 FactoryBeanはBeanとして定義されていますが、Spring IoCコンテナーは、ファクトリ自体ではなく、作成したオブジェクトを注入します。 FactoryBeanの詳細については、factory bean articleをご覧ください。
main()メソッドは、スタンドアロンアプリケーションをブートストラップし、コンテキストからCabBookingServiceのインスタンスを取得します。 内部的には、このオブジェクトはHttpInvokerProxyFactoryBeanによって作成されたプロキシであり、リモート呼び出しの実行に関連するすべての技術を処理します。 そのおかげで、サービスの実装がローカルで利用可能だった場合と同じように、プロキシを簡単に使用できるようになりました。
アプリケーションを複数回実行して複数のリモート呼び出しを実行し、タクシーが利用可能な場合と利用できない場合のクライアントの動作を確認しましょう。
5. 買い手責任負担
リモート呼び出しを許可するテクノロジーを使用する場合、注意すべきいくつかの落とし穴があります。
5.1. ネットワーク関連の例外に注意してください
信頼性の低いリソースをネットワークとして使用する場合、予期しないことを常に予想する必要があります。
ネットワークの問題またはサーバーがダウンしているために、クライアントがサーバーに到達できないときにサーバーを呼び出していると仮定すると、Spring Remotingは_ a _RuntimeException.であるRemoteAccessExceptionを発生させます。
コンパイラーは、try-catchブロックに呼び出しを強制することはありませんが、ネットワークの問題を適切に管理するために、常に実行することを検討する必要があります。
5.2. オブジェクトは参照ではなく値で転送されます
Spring Remoting HTTPは、メソッド引数と戻り値をマーシャリングして、ネットワーク上で送信します。 これは、サーバーが提供された引数のコピーに基づいて動作し、クライアントがサーバーによって作成された結果のコピーに基づいて動作することを意味します。
そのため、たとえば、クライアントとサーバーの間に共有オブジェクトがないため、結果のオブジェクトでメソッドを呼び出すと、サーバー側の同じオブジェクトのステータスが変更されることを期待できません。
5.3. きめ細かいインターフェースに注意してください
ネットワーク境界を越えてメソッドを呼び出すと、同じプロセス内のオブジェクトでメソッドを呼び出すよりも大幅に遅くなります。
このため、通常は、より煩雑なインターフェイスを犠牲にして、より少ない対話でビジネストランザクションを完了することができる、より粗いインターフェイスでリモートで呼び出すサービスを定義することをお勧めします。
6. 結論
この例では、Spring Remotingを使用してリモートプロセスを簡単に起動できることがわかりました。
このソリューションは、RESTやWebサービスなどの他の広範なメカニズムよりもオープン性がやや劣りますが、すべてのコンポーネントがSpringで開発されているシナリオでは、実行可能なはるかに高速な代替手段となります。
いつものように、ソースover on GitHubが見つかります。