지난 포스팅에 이어, 이번 포스팅에서는 4) ~ 8)
까지의 내용을 정리한다.
👉 목차는 다음과 같다.
1) 서블릿 필터 - 소개
2) 서블릿 필터 - 요청 로그
3) 서블릿 필터 - 인증체크
4) 스프링 인터셉터 - 소개
5) 스프링 인터셉터 - 요청 로그
6) 스프링 인터셉터 - 인증 체크
7) ArgumentResolver 활용
8) 정리
스프링 인터셉터도 서블릿 필터와 같이 웹과 관련된 공통 관심 사항을 효과적으로 해결할 수 있는 기술이다. 서블릿 필터가 서블릿이 제공하는 기술이라면, 스프링 인터셉터는 스프링 MVC가 제공하는 기술이다. 둘다 웹과 관련된 공통 관심 사항을 처리하지만, 적용되는 순서와 범위, 그리고 사용 방법이 다르다.
스프링 인터셉터 흐름
스프링 인터셉터 제한
인터셉터에서 적절하지 않은 요청이라고 판단하면 거기에서 끝을 낼 수도 있다. 그래서 로그인 여부를 체크하기에 딱 좋다.
스프링 인터셉터 체인
지금까지 내용을 보면 서블릿 필터와 호출되는 순서만 다르고, 제공하는 기능은 비슷해 보인다.
앞으로 설명하겠지만, 스프링 인터셉터는 서블릿 필터보다 편리하고, 더 정교하고 다양한 기능을 지원한다.
스프링 인터셉터 인터페이스
HandlerInterceptor
인터페이스를 구현하면 된다.doFilter()
하나만 제공된다. 인터셉터는 컨트롤러 호출 전( preHandle
), 호출 후( postHandle
), 요청 완료 이후( afterCompletion
)와 같이 단계적으로 잘 세분화 되어 있다.request
, response
만 제공했지만, 인터셉터는 어떤 컨트롤러( handler
)가 호출되는지 호출 정보도 받을 수 있다. 그리고 어떤 modelAndView
가 반환되는지 응답 정보도 받을 수 있다.스프링 인터셉터 호출 흐름
preHandle
: 컨트롤러 호출 전에 호출된다. (더 정확히는 핸들러 어댑터 호출 전에 호출된다.)preHandle
의 응답값이 true
이면 다음으로 진행하고, false
이면 더는 진행하지 않는다. false
인 경우 나머지 인터셉터는 물론이고, 핸들러 어댑터도 호출되지 않는다. 그림에서 1번에서 끝이 나버린다.postHandle
: 컨트롤러 호출 후에 호출된다. (더 정확히는 핸들러 어댑터 호출 후에 호출된다.)afterCompletion
: 뷰가 렌더링 된 이후에 호출된다.스프링 인터셉터 예외 상황
preHandle
: 컨트롤러 호출 전에 호출된다.postHandle
: 컨트롤러에서 예외가 발생하면 postHandle
은 호출되지 않는다afterCompletion
: afterCompletion
은 항상 호출된다. 이 경우 예외(ex
)를 파라미터로 받아서 어떤 예외가 발생했는지 로그로 출력할 수 있다.postHandle()
는 호출되지 않으므로 예외와 무관하게 공통 처리를 하려면 afterCompletion()
을 사용해야 한다.afterCompletion()
에 예외 정보(ex
)가 포함된다.ex
) 가 null로 넘어가고, 예외가 발생했을 때만 예외 정보(ex
)가 넘어온다.
✔️ 정리
✔️ 참고
다음 내용에서는, 이전에 서블릿 필터를 통해서 요청 로그를 적용했던 것 처럼, 스프링 인터셉터를 사용해서 동일한 기능을 만들어보자.
👉 코드로 바로 확인해보자.
HandlerInterceptor
인터페이스를 구현해야한다.LogInterceptor - preHandle()
: 아래와 같이 적용하자.LogInterceptor - postHandle()
: 아래와 같이 적용하자.LogInterceptor - afterCompletion()
: 아래와 같이 적용하자.
로직 분석 - preHandle
String uuid = UUID.randomUUID().toString()
request.setAttribute(LOG_ID, uuid)
preHandle
에서 지정한 값을 postHandle
, afterCompletion
에서 함께 사용하려면 어딘가에 담아두어야 한다. LogInterceptor
도 싱글톤 처럼 사용되기 때문에 맴버변수를 사용하면 위험하다. 따라서 request
에 담아두었다. 이 값은 afterCompletion
에서 request.getAttribute(LOG_ID)
로 찾아서 사용한다.return true
true
면 정상 호출이다. 다음 인터셉터나 컨트롤러가 호출된다.@Controller
, @RequestMapping
을 활용한 핸들러 매핑을 사용하는데, 이 경우 핸들러 정보로 HandlerMethod
가 넘어온다.@Controller
가 아니라 /resources/static
와 같은 정적 리소스가 호출 되는 경우 ResourceHttpRequestHandler
가 핸들러 정보로 넘어온다.
로직 분석 - postHandle, afterCompletion
postHandle
이 아니라 afterCompletion
에서 실행한 이유는, 예외가 발생한 경우 postHandle
가 호출되지 않기 때문이다. afterCompletion
은 예외가 발생해도 호출되는 것을 보장한다.
👉 이제 인터셉터를 등록 후 실행해보자.
WebMvcConfigurer
가 제공하는 addInterceptors()
를 사용해서 인터셉터를 등록할 수 있다.registry.addInterceptor(new LogInterceptor())
: 인터셉터를 등록한다.order(1)
: 인터셉터의 호출 순서를 지정한다. 낮을 수록 먼저 호출된다.addPathPatterns("/**")
: 인터셉터를 적용할 URL 패턴을 지정한다.excludePathPatterns("/css/**", "/*.ico", "/error")
: 인터셉터에서 제외할 패턴을 지정한다.addPathPatterns
, excludePathPatterns
로 매우 정밀하게 URL 패턴을 지정할 수 있다.logFilter()
의 @Bean
은 주석처리하자. 그리고 다시 실행해보자.
✔️ 참고
서블릿 필터에서 사용했던 인증 체크 기능을 스프링 인터셉터로 개발해보자.
👉 코드로 바로 적용해보자.
LoginCheckInterceptor - preHandle
: 다음과 같이 적용하자.preHandle
만 구현하면 된다.addPathPatterns
와 excludePathPatterns
에 작성하면 된다. (이 경로를 정밀하게 작성할 수 있다는 것은 정말 큰 장점이다.)/**
), 홈( /
), 회원가입( /members/add
), 로그인( /login
), 리소스 조회( /css/**
), 오류( /error
)와 같은 부분은 로그인 체크 인터셉터를 적용하지 않는다. 서블릿 필터와 비교해보면 매우 편리한 것을 알 수 있다./items
)를 요청할때는 LogInterceptor와 LoginCheckInterceptor가 정상적으로 동작하였다. (이때, preHandle에서 response.sendRedirect를 하고 false를 리턴하기 때문에, postHandle은 호출되지 않는다.)logFilter()
, loginCheckFilter()
의 @Bean
은 주석처리하였다.
✔️ 정리
서블릿 필터와 스프링 인터셉터는 웹과 관련된 공통 관심사를 해결하기 위한 기술이다.
서블릿 필터와 비교해서 스프링 인터셉터가 개발자 입장에서 훨씬 편리하다는 것을 코드로 이해했을 것이다. 특별한 문제가 없다면 인터셉터를 사용하는 것이 좋다.
스프링 MVC 1편 6. 스프링 MVC - 기본 기능 요청 매핑 헨들러 어뎁터 구조에서 ArgumentResolver 를 학습했다.
이번에는 해당 기능을 사용해서 로그인 회원을 조금 더 편리하게 찾아보자.
👉 코드로 바로 확인해보자.
@Target
을 넣어줘야 한다.@Target(ElementType.PARAMETER)
: 파라미터에만 사용@Retention(RetentionPolicy.RUNTIME)
: 리플렉션 등을 활용할 수 있도록 런타임까지 애노테이션 정보가 남아있음 (거의 사용할 때는 RUNTIME으로 사용한다.)homeLoginV3Spring()
의 @GetMapping("/")
을 주석 처리하자.homeLoginV3Spring()
메서드를 복사해서 homeLoginV3ArgumentResolver()
메서드를 생성하자.@Login
이 뭔지 인식을 못하기 때문에 ModelAtrribute가 동작한다. 따라서, 해당 애노테이션이 우리가 의도한 대로 동작하도록 적용해보자. (= 스프링 MVC 1편에서 학습한 HandlerMethodArgumentResolver 를 구현해보자.)supportsParameter()
: @Login
애노테이션이 있으면서 Member
타입이면 해당 ArgumentResolver
가 사용된다.resolveArgument()
: 컨트롤러 호출 직전에 호출되어서, 필요한 파라미터 정보를 생성해준다. 여기서는 세션에 있는 로그인 회원 정보인 member
객체를 찾아서 반환해준다. 이후 스프링MVC는 컨트롤러의 메서드를 호출하면서 여기에서 반환된 member
객체를 파라미터에 전달해준다.LoginMemberArgumentResolver
를 등록하자.@Login
애노테이션이 있으면 직접 만든 ArgumentResolver
가 동작해서 자동으로 세션에 있는 로그인 회원을 찾아주고, 만약 세션에 없다면 null
이 반환된다.)ArgumentResolver
를 활용하면 공통 작업이 필요할 때 컨트롤러를 더욱 편리하게 사용할 수 있다.강의를 듣고 정리한 글입니다. 코드와 그림 등의 출처는 김영한 강사님께 있습니다.