[Spring] Interceptor

HOJUN·2024년 6월 16일

Backend - Spring

목록 보기
27/34

Filter를 이야기할 때 Request를 핸들링하기 전이라는 말을 했다.
interceptor는 핸들링하기 전 요청과 응답을 "낚아챈다"는 의미일 것이다.

Controller에서 핸들링하기 전에 데이터를 가공하거나 참조해서 어떤 작업을 하고자할 때 사용한다.

스프링에서 제공하고 있는 HandlerInterceptor interface를 구현하는 것으로 interceptor를 조작할 수 있다.

Exception Handling처럼 전역적인 작업이 가능하고 다양한 용도를 가지며 특정 handler를 지정할 수 있다.

preHandle()

Controller가 호출되기 전 실행된다.
요청을 가로채 사전 작업이 가능하다.
요청을 계속 전달하거나, 처리를 중단할 수 있다.

postHandle()

Controller의 호출 이후 호출되고 요청이 처리되고나서 후처리 작업을 수행할 수 있다.

afterCompletion()

view의 렌더링까지 마친 후 호출된다. 반환한 응답을 로깅하는 등의 작업이 가능하다.

Annotation 식별

우리는 OpenApi라는 어노테이션을 만들어서 해당 어노테이션을 달고 있는 요소만 controller에 전달하려고 한다.

@Target(value = {ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface OpenApi {}

OpenApi는 Method, Type인데, METHOD는 물론 메소드이고
TYPE은 class, interface, enum 등에 적용할 수 있도록 한다.

@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Autowired
    private OpenApiInterceptor openApiInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(openApiInterceptor)
                .addPathPatterns("/**"); //root 하위 모든 주소를 매핑함
    }
}

Config 파일을 만들어서 설정을 해야 하는데, 내가 만든 Interceptor를 어느 주소범위까지 적용할지 매핑할 수 있다.
addPathPatterns()를 통해서 어떤 요청단계까지 매핑할 수 있는지 직접 정할 수 있고, 물론 두 주소 이상도 정할 수 있다.

preHanle()을 살펴보자

@Component
public class OpenApiInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // true -> controller 전달, false -> 전달 x
        log.info("Pre Handle");

        var handlerMethod = (HandlerMethod) handler;

        var methodLevel = handlerMethod.getMethodAnnotation(OpenApi.class); //Method가 달았나
        if(methodLevel != null){
            log.info("Method Level");
            return true;
        }

        var classLevel = handlerMethod.getBeanType().getAnnotation(OpenApi.class); //Class가 달았나
        if(classLevel != null){
            log.info("Class Level");
            return true;
        }
        
        //해당 Annotation을 안 달았음
        log.info("Not a OpenApi : {}" , request.getRequestURI());
        return false;
    }
}

@OpenApi라는 어노테이션을 생성했으니 @OpenApi가 붙은 메소드나 클래스는 Cotroller로 전달할 수 있어야 한다.

handler에 담긴 특정 핸들러 데이터를 HandlerMethod로 캐스팅해서 특정 핸들러의 메타데이터를 얻을 수 있다.

메소드에 어노테이션이 달려있는가, Controller 클래스에 어노테이션이 달려있는가를 식별하기 위해서 해당 요청의 handler정보를 이용하면 알 수 있다.
만약 메소드에 어노테이션이 달려있으면 Method Level이라는 로그와 함께 요청이 처리되는 결과가 나올 것이고, 클래스에 달려있다면 Class Level이라는 로그가 찍힐 것이다.

위에서 정한 범위 내 어디에도 @OpenApi 어노테이션이 달려있지 않다면 Not a OpenApi 로그와, 어떤 요청단계에서 매핑이 중단됐는지와 함께 요청이 중단될 것이다.

@RestController
@RequestMapping("/api/user")
public class UserApiController {

    @OpenApi
    @PostMapping("")
    public UserRequest register(
            @RequestBody
            UserRequest userRequest
    ){
        log.info("{}", userRequest);
        return userRequest;
    }

    @GetMapping("/hello")
    public void hello(){
        log.info("hello");
    }

register메소드에 @OpenApi가 달려있다. Post요청이 들어오면 정상적으로 응답이 내려올 것이고,
Get요청으로 /hello를 매핑하면 응답이 내려가지 않을 것이다.


Pre Handle 로그이후와 Controller의 filter 이전 Method Level 로그가 찍힌 것을 볼 수 있고, 응답도 정상적으로 내려갔다.


Get요청을 보내보면 Pre Handle 이후 Post Handle과 After Completion 로그도 없으며, 응답도 내려가지 않았다.

0개의 댓글