外部サービスとの API ⇔ API な連携処理を実現する案件を担当することになりまして、
タイトルについて、いろいろと調査/実装したことをメモしておきます。
環境
- Spring Framework 4.2.4.RELEASE
- Spring Boot 1.3.2.RELEASE
要件
外部サービス側の担当者に以下のことを言われました。
- 必ず 200 OK を返して欲しい。
200 OK 以外を受け取ると、監視に引っかかって運用コストが生じるのでヤダ
上記を受けて、こちら側の API を設計していたときに考えたのは以下です。
- リクエストを受け取ったらさっさと 200 OK を返す
- こちらが実装したい内部処理は 200 OK を返した後の処理として実装する
上記のようにしてしまえば、まず外部サービス側の要求には合うかなと。
こちらとしても、先に返すもの返してしまった方が気楽なので。
コードイメージは以下です。
@RestController
public class SampleController() {
// 外部サービスからのリクエスト受付
@RequestMapping(value = "/sample")
public String sample(@Validated SampleForm form) {
// レスポンスを返すための最低限の処理
return "";
}
// 後処理(あくまでイメージ:この記事を読んでいくとこんな実装にはならない)
private void after() {
// レスポンスを返す以外に必要な処理
}
}
実現方法
調査したところ、HandlerInterceptor#afterCompletion()
によって実現できそうです。
以下の記事を参考にしました。
Spring MVC(+Spring Boot)上でのリクエスト共通処理の実装方法を理解する
HandlerInterceptor
は後処理以外の用途に向けたメソッドも持つインタフェースですが、
今回は後処理のみ個別に実装したいため、
HandlerInterceptor
の実装クラスである HandlerInterceptorAdapter
を継承したクラスを用意します。
また、後処理という観点であれば HandlerInterceptor#postHandle()
も候補に入りますが、
こちらは例外発生時には呼び出されないため、我々の要件に合わないことから、除外しました。
HandlerInterceptor#afterCompletion()
は正常終了しようが例外が発生しようが呼び出されます。
public class SampleInterceptor extends HandlerInterceptorAdapter {
// 正常終了時/異常終了時を問わず後処理のみ実装したいため afterCompletion() のみオーバーライド
@override
public void afterCompletion(HttpServletRequest request,
HttpServletResponse response, Object handler, Exception ex) throws Exception {
// 後処理の実装
}
}
次に、我々の要件では、上記コードイメージの /sample
に対するリクエストだけに後処理を適用したいです。
これについては、上記の記事にもあります以下の実装で対応できました。
@Configuration
public class WebMvcConfig extends WebMvcConfigurerAdapter {
// どうも Bean 登録しないといけない
@Bean
public SampleInterceptor sampleInterceptor() {
return new SampleInterceptor();
}
@Bean
public HogeInterceptor hogeInterceptor() {
return new HogeInterceptor();
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(sampleInterceptor())
.addPathPatterns("/sample"); // ここで対象を指定している
// 複数の Interceptor を指定したければ並べて書く。チェインして複数パスの指定も OK
registry.addInterceptor(hogeInterceptor())
.addPathPatterns("/hoge").addPathPatterns("/huga")
// 異なる Interceptor で同じパスを指定すると、両方の afterCompletion() が実行される
// 実行順序がどう決められるかは未調査
.addPathPatterns("/sample");
}
}
以上で求める処理を実現できました。
addPathPatterns
で対象を指定しない場合は、すべての Controller が後処理の対象になるようです。
実際のコードは上記のサンプルコードだけではなく、もう少しいろいろあるのですが、
書き留めておきたいことはこれぐらいで。