この記事は
Intercepter
を使用して、コントローラーの処理の前後に任意の処理を実行する方法を書く。
なお、REST APIを想定して書いている。
GETメソッドの作成
以下のような単純なエンドポイントを用意する。
import org.springframework.http.ResponseEntity import org.springframework.web.bind.annotation.GetMapping import org.springframework.web.bind.annotation.RestController @RestController class HelloController { @GetMapping("hello") fun hello(): ResponseEntity<HelloMessage> { return ResponseEntity.ok(HelloMessage("hello!")) } data class HelloMessage(val message: String) }
/hello
に対してリクエストを実行すると、{"message": "hello!"}
が返ってくるだけの単純な処理である。
この処理の前後にログを出力するようなインターセプターを実装する。
インターセプターの実装
インターセプターの実装例は以下のようなものである。
import jakarta.servlet.http.HttpServletRequest import jakarta.servlet.http.HttpServletResponse import org.slf4j.Logger import org.springframework.stereotype.Component import org.springframework.web.servlet.HandlerInterceptor import org.springframework.web.servlet.ModelAndView import java.lang.Exception @Component class LoggingInterceptor( private val logger: Logger ) : HandlerInterceptor { // コントローラーの実行前の処理を定義する override fun preHandle(request: HttpServletRequest, response: HttpServletResponse, handler: Any): Boolean { logger.info("----------------preHandle") return super.preHandle(request, response, handler) } // コントローラーの実行後の処理を定義する。より正確には、レスポンス送信前の処理 override fun postHandle( request: HttpServletRequest, response: HttpServletResponse, handler: Any, modelAndView: ModelAndView? ) { logger.info("----------------postHandle") super.postHandle(request, response, handler, modelAndView) } // レスポンス送信後の処理 override fun afterCompletion( request: HttpServletRequest, response: HttpServletResponse, handler: Any, ex: Exception? ) { logger.info("----------------afterHandle") super.afterCompletion(request, response, handler, ex) } }
ロギングコンポーネント
上述したLoggingInterceptor
にインジェクションするLogger
Beanを用意する。以下のコンフィグクラスを作成することで、Logger
を利用しやすくなる。詳細についてはこちらを参照。
import org.slf4j.Logger import org.slf4j.LoggerFactory import org.springframework.beans.factory.InjectionPoint import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Configuration import org.springframework.context.annotation.Scope @Configuration class LoggerConfig { @Bean @Scope("prototype") fun logger(injectionPoint: InjectionPoint): Logger { val targetClass = injectionPoint.targetClass ?: throw IllegalArgumentException("couldn't obtain a target class for logger from $injectionPoint") return LoggerFactory.getLogger(targetClass) } private val InjectionPoint.targetClass: Class<*>? get() = methodParameter?.containingClass ?: field?.declaringClass }
また、インターセプターを有効化するためにWebMvcConfigurer
を継承したクラスに作成したインターセプターを登録するようなコードも必要となる。
import com.example.demo.controller.LoggingInterceptor import org.springframework.context.annotation.Configuration import org.springframework.web.servlet.config.annotation.InterceptorRegistry import org.springframework.web.servlet.config.annotation.WebMvcConfigurer @Configuration class WebMvcConfig( private val interceptor: LoggingInterceptor ) : WebMvcConfigurer { override fun addInterceptors(registry: InterceptorRegistry) { registry.addInterceptor(interceptor) // 他に登録したいインターセプターがある場合は同様に書いていく // registry.addInterceptor(interceptor2) // registry.addInterceptor(interceptor3) } }
ここまでできれば先ほどと同様に/hello
に対してリクエストを投げてみると、以下のようなログが出力されるようになるはず。
2023-02-07T12:05:28.568+09:00 INFO 258 --- [nio-8080-exec-1] c.e.demo.controller.LoggingInterceptor : ----------------preHandle 2023-02-07T12:05:28.610+09:00 INFO 258 --- [nio-8080-exec-1] c.e.demo.controller.LoggingInterceptor : ----------------postHandle 2023-02-07T12:05:28.610+09:00 INFO 258 --- [nio-8080-exec-1] c.e.demo.controller.LoggingInterceptor : ----------------afterHandle
特定のアノテーションが付与されているメソッドのみに対する処理
続いて、特定のアノテーションが付与されているメソッドのみ実行する処理を書いてみる。
今回は@Hello
アノテーションを作成し、このアノテーションが付与されているメソッドが実行された場合にログにhello! ${EXECUTED_METHOD}
を出力してみようと思う。
まずはアノテーションから
@Target(AnnotationTarget.FUNCTION) @Retention(AnnotationRetention.RUNTIME) annotation class Hello { }
続いて、インターセプター
import jakarta.servlet.http.HttpServletRequest import jakarta.servlet.http.HttpServletResponse import org.slf4j.Logger import org.springframework.core.annotation.AnnotationUtils import org.springframework.stereotype.Component import org.springframework.web.method.HandlerMethod import org.springframework.web.servlet.HandlerInterceptor @Component class HelloInterceptor( private val logger: Logger ) : HandlerInterceptor { override fun preHandle(request: HttpServletRequest, response: HttpServletResponse, handler: Any): Boolean { // HandlerMethodを取得し、実行したメソッドの名前・付与されているアノテーション情報を取得する val handlerMethod = handler as HandlerMethod val method = handlerMethod.method val methodName = method.name val helloAnnotation = AnnotationUtils.findAnnotation(method, Hello::class.java) helloAnnotation?.let { logger.info("hello!, $methodName") } return super.preHandle(request, response, handler) } }
インターセプターの登録も忘れずに
@Configuration class WebMvcConfig( private val interceptor: LoggingInterceptor, private val helloInterceptor: HelloInterceptor ) : WebMvcConfigurer { override fun addInterceptors(registry: InterceptorRegistry) { registry.addInterceptor(interceptor) // 追加 registry.addInterceptor(helloInterceptor) } }
上記までで準備が済んでいるので、/hello
を実行してみると・・・
2023-02-07T12:44:32.165+09:00 INFO 2556 --- [nio-8080-exec-5] c.e.demo.controller.LoggingInterceptor : ----------------preHandle 2023-02-07T12:44:32.166+09:00 INFO 2556 --- [nio-8080-exec-5] c.e.demo.controller.HelloInterceptor : hello!, hello 2023-02-07T12:44:32.170+09:00 INFO 2556 --- [nio-8080-exec-5] c.e.demo.controller.LoggingInterceptor : ----------------postHandle 2023-02-07T12:44:32.170+09:00 INFO 2556 --- [nio-8080-exec-5] c.e.demo.controller.LoggingInterceptor : ----------------afterHandle
上記のようなログが出力される。それぞれ、LoggingInterceptor
とHelloInterceptor
で定義したログが出力されていることがわかる。
余談:preHandleでfalseを返すとどうなるか
ちょっと気になったので試してみる。
インターセプターでoverride
したpreHandle
メソッドについて、常にfalse
を返却するように修正する。
override fun preHandle(request: HttpServletRequest, response: HttpServletResponse, handler: Any): Boolean { logger.info("----------------preHandle") // 常にfalseを返すとどうなる??? return false }
↓↓↓↓実行結果のログ
2023-02-07T14:15:14.397+09:00 INFO 6835 --- [nio-8080-exec-2] c.e.demo.controller.LoggingInterceptor : ----------------preHandle
上記の通り、preHandle
の処理で実行された.logger.info
のみが実行され、postHandle
およびafterCompletion
が実行されていない(ログが出力されていない)ことがわかる。
終わりに
今回はいずれも単純な例だったが、インターセプターを使用することで各コントローラーに対して共通した処理を実装することができる。そのため、認証やロギングなどに用いられることが多い(と思う)。
参考
SpringBootの特定のAnnotationが付与されたControllerのメソッドに対して事前処理を行う - Qiita