본문 바로가기
⚙️Backend/Spring

[Spring] Filter + AOP + Interceptor : Filter

by Bㅐ추 2020. 6. 6.
728x90
반응형

0. 개인적으로 설명이 제일 이해 안갓던 파트.. ^^.. 역시 구글링과 네이버블로그가 최고다.

 

1. 서론

웹 개발을 하다 보면, 공통적으로 처리해야 할 업무들이 많다.

공통 업무에 관련된 코드를 모든 페이지마다 작성해야 한다면, 중복된 코드가 많아지게 되고,

프로젝트 단위가 커질 수록 서버에 부하를 줄 수도 있으며, 소스관리도 되지 않는다.

=> 즉 공통부분은 따로 빼서 관리하는 것이 좋다.

 

위와 같은 공통 처리를 위해 활용할 수 있는 것이 3가지가 있다.

" Filter / Interceptor / AOP "

 

2. Filter Interceptor AOP 의 흐름

 사진출처 :  https://blog.naver.com/platinasnow/220035316135

실행 순서를 보면 Filter가 가장 밖에 있고, 그 안에 Interceptor, 그 안에 AOP가 있는 형태이다.

따라서 요청이 들어오면,

Filter -> Interceptor -> AOP -> Interceptor -> Filter 순으로 거치게 된다.

 

* 간단 정리*

1. 서버를 실행시켜 서블릿이 올라오는 동안에 init() 이 실행이 되고, 그 후에 doFilter실행

2. 컨트롤러에 들어가기 전 preHandler 실행

3. 컨트롤러로 나와 postHandler, dfter Completion , doFilter 순으로 진행

4. 서블릿 종료시 destroy가 실행.

 

(실행메소드는 차차 알게된다. 일단 이런게 있구나 하고 넘어가자.)

  Interceptor Filter AOP
실행위치 servlet servlet method
실행순서 2 1(제일 먼저와 제일 나중) 3
설정 위치 xml or java web.xml xml or java
실행 메소드 preHandler
postHandler
afterCompletion
init
dofilter
destroy
pointcut 으로 @after,@before,@around등 위치를 지정하여 자유롭게 메소드 생성 가능

표를 설명해보자면, interceptor와 filter는 서블릿 단위에서 실행된다. 반면에 AOP는 메소드의 앞에 Proxy패턴을 이용해서 실행된다. 그래서 실행 순서에서도 차이가 생긴다. filter 가 가장 겉에 있고, 그 안에 interceptor 그리고 그 안에 aop가 들어있는 식의 구조이다.

=>request가 filter를 거쳐 interceptor 쪽으로 들어가고 aop를 거쳐 다시 나오면서 interceptor와 filter를 거침

 

실행되는 메소드를 기준으로 설명하면, 서버를 실행시켜 서블릿이 올라오는 동안에 init이 실행되고, 그 후 dofilter가 실행된다.

그 후 컨트롤러에 들어가기 전에 preHandler가 실행되고, aop가 실행된 후에 컨트롤러에서 나와 postHandler, after Completion, dofilter 순서대로 진행되고, 서블릿 종료시 destory가 실행 될 것이다.

 

AOP의 경우에는 Interceptor나 Filter와 달리 메소드 전후의 지점을 자유롭게 설정가능하고, interceptor와 filter가 주소로 밖에 걸러낼 대상을 구분 할 수 없는 것에 비해서 AOP는 주소, 파라미터, 어노테이션등 다양한 방법으로 대상을 지정할 수 있는 장점이 있습니다.

 

이제, 하나하나 살펴보자.

< Filter , Interceptor, AOP 의 개념>

3. Filter

3-1. Filter(필터)

말 그대로 요청과 응답을 거른 뒤 정제하는 역할.

 

그림 1

 

자원이 받게 되는 요청 정보는 클라이언트와 자원 사이에 존재하는 필터에 의해 변경된 요청 정보가 되며, 또한 클라이언트가 보게 되는 응답 정보는 클라이언트와 자원 사이에 존재하는 필터에 의해 변경된 응답 정보가 된다.

(위 그림에서는 요청 정보를 변경하는 필터와 응답 정보를 변경하는 필터를 구분해서 표시했는데 실제로 이 둘은 같은 필터이다.

단지 개념적인 설명을 위해 위 그림1과 같이 분리해 놓은 것 뿐이다.)

 

필터는 그림1에서처럼 클라이언트와 자원 사이에 1개가 존재하는 경우가 보통이지만, 여러 개의 필터가 모여 하나의 체인(chain; 또는 사슬)을 형성할 수도 있다. 그림2는 필터 체인의 구조를 보여주고 있다.

 

그림2

그림2와 같이 여러 개의 필터가 모여서 하나의 체인을 형성할 때 첫번째 필터가 변경하는 요청 정보는 클라이언트의 요청 정보가 되지만, 체인의 두번째 필터가 변경하는 요청 정보는 첫번째 필터를 통해서 변경된 요청 정보가 된다. 즉, 요청 정보는 변경에 변경에 변경을 거듭하게 되는 것이다. 응답 정보의 경우도 요청 정보와 비슷한 과정을 거치며 차이점이 있다면 필터의 적용 순서가 요청 때와는 반대라는 것이다. (그림2를 보면 이를 알 수 있다.)

 

필터는 변경된 정보를 변경하는 역할 뿐만 아니라 흐름을 변경하는 역할도 할 수 있다. 즉, 필터는 클라이언트의 요청을 필터 체인의 다음 단계(결과적으로는 클라이언트가 요청한 자원)에 보내는 것이 아니라 다른 자원의 결과를 클라이언트에 전송할 수 있다. 필터의 이러한 기능은 사용자 인증이나 권한 체크와 같은 곳에서 사용할 수 있다.

 

스프링에서의 필터는 DispatacherServlet 이전에 실행이 된다.

 

 

3-2. Filter가 필요한 이유.

예를들어 요청 데이터의 본문을 UTF-8로 인코딩하는 부분을 떠올릴 수 있다.

request.setCharacterEncoding("utf-8");                                                
String param = request.getParameter("param");

(정말 많이 사용했다.)

 

그러나 웹 어플리케이션에 수십 수백개의 웹 컴포넌트를 개발해야 한다면 이런 한글 인코딩 처리 코드는 수백여개의 중복이 발생할 것이다. 매번 웹 컴포넌트를 개발할때마다 인코딩 처리 로직을 넣어주는것도 문제지만, 만약 웹 어플리케이션을 수출한다거나 정책이 바뀌어 인코딩처리를 다른것으로 바꿔주어야 한다면 중복되는 모든 코드를 각각 바꿔주어야 하므로 유지보수 측면에서도 좋지 못한 방법이다.

 

필터는 이런 상황에서 인코딩 처리 부분을 분리하여 개발을 빠르고 유지보수 측면에서 나중에 코드를 변경할때도 쉽게 수정이 용이하게 해준다. 인코딩 정책이 바뀐다 하더라도 필터부분만 수정해주면 되므로 유지보수도 쉬워진다.

(결국, 유지보수를 위한 것 . 중복을 싫어해서 즉 똑같은 코드를 치기 싫어서 만들어 낸 기능.. 똑같은 메서드를 쓰기 싫으니까 상속을 만들어내고, 똑같은 틀의 클래스들을 만들기 싫으니까 인터페이스라는 기능으로 틀을 만들고.. 언뜻 어려워 보이는 개념들 이지만 다 하나같이 중복을 피하기 위해서, 반복작업을 하기 싫어서 만들어진 기능이라 생각하면 웃기다. 얼마나 귀찮았으면 이런 기능들을 만들어냈을 까 생각도 들고, 물론 그 덕분에 편해졌지만 .... 개념이 너무 어렵잖아요ㅠ)

그림3

사진출처 : https://dololak.tistory.com/602

 

필터의 기능 및 사용 예

  • 서블릿이 호출되기 전에 요청(request)를 가로채어 조작한다.

  • 서블릿이 호출되기 전에 요청을 가로채어 점검할 수 있다.

  • 서블릿이 호출되기 전에 HTTP 요청의 헤더를 조작할 수 있다.

  • 서블릿이 호출된 이후 응답(response)를 출력하기 전에 가로채어 조작한다.

  • 서블릿이 호출된 이후 응답 헤더를 조작할 수 있다.

 

그에따른 사용 예

  • 로그인여부나 권한 검사와 같은 인증 기능

  • 요청이나 응답에 대한 로그(기록) 기능

  • 오류 처리 기능

  • 데이터 압축이나 변환 기능

  • 인코딩 처리 기능 등..

 

3-3. Filter 생성

Filter는 Clinet 와 DispathcerServlet 사이에 맨 처음 진행되므로 web.xml 에 지정을 해준다.

(왜? web.xml은 톰캣이 실행될 때 처음으로 읽어들이는 설정파일이다. filter는 맨 처음 진행되니까 web.xml에 설정해주는 것이 아닐까 추측..)

 

예시) 

	<!-- Request의 인코딩 처리.
		filter가 동작하는 위치는 client와 Dispatcher Servlet 사이!  -->
	<filter>
		<filter-name>encodingFilter</filter-name>
		<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
		<init-param>
			<param-name>encoding</param-name>
			<param-value>UTF-8</param-value>
		</init-param>
		<init-param>
			<param-name>forceEncoding</param-name>
			<param-value>true</param-value>
		</init-param>
	</filter>
	
	<filter-mapping>
		<filter-name>encodingFilter</filter-name>
		<url-pattern>/*</url-pattern><!-- 모든 request를 encoding하겠다. -->
	</filter-mapping>

위의 등록한 filter의 이름은 encodingFilter이고, 값은 UTF-8인 파라미터를 정의하고 있다.

필터의 url-pattern을 /*로 정의하면 servlet,jsp뿐만 아니라 이미지와 같은 모든 자원의 요청에도 호출이 된다.

 

위와 같이 바로 필터를 web.xml에 지정할 수 있지만, 사용자가 따로 필터클래스를 생성해서 필터를 적용시킬 수 있다.

그렇다면,

Filter 클래스를 만들어보자.

 

*필터관련 인터페이스 및 클래스

필터를 구현하는 데에 있어 핵심적인 역할을 인터페이스 및 클래스가 3개가 있는데, 그것들은 바로

 javax.servlet.Filter 인터페이스

javax.servlet.ServletRequestWrapper 클래스

javax.servlet.ServletResponseWrapper 클래스

이다.

  javax.servlet.Filter
: 클라이언트와 최종 자원 사이에 위치하는 필터를 나타내는 객체가 구현해야 하는 인터페이스이다.

javax.servlet.ServletRequestWrapper ,javax.servlet.ServletResponseWrapper
: 필터가 요청을 변경한 결과 또는 응답을 변경할 결과를 저장할 래퍼 클래스.개발자는 이 두 클래스를 알맞게 상속하여 요청/응답 정보를 변경하면 된다.

 

* javax.servlet.Filter

Filter 인터페이스( javax.servlet.Filter)에는 다음과 같은 메소드가 선언되어 있다.

 

  • public void init(FilterConfig filterConfig) throws ServletException
    필터를 웹 컨테이너 내에 생성한 후 초기화할 때 호출한다.
  • public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws java.io.IOException, ServletException
    체인을 따라 다음에 존재하는 필터로 이동한다. 체인의 가장 마지막에는 클라이언트가 요청한 최종 자원이 위치한다.
  • public void destroy()
    필터가 웹 컨테이너에서 삭제될 때 호출된다.

 

위 메소드에서 필터의 역할을 하는 메소드가 바로 doFilter() 메소드이다. 서블릿 콘테이너는 사용자가 특정한 자원을 요청했을 때 그 자원 사이에 필터가 존재할 경우 그 필터 객체의 doFilter() 메소드를 호출하며, 바로 이 시점부터 필터가 작용하기 시작한다. 다음은 전형적인 필터의 구현 방법을 보여주고 있다.

 

해당 내용을 바탕으로 Filter를 만들어보자.

 

 com/mvc/upgrade/common/filter/Logfilter.java 를 생성한다.

 

 

아래와 같이 Filter 인터페이스를 구현한다.

import javax.servlet.Filter;
public class LogFilter implements Filter{

	@Override
	public void init(FilterConfig filterConfig) throws ServletException {
		// TODO Auto-generated method stub
		
	}
	// Dispatcher 앞에서 처리
	@Override
	public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
			throws IOException, ServletException {
		// TODO Auto-generated method stub
		
	}

	@Override
	public void destroy() {
		// TODO Auto-generated method stub
		
	}
}

init() : 필터 초기화
doFilter() : 전 후 처리
destory() : 필터 인스턴스 종료

 

< doFilter()의 흐름 >

  1. request 파리미터를 이용하여 클라이언트의 요청 필터링
    1 단계에서는 RequestWrapper 클래스를 사용하여 클라이언트의 요청을 변경한다.
  2. chain.doFilter() 메소드 호출
    2 단계에서는 요청의 필터링 결과를 다음 필터에 전달한다.
  3. response 파리미터를 사용하여 클라이트로 가는 응답 필터링
    3 단계에서는 체인을 통해서 전달된 응답 데이터를 변경하여 그 결과를 클라이언트에 전송한다.

클라이언트의 자원 요청이 필터를 거치는 경우, 클라이언트의 요청이 있을 때 마다 doFilter()메소드가 호출된다.

doFilter()메소드는 각각의 요청에 대해서 알맞은 작업을 처리하게 되는 것.

 

위 코드를 보면 doFilter()메소드는 세번째 파라미터로 FilterChain 객체를 전달받는 것을 알 수 있다. 이는 클라이언트가 요청한 자원에 이르기까지 클라이언트의 요청이 거쳐가게 되는 필터 체인을 나타낸다(그림2 참고.) FilterChain을 사용함으로써 필터는 체인에 있는 다음 필터에 변경한 요청과 응답을 건내줄 수 있게 된다.

 

일단 doFilter메소드를 채워보자.

private Logger logger = LoggerFactory.getLogger(LogFilter.class);

	@Override
	public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
			throws IOException, ServletException {
		
		// 1. request 파라미터를 이용해서 요청의 필터 작업을 수행한다.
		// 2. 만약 다음 필터가 존재할 시, 체인의 다음 필터를 처리한다.
		// chain.doFilter(request,response);
		// 3. response를 이용하여 응답의 필터링 작업을 수행한다.
		
		
		HttpServletRequest req = (HttpServletRequest) request; // 해당 요청을 HttpServletRequest로 형변환.
		String remoteAddr = req.getRemoteAddr(); // ip주소 가져오기.
		String uri = req.getRequestURI(); 
		String url = req.getRequestURL().toString();
		
		String queryString = req.getQueryString();
		
		String referer = req.getHeader("referer");
		String agent = req.getHeader("User-Agent");
		
		logger.info("---START LOG FILTER---"); //filter 가 실행
		
		logger.info("remoteAddr: "+remoteAddr+"\n"); // ip주소
		logger.info("uri: "+uri+"\n");
		logger.info("url: "+url+"\n");
		logger.info("quertString: "+queryString+"\n");
		logger.info("referer: "+referer+"\n"); // 이전 페이지(보내는 페이지) url
		logger.info("agent: "+agent+"\n");	// 사용자 정보(browser,os등...)
		logger.info("---END LOG FILTER---"); //filter 가 실행
		chain.doFilter(request, response); 
		//다음에 존재하는 필터가 있으면, 그 필터가 실행될 수 있도록 chaining!
		// request에 들어있는 정보를 읽어서 log.info한 것!
	}

 

이제 작성한 필터를 web.xml에 적용시켜주자.

 

필터를 사용하기 위해서는 어떤 필터가 어떤 자원에 대해서 적용된다는 것을 서블릿/JSP컨테이너에 알려주어야 한다. 서블릿 설정은 web.xml파일을 통해 하고 있으며, 필터 역시 web.xml파일을 통해 설정한다.

 

< 태그 정리 >

<filter> : 웹애플리케이션에서 사용될 필터를 지정하는 역할

<filter-name> : 필터의 이름

<filter-class> 해당 필터의 클래스

 

<filter-mapping> : 특정 자원에 대해 어떤 필터를 사용할 지 지정함

<url-pattern> : 클라이언트가 요청한 특정 URI에 대해 필터링을 할 때 사용.

 

만약, 다음과 같이 <filter-mapping>태그를 지정하였다고 해보자.

<filter-mapping>
      <filter-name>AuthCheckFilter</filter-name>
      <url-pattern>/pds/*</url-pattern>
</filter-mapping>

이 경우 클라이언트가 /pds/a.zip을 요청하든, /pds/b.zip을 요청하는 지에 상관없이 AuthCheckFilter가 필터로 사용될 것이다. 

<url-pattern> 태그를 사용하지 않고 대신 <servlet-name> 태그를 사용함으로써 특정 서블릿에 대한 요청에 대해서 필터를 적용할 수도 있다. 예를 들면 다음과 같이 이름이 FileDownload인 서블릿에 대해서 AuthCheckFilter를 필터로 사용하도록 할 수 있다.

   <filter-mapping>
        <filter-name>AuthCheckFilter</filter-name>
        <servlet-name>FileDownload</servlet-name>
   </filter-mapping>

     <servlet>
        <servlet-name>FileDownload</servlet-name>
        ...
     </servlet>

앞에서 필터는 체인을 형성할 수 있다고 하였다. 체인을 형성한다는 것은 어떤 특정한 순서에 따라 필터가 적용된다는 것을 의미한다. 예를 들면, 여러분은 '인증필터->파라미터 변환 필터->XSL/T 필터->자원->XSL/T 필터->파라미터 변환 필터->인증필터'와 같이 특정 순서대로 필터를 적용하길 원할 것이다. 서블릿2.3 규약은 다음과 같은 규칙에 기반하여 필터 체인 내에서 필터의 적용 순서를 결정한다.

  1. url-pattern 매칭은 web.xml 파일에 표기된 순서대로 필터 체인을 형성한다.
  2. 그런 후, servlet-name 매칭이 web.xml 파일에 표기된 순서대로 필터 체인을 형성한다.

 

이제 위에서 만든 LogFilter를 web.xml에 적용시키자.

해당 LogFilter는 모든 요청(/*)에 대해서 실행시킬 것이다.

 

	<!-- filter 추가 -->
	<filter>
		<filter-name>logFilter</filter-name>
		<filter-class>com.mvc.upgrade.common.filter.LogFilter</filter-class>
	</filter>
	
	<filter-mapping>
		<filter-name>logFilter</filter-name>
		<url-pattern>/*</url-pattern> <!-- 모든 요청을 filter -->
	</filter-mapping>

이제 실행시켜서 결과를 봐보자.

 

>> http://localhost:8787/upgrade/index.html 실행시!

>> http://localhost:8787/upgrade/list.do 실행시!

>> http://localhost:8787/upgrade/detail.do?myno=2 실행시!

 

< 필터체인 순서 정하는 법 >

<filer-mapping>이 정의된 순서를 기준으로 필터체인의 정렬 순서를 정의한다. 따라서 다음과 같이 설정하는 경우 firstFilter -> thirdFilter 순서로 필터체인이 형성된다.

<filer-mapping>
    <filter-name>firstFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filer-mapping>
 
 
<filer-mapping>
    <filter-name>secondFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filer-mapping>
 
 
<filer-mapping>
    <filter-name>thirdFilter</filter-name>
    <url-pattern>/third</url-pattern>
</filer-mapping>

 

 

여기까지 읽었으면, 다시 위로 가서 

 

Filter Interceptor AOP 의 흐름을 다시 읽어보자. 

 

내용출처

https://doublesprogramming.tistory.com/133

https://goddaehee.tistory.com/154

https://javacan.tistory.com/entry/58

728x90
반응형

'⚙️Backend > Spring' 카테고리의 다른 글

[Spring] Login 기능 및 기타 어노테이션  (0) 2020.06.08
[Spring] Filter + AOP + Interceptor : AOP  (0) 2020.06.06
[Spring] 전체 정리  (0) 2020.06.06
💡용어 정리💡  (0) 2020.05.31
💡Spring 동작과정 정리 💡  (1) 2020.05.31