Interceptor를 검색하면 Filter와의 차이를 비교하는 내용이 첫 페이지에 나올 정도로 비슷하고 헷갈리는 것을 알 수 있다. 그래서 Interceptor의 설명에 들어가기 이전 그림을 먼저 보면 Filter와의 큰 차이를 한 눈에 알 수 있다. 바로 적용되는 위치이다. Filter는 Dispatcher Servlet 이전 그리고 Interceptor는 그 이후에 적용이 된다. 적용 위치가 다른 것은 알겠지만 그게 어떠한 차이를 나타내는 것인지 잘 와닿지 않는 것이 사실이기에 이번 포스팅에서 Interceptor에대해 확실하게 설명하고 실습한 코드를 포스팅하려고한다.
Interceptor는 디스패처 서블릿 안에 있으며 Spring Context에서 관리된다. Handler에 의한 유효성 검사로 Exception을 처리할 수 있다. Spring이 제공하는 기술로 가로챈다는 뜻 그대로 디스패처 서블릿(Dispatcher Servlet)이 컨트롤러를 호출하기 전과 후에 요청을 가로채서 응답을 참조하거나 가공할 수 있게 도와준다. 만약 1개 이상의 Interceptor가 등록되어 있으면 순차적으로 Interceptor들을 거쳐 컨트롤러가 실행되도록 하지만 등록되어 있지 않다면 바로 컨트롤러를 실행한다.
이러한 방식으로 Interceptor를 활용하여 얻을 수 있는 장점은 크게 아래 세 가지와 같다.
공통 코드 사용으로 코드 재사용성이 증가한다.
메모리 낭비 서버부하가 감소한다.
코드 누락에 대한 위험성이 감소한다.
preHandler()
preHandle 메소드는 컨트롤러가 호출되기 전에 실행되며 컨트롤러 이전에 처리해야 하는 전처리 작업이나 요청 정보를 가공하거나 추가하는 경우에 사용할 수 있다. HttpServletRequest, HttpServletResponse, Object handler 총 세 개의 파라미터를 사용한다.
preHandle의 반환 타입은 boolean로 반환 값이 true면 다음 단계로 진행이 되지만 false이면 작업을 중단하여 이후의 작업(다음 인터셉터 또는 컨트롤러)은 실행되지 않는다.
※ Object Handler
현재 실행하려는 메소드 자체를 의미하며 이를 활용하면 현재 실행되는 컨트롤러를 파악하거나 추가적인 메소드를 실행하는 등의 작업이 가능하다.
postHandler()
postHandle 메소드는 컨트롤러를 호출된 후에 실행되어서 컨트롤러 이후에 처리해야 하는 후처리 작업이 있을 때 사용한다. 이 메소드에는 컨트롤러가 반환하는 ModelAndView 타입의 정보가 제공되는데 Json 형태로 데이터를 제공하는 RestAPI 기반의 컨트롤러(@RestController)가 만들어지면서 최근에는 자주 사용되지 않는다. 또한 컨트롤러 하위 계층에서 작업을 진행하다가 중간에 예외가 발생하면 postHandle은 호출되지 않는다.
컨트롤러 실행이 끝나고 아직 화면 처리가 되지 않은 상태이기에 메소드 실행 이후 추가 작업이 가능하다.
afterCompletion()
afterCompletion 메소드는 이름에서 알 수 있듯이 뷰들에서 최종 결과를 생성하는 일을 포함하여 모든 작업이 완료된 후에 실행된다. 그뿐아니라 요청 처리 중에 사용한 리소스를 반환할때 사용하기도 한다. postHandler와 달리 컨트롤러 하위 계층에서 작업을 진행하다가 중간에 예외가 발생하더라도 반드시 호출된다.
Request/Response 객체 조작 가능 여부 : 디스패처 서블릿이 여러 Interceptor 목록을 가지고 for문으로 순차적으로 실행시킨다. 만약 true를 반환하면 다음 Interceptor가 실행되거나 Controller로 요청이 전달되며 flase가 반환되면 요청이 중단된다. 그래서 다른 Request나 Response 객체를 넘겨줄 수 없다.
스프링 내의 Bean 접근성 : 스프링에서 관리되기 때문에 스프링 내의 모든 객체에 접근이 가능하다.
Interceptor가 적용될 수 있는 경우는 무궁무진하기때문에 현재 배우는 웹 페이지 생성에서 적용하기로 했다. 로그인 시에 뷰에서 공개되지 않는 회원가입 페이지들, 로그인 페이지를 url을 이용해서 접속하고자 할 때 "현재 이용할 수 없다"는 문구를 띄워주는 가벼운 Interceptor이다.
적용할 map의 path는 '/user/login/form', '/user/join/write', '/user/agree'이다.
먼저 Interceptor를 작성한다.
@Component
public class PreventLoginInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
Object handler) throws Exception {
if(request.getSession().getAttribute("loginUser") != null) {
response.setContentType("text/html; charset=UTF-8");
PrintWriter out = response.getWriter();
out.println("<script>");
out.println("alert('해당 기능은 사용할 수 없습니다.')");
out.println("location.href='"+ request.getContextPath() + "'");
out.println("</script>");
out.close();
return false;
} else {
return true;
}
}
}
가장 많이 사용되는 preHandle을 이용하였으며 내용을 custom하였다. 코드의 내용은 다음과 같다.
(1) 현재 세션에 사용자의 로그인 정보(loginUser)가 있으면 return true하여 Controller로 넘겨 진행한다.
(2) 만약 로그인 정보가 없다면 script를 통해 '해당 기능은 사용할 수 없습니다.'라는 문구를 전달하고 contextPath의 index페이지로 돌려보내준다.
(3) 그리고 return false하여 Controller로 넘어가지 않고 종료한다.
Interceptor 작성 후에는 main/WEB-INF/spring/appServlet/servlet-context.xml에서 Intercpetor를 적용해준다.
<!-- interceptor -->
<interceptors>
<interceptor>
<mapping path="/user/login/form"/>
<mapping path="/user/join/write"/>
<mapping path="/user/agree"/>
<beans:bean class="com.gill.prac.interceptor.PreventLoginInterceptor"></beans:bean>
</interceptor>
</interceptors>
<interceptors> 태그 안에 다시 <interceptor> 태그를 생성해 <mapping> 태그를 이용하여 적용할 mapping값을 입력해준다.
그후 benas:bean으로 적용시킬 Intercpetor를 등록하는데 만약 bean을 이미 생성했다면 class에 위치 값을 넣어주면 되지만 bean을 생성하지 않았다면
<beans:bean id="keepLoginInterceptor" class="com.gill.prac.interceptor.PreventLoginInterceptor"></beans:bean>
<interceptors>
<interceptor>
<mapping path="/user/login/form"/>
<mapping path="/user/join/write"/>
<mapping path="/user/agree"/>
<beans:ref bean="keepLoginInterceptor"/>
</interceptor>
</interceptors>
servlet-context.xml 안에 bean을 등록하고 bean에 id를 집어넣으면 된다.
로그인 이전에는 이 페이지가 들어가진다!
하지만 로그인 후에는
알림이 뜨며 원래 페이지로 돌아가게된다.
https://velog.io/@gillog/Spring-InterceptorHandlerInterceptor-HandlerInterceptorAdapter (Interceptor 개요)
https://mangkyu.tistory.com/173 (Interceptor Method)
https://gngsn.tistory.com/153 (Interceptor, Filter의 차이)