きり丸の技術日記

技術・エンジニアのイベント・資格等はこちらにまとめる予定です

SpringBootでRequest前後に処理を挟むインターセプタを実装する(テストコードもあり)

JavaでRequestの前後に処理を挟むことができるインターセプタを実装したので、メモをします。

実装方法は色んな書いてあるものの、テストまでは書いてないことが多いのでそこで差別化を図ります。

環境

  • Java
    • 15
  • SpringBoot
    • 2.4.1

ユースケース

  • APIの流量制御をしたい
  • Request/Responseを明示的にログに出力したい

インターセプタの実装

HandlerInterceptorAdapterを紹介している記事も多いですが、Spring Framework 5.3からDeperacatedになっています。ですので代わりに、インターフェースのAsyncHandlerInterceptorを実装します。

メソッド実行前に処理させたい場合はpreHandleメソッドをオーバーライドします。同様に、メソッド実行後に処理させたい場合はpostHandleを使用し、リクエスト処理完了後はafterCompletionを使用します。

メソッド名 実行されるタイミング
preHandle コントローラメソッドの実行前
postHandle コントローラメソッドの実行後
afterCompletion リクエスト処理が完了した後
public class MaintenanceInterceptor implements AsyncHandlerInterceptor {
  @Override
  public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
      throws Exception {
    // APIの実行前にハンドリングする。
    // booleanだけでなく、throw new Exceptionすることで、HttpStatus等の表現もできる
    return true;
  }

    @Override
  public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
                         @Nullable ModelAndView modelAndView) throws Exception {
  }

  @Override
  public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler,
                              @Nullable Exception ex) throws Exception {
  }
}

正常時のメッセージ。

17:36:37.478 [Test worker] DEBUG org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping - Mapped to jp.co.kelly.restapi.AnimalsRestController#hogeApi(Param)
17:36:37.482 [Test worker] INFO jp.co.kelly.restapi.interceptor.MaintenanceInterceptor - preHandle
17:36:37.770 [Test worker] DEBUG org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor - Using 'text/plain', given [*/*] and supported [text/plain, */*, application/json, application/*+json]
17:36:37.771 [Test worker] DEBUG org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor - Writing ["hogehoge"]
17:36:37.781 [Test worker] ERROR jp.co.kelly.restapi.interceptor.MaintenanceInterceptor - postHandle
17:36:37.782 [Test worker] ERROR jp.co.kelly.restapi.interceptor.MaintenanceInterceptor - afterCompletion

異常時のメッセージ。

17:38:15.507 [Test worker] DEBUG org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping - Mapped to jp.co.kelly.restapi.AnimalsRestController#hogeApi(Param)
17:38:15.514 [Test worker] INFO jp.co.kelly.restapi.interceptor.MaintenanceInterceptor - preHandle
17:38:15.525 [Test worker] DEBUG org.springframework.test.web.servlet.TestDispatcherServlet - Failed to complete request: java.lang.RuntimeException: Error

インターセプタのテスト

MockMvcBuildersを使用してテストします。

addInterceptorsメソッドで実装したインターセプタを追加することで、処理の確認ができます。

class MaintenanceInterceptorTests {
  private MockMvc mockMvc;
  private final String rootUrl = "/animals";

  @BeforeEach
  void setup() {
    mockMvc = MockMvcBuilders.standaloneSetup(new AnimalsRestController())
        .addInterceptors(new MaintenanceInterceptor())
        .build();
  }

  @Test
  void success() throws Exception {
    this.mockMvc.perform(get(rootUrl).param("piyo", "aiueo"));
  }

}

インターセプタの登録

インターセプタを実装とテストできましたが、実際のリクエスト時にはインターセプタは機能しません。明示的にインターセプタを登録する必要があります。

@Configurationアノテーションを付けたうえで、WebMvcConfigurerを実装したクラスを作成します。

addInterceptorsメソッドを継承し、InterceptorRegistryaddInterceptorメソッドで作成したインターセプタを登録します。

@Configuration
public class WebConfig implements WebMvcConfigurer {

  @Override
  public void addInterceptors(InterceptorRegistry registry) {
    registry.addInterceptor(new MaintenanceInterceptor());
  }
}

WebMvcTestを使用してインターセプタのテスト

WebMvcConfigurerが機能していることを確認するテストを書きます。

ただ、難しいことはありません。@WebMvcTestを使用すると、自動的にWebMvcConfigurerをDIしてくれます。

ですので、WebMvcTestを使用することで、インターセプタの動作確認をすることができます。

@WebMvcTest(value = AnimalsRestController.class)
class AnimalsRestControllerTests {
  @Autowired
  private MockMvc mockMvc;
  private final String rootUrl = "/animals";

  @Test
  void success() throws Exception {
    this.mockMvc.perform(get(rootUrl).param("piyo", "aiueo"))
        .andExpect(status().isOk())
        .andExpect(content().string("hogehoge"));
  }
}

WebMvcTestでインターセプタを使用しない

インターセプタの役割が重くなってくると、Controllerのテストをするたびにインターセプタのセットアップが大変になります。そうなってくると、コントローラーのテストをしたいのか、インターセプタのテストをしたいのかが分からなくなります。

その場合は、@WebMvcTestexcludeFiltersを使用することで、DI対象から除外してくれるようになります。

@WebMvcTest(value = AnimalsRestController.class,
    excludeFilters = @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, value = WebConfig.class)
)

ソースコード

終わりに

Interceptorの実装も色んなプロジェクトで実装しますが、地味に忘れるのでメモできて良かったです。

職場では容易にSpringBootのバージョンアップできないので、新しいバージョンではDeperecatedされていることを自宅で確認できたのは大きいです。


この記事がお役に立ちましたら、各種SNSでのシェアや、今後も情報発信しますのでフォローよろしくお願いします。

参考

teqspaces.com

f:id:nainaistar:20210306183100p:plain