어플리케이션의 여러 로직에서 공통으로 처리해야할 로직이 있다면 필터나 인터셉터를 사용해서 처리하면 좋다. 이러한 공통 관심사는 스프링의 AOP로도 해결할 수 있지만, 웹과 관련된 공통 관심사는 HTTP 헤더나 URL 정보가 필요하다. 서블릿에서는 ‘필터’를, 스프링에서는 ‘인터셉터’ 라는 기능을 지원한다.
필터 인터페이스를 구현하고 등록하면 서블릿 컨테이너가 필터를 싱글톤 객체로 생성하고 관리한다.
메서드
설명
init()
필터 초기화 메서드, 서블릿 컨테이너가 생성될 때 호출한다.
doFilter()
고객의 요청이 올 때 마다 해당 메서드가 호출된다. 공통 로직을 구현하면 된다.
destroy()
필터 종료 메서드, 서블릿 컨테이너가 종료될 때 호출된다.
<예제> 예제>
@Slf4jpublicclassLoginCheckFilterimplementsFilter{//필터를 통과하는 경로는 따로 설정privatestaticfinalString[]whitelist={"/","/member/add","/login","logout","/css/*"};@OverridepublicvoiddoFilter(ServletRequestrequest,ServletResponseresponse,FilterChainfilterChain)throwsIOException,ServletException{HttpServletRequesthttpRequest=(HttpServletRequest)request;StringrequestURI=httpRequest.getRequestURI();HttpServletResponsehttpResponse=(HttpServletResponse)response;try{log.info("인증 체크 필터 시작 {}",requestURI);if(isLoginCheckPath(requestURI)){log.info("인증 체크 로직 실행 {}",requestURI);HttpSessionsession=httpRequest.getSession(false);if(session==null||session.getAttribute(SessionConst.LOGIN_MEMBER)==null){//로그인 안된 사용자log.info("미인증 사용자 요청 {}",requestURI);//로그인 페이지로 redirecthttpResponse.sendRedirect("/login?redirectURL="+requestURI);return;}}filterChain.doFilter(request,response);}catch(Exceptione){throwe;}finally{log.info("인증 체크 필터 종료 {}",requestURI);}}/**
* 화이트리스트의 경우 인증 체크를 하지 않도록 설정
*/privatebooleanisLoginCheckPath(StringrequestURI){return!PatternMatchUtils.simpleMatch(whitelist,requestURI);}}
1️⃣ - 2. 필터 등록
필터를 등록하는 방법은 여러가지 있지만, 스프링 부트를 사용한다면 FilterRegistrationBean 을 사용해서 등록하면 된다.
필터와 달리 인터셉터는 컨트롤러 호출 전(preHandle), 호출 후(postHandle), 요청완료 이후(afterCompletion) 세 단계로 세분화되어있다.
메서드
설명
preHandle
컨트롤러 호출 전에 호출된다. (더 정확히는 핸들러 어댑터 이전에 호출된다.) preHandle 의 return 값이 true이면 다음으로 진행하고, false 면 더는 진행하지 않는다.
postHandle
컨트롤러 호출 후에 호출된다. (더 정확히는 핸들러 어댑터 호출 후에 호출된다.)
afterCompletion
뷰가 렌더링된 이후에 호출된다. 컨트롤러에서 예외가 발생되도 항상 호출되어 예외정보를 포함해서 호출할 수 있다.
출처: 김영한의 스프링MVC (인프런)
@Slf4jpublicclassLogInterceptorimplementsHandlerInterceptor{publicstaticfinalStringLOG_ID="logId";@OverridepublicbooleanpreHandle(HttpServletRequestrequest,HttpServletResponseresponse,Objecthandler)throwsException{StringrequestURI=request.getRequestURI();Stringuuid=UUID.randomUUID().toString();//afterCompletion에서 같은 요청에 대해 같은 uuid를 쓰고싶기 때문에 request객체에 담아서 보낸다.request.setAttribute(LOG_ID,uuid);//@RequestMapping 으로 처리하는 핸들러는 'HandlerMethod'에 속한다.//정적 리소스를 사용하는 경우에는 ResourceHttpHandler 가 사용된다.if(handlerinstanceofHandlerMethod){HandlerMethodhm=(HandlerMethod)handler;}log.info("REQUEST [{}][{}][{}]",uuid,requestURI,handler);returntrue;}@OverridepublicvoidpostHandle(HttpServletRequestrequest,HttpServletResponseresponse,Objecthandler,ModelAndViewmodelAndView)throwsException{log.info("postHandle [{}]",modelAndView);}@OverridepublicvoidafterCompletion(HttpServletRequestrequest,HttpServletResponseresponse,Objecthandler,Exceptionex)throwsException{StringrequestURI=request.getRequestURI();Objectuuid=(String)request.getAttribute(LOG_ID);log.info("REQUEST [{}][{}][{}]",uuid,requestURI,handler);//afterCompletion 은 예외가 발생해도 무조건 호출된다.if(ex!=null){log.error("afterCompletion error!!",ex);}}}
2️⃣ - 2. 인터셉터 등록
메서드
설명
registry.addInterceptor(new LogInterceptor())
인터셉터를 등록한다.
order(1)
인터셉터의 호출 순서를 지정한다. 낮을수록 먼저 호출된다.
addPathPatterns(“/**”)
인터셉터를 적용할 URL 패턴을 지정한다. (/** : 모든 경로)
excludePathPatterns()
인터셉터에서 제외할 패턴을 지정한다.
@ConfigurationpublicclassWebConfigimplementsWebMvcConfigurer{@OverridepublicvoidaddInterceptors(InterceptorRegistryregistry){registry.addInterceptor(newLogInterceptor()).order(1).addPathPatterns("/**")//모든 경로를 허용.excludePathPatterns("/css/**","/*.ico","/error");//하지만 얘네들은 인터셉터에서 제외할거야registry.addInterceptor(newLoginCheckInterceptor()).order(2).addPathPatterns("/**").excludePathPatterns("/","/members/add","/login","/logout","/css/**","/*.ico","/error");}}