kikki's tech note

技術ブログです。UnityやSpine、MS、Javaなど技術色々について解説しています。

Spring Bootでコントローラーの処理前後でインターセプトさせる

本章では、Spring Bootで特定のコントローラーに対して、共通の処理を行う場合の方法について共有します。

インターセプターの役割

WEB開発をしていて、クライアントからのリクエストに対する処理を共通化したい場合があるかと思います。例えば、ログの書き出し、二重POSTの制御、セッション情報の検証など。そのような場面で、リクエストを受け取る個別のハンドラすべてに処理を記述していては開発コストがかかり、後のリファクタリング作業にも影響を及ぼしかねません。そこでSpringで、コントローラーの前後で特定の処理を挟み込む方法について紹介します。

実装

インターセプター

まず、インターセプトの処理の記述方法を伝えます。Springでは、コントローラーの実行前、ビューのレンダリング前、ビューのレンダリング後に処理を挟み込めます。

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.resource.ResourceHttpRequestHandler;

@Component
public class HogeHogeIntercepter implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request,
            HttpServletResponse response, Object handler) throws Exception {
        // コントローラー実行前に行いたい処理を記述する
        // 例えば、リクエストを受け付けたログの書き出し など

        if (handler instanceof ResourceHttpRequestHandler) {
            // staticファイルはインターセプトさせずにスルーさせる
            return true;
        }

        // 以下は、特定のコントローラーメソッドに実行したい場合に利用する
        if (AnnotationUtils.findAnnotation(
                ((HandlerMethod) handler).getMethod(),
                HogeHogeAnnotation.class) != null) {
            // Annotationがついている、特定のコントローラーメソッドでの手続き前に実行される

            // インターセプトの処理の中で、認証されたユーザー情報も取得できるので、ユーザー情報でHogeHogeできる
            Authentication authentication = SecurityContextHolder.getContext()
                    .getAuthentication();
            if (authentication == null) {
                return true;
            }
            HogeUserDetails hogeUser = (HogeUserDetails ) authentication
                    .getPrincipal();
        }
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request,
            HttpServletResponse response, Object handler, ModelAndView model)
            throws Exception {
        // コントローラーでの処理が完了し、ビューのレンダリング前に行いたい処理を記述する
    }

    @Override
    public void afterCompletion(HttpServletRequest request,
            HttpServletResponse response, Object handler, Exception exception)
            throws Exception {
        // ビューのレンダリング後に行いたい処理を記述する
    }
}

アノテーション

次に、特定のURIへのアクションに限定して、処理を挟みたい場合に利用するアノテーションを用意します。(なお、リクエストすべてに対して処理を行いたい場合には不要です。)

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface HogeHogeAnnotation{
}

インターセプターの登録

そして、Servlet起動時の設定情報にインターセプトを登録します。

import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.actuate.autoconfigure.ManagementWebSecurityAutoConfiguration;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.data.rest.RepositoryRestMvcAutoConfiguration;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.context.TypeExcludeFilter;
import org.springframework.boot.web.support.SpringBootServletInitializer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.ComponentScan.Filter;
import org.springframework.context.annotation.FilterType;
import org.springframework.data.domain.AuditorAware;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.servlet.handler.MappedInterceptor;

@ComponentScan(excludeFilters = {
        @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class)})
@SpringBootApplication(exclude = {RepositoryRestMvcAutoConfiguration.class,
        ManagementWebSecurityAutoConfiguration.class})
public class HogeServer extends SpringBootServletInitializer {
    @Autowired
    private HogeHogeIntercepter hogeIntercepter;

    // インターセプトを登録
    @Bean
    public MappedInterceptor accessInterceptor() {
        return new MappedInterceptor(new String[] {"/**"},
                hogeIntercepter);
    }
}

インターセプトさせたいリクエスト処理の指定

最後に、インターセプトさせたいコントローラーの特定のメソッドに、アノテーションを指定します。

import javax.servlet.http.HttpServletRequest;

import org.springframework.security.access.annotation.Secured;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;

import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import io.swagger.annotations.ApiResponse;
import io.swagger.annotations.ApiResponses;
import lombok.extern.slf4j.Slf4j;

@RestController
@Slf4j
public class HogeController {

    // インターセプトさせたいメソッドにアノテーションを指定する
    @HogeHogeAnnotation
    @Secured("GetHogeSecured")
    @RequestMapping(value = "/HogeHoge", method = RequestMethod.GET)
    @ResponseBody
    public JsonBase get(@AuthenticationPrincipal HogeUser user) {
    }
}

筆休め

昨今のWEB開発は、開発からリリースまで数ヶ月といった現場も少なくありません。そのような環境では、いかに効率よく、不具合が出ないように、開発できるかが重要になってきます。プロジェクトに対して、スリムかつ影響を局所的にできるような仕組みを取り入れていきたいですね。

以上、「Spring Bootでコントローラーの処理前後でインターセプトさせる」でした。


※無断転載禁止 Copyright (C) kikkisnrdec All Rights Reserved.