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
メソッドを継承し、InterceptorRegistry
にaddInterceptor
メソッドで作成したインターセプタを登録します。
@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のテストをするたびにインターセプタのセットアップが大変になります。そうなってくると、コントローラーのテストをしたいのか、インターセプタのテストをしたいのかが分からなくなります。
その場合は、@WebMvcTest
のexcludeFilters
を使用することで、DI対象から除外してくれるようになります。
@WebMvcTest(value = AnimalsRestController.class, excludeFilters = @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, value = WebConfig.class) )
ソースコード
- インターセプタ
- WebMvcConfigurerの継承
終わりに
Interceptorの実装も色んなプロジェクトで実装しますが、地味に忘れるのでメモできて良かったです。
職場では容易にSpringBootのバージョンアップできないので、新しいバージョンではDeperecatedされていることを自宅で確認できたのは大きいです。
この記事がお役に立ちましたら、各種SNSでのシェアや、今後も情報発信しますのでフォローよろしくお願いします。
参考