본문 바로가기
공부/Spring

[Spring][인프런 스프링 MVC] 필터와 인터셉터의 차이

by 웅대 2023. 3. 12.
728x90
반응형

본 포스팅은 김영한 강사님의 인프런 강의 "스프링 MVC 1편"을 정리한 포스팅입니다.

https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-mvc-1/dashboard

 

스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술 - 인프런 | 강의

웹 애플리케이션을 개발할 때 필요한 모든 웹 기술을 기초부터 이해하고, 완성할 수 있습니다. 스프링 MVC의 핵심 원리와 구조를 이해하고, 더 깊이있는 백엔드 개발자로 성장할 수 있습니다., -

www.inflearn.com

필터와 인터셉터는 주로 웹에 관련된 공통 관심사를 처리할 때 사용된다.

 

예를 들어 서버에 요청이 들어올 때마다 요청과 관련된 로그를 출력하도록 한다거나 특정 요청은 인증된 사용자만 허용해야할 때 각각의 요청마다 로직을 작성해도 되겠지만 필터나 인터셉터를 사용하면 이를 공통적으로 처리할 수 있다.

 

필터와 인터셉터는 다음과 같은 차이점이 있다.

종류 필터 인터셉터
지원 주체 서블릿 스프링 
호출 시점 서블릿 호출 이전 서블릿 호출 이후
체인 여부 (여러개 연결 가능) O O
     

인터셉터는 스프링이 제공하기 때문에 특별한 이유가 없는 이상 필터 보다는 인터셉터를 사용하는 편이 좋다고 한다.

 

필터와 인터셉터를 사용하여 요청이 들어올 때마다 요청 url을 남기는 코드를 작성해보려 한다.

 

필터

우선 요청을 처리할 컨트롤러부터 생성한다.

@RestController
public class LogController {
    @GetMapping("/log")
    public String log() {
        return "ok";
    }
    @GetMapping("/except")
    public String except() {
        return "ok";
    }
    @GetMapping("/no-filter")
    public String noFilter() {
        return "ok";
    }
}

필터를 테스트하기 위함이므로 ok를 반환하는 컨트롤러를 만들었다.

 

그 다음 필터를 사용하려면 Filter 인터페이스를 구현해야 한다.

@Slf4j
public class LogFilter implements Filter {
    private static final String[] whiteList = {"/except", "no-filter"}; //filter를 적용하지 않을 uri
    @Override
    public void init(FilterConfig filterConfig) throws ServletException { //서블릿 컨테이너 생성시 호출
        log.info("init method");
    }

    @Override
    public void doFilter(ServletRequest request,
                         ServletResponse response,
                         FilterChain chain) throws IOException, ServletException { //요청이 올 때마다 호출
        HttpServletRequest httpRequest = (HttpServletRequest) request;
        String uri = httpRequest.getRequestURI();
        try {
            if (!PatternMatchUtils.simpleMatch(whiteList, uri)) { //filter를 적용해야하는 uri면
                log.info("Request URI : {}", uri);

            }
            chain.doFilter(request, response);
        } catch (Exception e) {
            throw e;
        } finally{
            log.info("Response : {}", uri);
        }
    }

    @Override
    public void destroy() {//서블릿 컨테이너 종료될 때 호출
        log.info("destroy method");
    }
}
 

init 메소드 : 서블릿 컨테이너가 생성될 때 실행

doFilter 메소드 : 요청이 들어올 때 실행

destroy 메소드 : 서블릿 컨테이너가 종료될 때 실행

 

필터의 핵심 로직은 doFilter에 적용하면 된다.

 

ServletRequest가 존재하는데 URI를 가져오기 위해 이를 HttpServletRequest로 타입 캐스팅을 해준다.

 

필터를 적용하지 않을 uri는 whiteList에 보관해두었다가 이 uri가 아닐 경우만 로그를 출력한다.

 

또한 잘 보면 doFilter 메소드 안에 filter.doFilter()를 호출하는 것을 확인할 수 있다.

 

이는 만약 이 필터 다음에 실행되야 할 필터가 있다면 그 필터를 호출하고 아니라면 서블릿을 호출한다.

 

이 다음 이 필터를 등록할 차례이다.

 

@Configuration
public class WebConfig {
    @Bean
    public FilterRegistrationBean logFilter() {
        FilterRegistrationBean<Filter> filterRegistrationBean = new
                FilterRegistrationBean<>();
        filterRegistrationBean.setFilter(new LogFilter()); //filter 등록
        filterRegistrationBean.setOrder(1); //순서. 낮을수록 우선수위 높음
        filterRegistrationBean.addUrlPatterns("/*"); //모든 요청에 대하여
        return filterRegistrationBean;
    }
}

FilterRegistrationBean을 등록해준다.

 

그리고 이제 서버를 켜고 아까 만들어둔 컨트롤러의 URI("/log")로 요청을 세 번 보내보았다.


2023-03-09T19:57:44.494+09:00  INFO 2752 --- [nio-8080-exec-2] c.e.f.filter.LogFilter                   : Request URI : /log
2023-03-09T19:57:44.545+09:00  INFO 2752 --- [nio-8080-exec-2] c.e.f.filter.LogFilter                   : Response : /log
2023-03-09T19:57:52.085+09:00  INFO 2752 --- [nio-8080-exec-1] c.e.f.filter.LogFilter                   : Request URI : /log
2023-03-09T19:57:52.091+09:00  INFO 2752 --- [nio-8080-exec-1] c.e.f.filter.LogFilter                   : Response : /log
2023-03-09T19:57:52.536+09:00  INFO 2752 --- [nio-8080-exec-3] c.e.f.filter.LogFilter                   : Request URI : /log
2023-03-09T19:57:52.539+09:00  INFO 2752 --- [nio-8080-exec-3] c.e.f.filter.LogFilter                   : Response : /log

 

잘 작동하는 모습을 확인할 수 있다.

 

whiteList에 등록한 uri로 요청을 보내면 Request 로그가 출력되지 않는 모습을 확인할 수 있다.

 

인터셉터

인터셉터는 필터보다 편리한 기능들을 제공한다.

 

요청을 처리할 컨트롤러를 간단하게 만들어준다.

@RestController
public class LogInterceptorController {
    @GetMapping("/log")
    public String log() {
        return "ok";
    }
    @GetMapping("/except")
    public String except() {
        return "ok";
    }
    @GetMapping("/no-interceptor")
    public String noInterceptor() {
        return "ok";
    }
}

인터셉터를 만드려먼 HandlerInterceptor 인터페이스를 구현해야 한다.

 

public class LogInterceptor implements HandlerInterceptor {
    //컨트롤러 호출 전
    @Override
    public boolean preHandle(HttpServletRequest request,
                             HttpServletResponse response,
                             Object handler) throws Exception {
        return HandlerInterceptor.super.preHandle(request, response, handler);
    }

    //컨트롤러 호출 후
    @Override
    public void postHandle(HttpServletRequest request,
                           HttpServletResponse response,
                           Object handler,
                           ModelAndView modelAndView) throws Exception {
        HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
    }

    //뷰 렌더링 이후
    @Override
    public void afterCompletion(HttpServletRequest request,
                                HttpServletResponse response,
                                Object handler, Exception ex) throws Exception {
        HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
    }
}

세 가지 메소드가 존재하고 필요한 메소드를 골라서 구현하면 된다.

 

<인터셉터 호출 흐름>

김영한 스프링 MVC 1편

컨트롤러에서 예외가 발생하면 postHandle은 호출되지 않는다.

 

우선 요청 URI 로그를 출력하는 인터셉터를 생성한다.

@Slf4j
public class LogInterceptor implements HandlerInterceptor {
    //컨트롤러 호출 전
    @Override
    public boolean preHandle(HttpServletRequest request,
                             HttpServletResponse response,
                             Object handler) throws Exception {
        String uri = request.getRequestURI();
        log.info("Request uri : {}", uri);
        return true;
    }

    //컨트롤러 호출 후
    @Override
    public void postHandle(HttpServletRequest request,
                           HttpServletResponse response,
                           Object handler,
                           ModelAndView modelAndView) throws Exception {
        String uri = request.getRequestURI();
        log.info("post handle : {}", uri);
    }

    //뷰 렌더링 이후
    @Override
    public void afterCompletion(HttpServletRequest request,
                                HttpServletResponse response,
                                Object handler, Exception ex) throws Exception {
        String uri = request.getRequestURI();
        log.info("Response : {}", uri);
    }
}

필터의 경우 요청이 들어왔을 때 doFilter 메소드 하나로 처리를 했는데 인터셉터는 위와 같이 컨트롤러 호출 전, 후, 뷰 렌더링 이후에 대한 로직을 각각 작성할 수 있는 장점이 있다.

 

이렇게 인터셉터를 만들고 나면 인터셉터를 등록해야 한다.

@Configuration
public class WebConfig implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new LogInterceptor())
                .order(1)
                .addPathPatterns("/log")
                .excludePathPatterns("/no-interceptor", "except");
    }
}

필터와 마찬가지로 순서, 인터셉터를 적용하지 않을 URI들을 설정할 수 있다.

서버를 켜고 요청을 넣어보면 addPathPatterns에 들어있는 URI에 대한 요청만 처리하는 모습을 확인할 수 있다.

728x90
반응형

댓글