[Spring] Servlet부터 Filter와 Interceptor까지
TIL

[Spring] Servlet부터 Filter와 Interceptor까지

Servlet

HTTP 요청이 WAS, Servlet에 의해 처리되고 응답이 되는 과정을 보면 Servlet이 어떤 것인지 알 수 있다.

HTTP 요청 -> WAS는 Request, Response 객체를 만들어서 Servlet 객체 호출 -> Servlet 객체에서 Request  객체에서 HTTP 요청 정보를 꺼내서 비즈니스 로직 수행 -> 다시 Response 객체에 HTTP 응답 정보를 담아서 WAS가 Response 객체에 담겨있는 내용으로 HTTP 응답 정보를 생성해서 클라이언트로 보냄

 

즉, Servlet은 Web Application에서 HTTP 프로토콜을 이용해 요청을 처리하고 응답을 하는 자바 클래스이다.

이러한 servlet을 관리해주는 것이 Servlet Container이다. Servlet은 어떠한 역할을 수행하는 정의서라고 하면, Servlet Container는 그 정의서를 보고 수행하는 역할인 것이다. (예) 톰캣

 

간략하게 작성하면 Servlet Container 역할은 1) 웹서버와의 통신 지원 2) Servlet 생명주기 관리 3) 멀티스레드 지원 및 관리 4) 선언적인 보안 관리 이다.

 

참고로 Servlet은 자바 코드 속에 HTML 코드가 들어가는 형태인데, JSP는 HTML 코드 속에 자바 코드가 들어가는 구조다. JSP는 Servlet 규칙이 복잡해서 나온 기술인고 WAS에 의해서 Servlet 클래스로 변환하여 사용되어 진다.

Servlet 동작 방식

 

JSP 동작 방식

 

 

Dispatcher Servlet

Servlet 중 한가지인 Dispatcher Servlet (디스패처 서블릿)은 HTTP 프로토콜로 들어오는 모든 요청을 가장 먼저 받아 적합한 컨트롤러에 위임해주는 프론트 컨트롤러다. Dispatcher Servlet은 Tomcat과 같은 Servlet Container에게 HTTP 요청을 가장 먼저 받기 때문에 공통적인 작업을 먼저 처리하게 된다. Dispatcher Servlet이 나옴으로써 과거에는 모든 Servlet을 URL 매핑을 위해 web.xml에 등록해줘야 했지만 이제는 dispatcher servlet이 요청을 핸들링하고 공통 작업을 처리해주게 되었다. 우리는 컨트롤러를 구현해두기만 하면 dispatcher servlet이 알아서 적합한 컨트롤러로 위임을 해준다.

 


Spring에서의 Filter

filter와 interceptor

위에서 언급한 Dispatcher Servlet에 요청이 전달되기 전후에 URL 패턴에 맞는 모든 요청에 대해 부가 작업을 처리할 수 있는 기능을 의미한다. 즉 Dispatcher Servlet은 스프링의 가장 앞단에 존재하는 프론트 컨트롤러이므로, Filter는 스프링 범위 밖에서 처리가 되는 것이다. (다만 스프링 빈으로 등록은 된다.)

 

Filter 사용법

javax.servlet의 Filter 인터페이스를 구현해야 한다. Filter 인터페이스를 타고 들어가보면 다음과 같이 구성되어있다.

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package javax.servlet;

import java.io.IOException;

public interface Filter {
    default void init(FilterConfig filterConfig) throws ServletException {
    }

    void doFilter(ServletRequest var1, ServletResponse var2, FilterChain var3) throws IOException, ServletException;

    default void destroy() {
    }
}
  • init(): 필터 객체를 초기화하고 서비스에 추가하기 위한 메소드. 웹 컨테이너가 1회 init 메소드를 호출하여 필터객체를 초기화하면 이후의 요청들은 doFilter를 통해 처리된다.
  • doFilter(): 모든 HTTP 요청이 dispatcher servlet으로 전달되기 전에 웹 컨테이너에 의해 실행되는 메소드. 파라미터인 FilterChain chain 의 doFilter를 통해 다음 대상으로 요청을 전달하게 된다. chain.doFilter() 전/후에 우리가 필요한 처리 과정을 넣어줌으로써 원하는 처리를 진행할 수 있다.
  • destory(): 필터 객체를 서비스에서 제거하고 사용하는 자원을 반환하기 위한 메소드. 웹 컨테이너에 의해 1번 호출되며 이후에는 이제 doFilter에 의해 처리되지 않는다.

위의 인터페이스를 구현하면 다음과 같다.

public class DemoService implements Filter {

	@Override
	public void init(FilterConfig filterConfig) throws ServletException {
		System.out.println("필터 생성");
	}

	@Override
	public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws
		IOException,
		ServletException {

		System.out.println("== 필터 시작 ==");
		filterChain.doFilter(servletRequest, servletResponse);
		System.out.println("== 필터 종료 ==");
	}

	@Override
	public void destroy() {
		System.out.println("필터 제거");
	}
}

 

추가로, 필터 등록을 위해 Configuration 설정을 통해 Bean으로 등록해줘야한다.

@Configuration
public class Config {

	@Bean
	public FilterRegistrationBean filterRegister() {
		FilterRegistrationBean registrationBean = new FilterRegistrationBean(new DemoService());
		return registrationBean;
	}
}

 

임의로 아래와 같이 Controller을 만들어주고 API 호출해주면 다음과 같이 필터가 실행되는 것을 알 수 있다.

@RestController
public class DemoController {

	@GetMapping("/")
	public String hello() {
		System.out.println("Hello world!");
		return "Hello";
	}
}
  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::               (v2.7.17)

2024-07-22 23:20:42.968  INFO 16412 --- [           main] com.example.demo.DemoApplication         : Starting DemoApplication using Java 11 on DESKTOP-THAI4MF with PID 16412 (C:\Users\admin\Downloads\demo (4)\demo\target\classes started by admin in C:\Users\admin\Downloads\demo (4)\demo)
2024-07-22 23:20:42.970  INFO 16412 --- [           main] com.example.demo.DemoApplication         : No active profile set, falling back to 1 default profile: "default"
2024-07-22 23:20:44.378  INFO 16412 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port(s): 8080 (http)
2024-07-22 23:20:44.389  INFO 16412 --- [           main] o.apache.catalina.core.StandardService   : Starting service [Tomcat]
2024-07-22 23:20:44.389  INFO 16412 --- [           main] org.apache.catalina.core.StandardEngine  : Starting Servlet engine: [Apache Tomcat/9.0.82]
2024-07-22 23:20:44.487  INFO 16412 --- [           main] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
2024-07-22 23:20:44.487  INFO 16412 --- [           main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 1476 ms
필터 생성
2024-07-22 23:20:44.817  INFO 16412 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8080 (http) with context path ''
2024-07-22 23:20:44.826  INFO 16412 --- [           main] com.example.demo.DemoApplication         : Started DemoApplication in 2.214 seconds (JVM running for 2.574)
2024-07-22 23:21:28.810  INFO 16412 --- [nio-8080-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring DispatcherServlet 'dispatcherServlet'
2024-07-22 23:21:28.810  INFO 16412 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : Initializing Servlet 'dispatcherServlet'
2024-07-22 23:21:28.811  INFO 16412 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : Completed initialization in 1 ms
== 필터 시작 ==
Hello world!
== 필터 종료 ==

 

 

추가로,

  • Filter를 등록하는 다양한 방법
    1. @Configuration 별도로 붙여주고 작성하는 방식(위에 언급한 방식): 설정을 위한 별개의 파일(@Configuration)이 필요하지만 setOrder()를 통해 순서를 정할 수 있고 addUrlPatterns()를 통해 베이스 URL 및 WhiteList를 설정할 수 있음
    2. @Component를 필터에 붙여서 빈으로 등록하는 방식: 설정을 위한 별개의 파일이 필요하지 않고 @Order 어노테이션을 이용해 순서를 정할 수 있다. 기본 URL Pattern이 /*이며 설정할 수 없다.
    3. @ServletComponentScan을 @Configuration 어노테이션이 설정되어 있는 곳에 걸어주고, 필터에 @WebFilter 어노테이션을 설정해주는 방식: 설정을 위한 별개의 파일이 필요하지 않다. 필터에 대한 순서를 보장할 수 없다. @WebFilter의 value나 urlPatterns 파라미터로 베이스 URL을 설정할 수 있다.
    4. @WebFilter, @Component를 필터 위에 붙이는 방식: 설정을 위한 별개의 파일이 필요하지 않고 컴포넌트 스캔 방식을 사용하기 때문에 @ServletComponentScan도 필요없음. @Order로 순서 설정할 수 있고, @WebFilter의 value나 urlPatterns 파라미터로 베이스 URL을 설정할 수 있다.

        설정 방법 순서 설정 베이스 URL Bean 여부
      @Configuration FilterRegistrationBean setOrder addUrlPatterns X
      @Component 없어도 됨 @Order 사용할 수 없음 O
      @WebFilter @ServletComponentScan 사용할 수 없음 value, urlPatterns 파라미터 O
      @WebFilter +
      @Component
      없어도 됨 @Order value, urlPatterns 파라미터 O
  • registrationBean.addUrlPatterns("/url");을 하면 특정 URL에 대해서만 Filter를 등록할 수 있다.
  • Filter를 2개 등록하고 registrationBean.setOrder(1);을 통해 순서를 지정해주면 필터도 순서대로 작동하게 된다. 다만 주의할 점은 1번 필터, 2번 필터를 등록했을 경우 다음과 같이 나온다.
== 1번 필터 시작 ==
== 2번 필터 시작 ==
Hello world!
== 2번 필터 종료 ==
== 1번 필터 시작 ==

 

필터 동작 순서

 


Spring에서의 Interceptor

Spring에서의 Interceptor

Filter와 다르게 Interceptor는 Dispatcher Servlet이 Controller를 호출하기 전/후에 끼어들어 요청과 응답을 참조하거나 가공하는 역할을 하게 된다. Filter는 웹 컨테이너에서 동작했지만 Interceptor는 스프링 컨텍스트에서 동작한다.

 

Dispatcher Servlet이 Handler Mapping을 통해 Controller를 찾고, 그 결과로 실행 체인(HandlerExecutionChain)을 돌려준다. 여기서 1개 이상의 Interceptor가 등록되어 있으면 순차적으로 Interceptor들을 거쳐 Controller가 실행되고, 없다면 바로 Controller를 실행한다.

 

Interceptor 사용법

org.springframework.web.servlet의 HandlerInterceptor 인터페이스를 구현해야 한다.

public interface HandlerInterceptor {
    default boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        return true;
    }

    default void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable ModelAndView modelAndView) throws Exception {
    }

    default void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception {
    }
}
  • preHandle(): Controller가 호출되기 전에 실행되는 메서드. 전처리 작업이나 요청정보 가공/추가에 활용 가능
  • postHandle(): Controller가 호출된 후에 실행되는 메서드. (View 렌더링 전) 후처리 작업에 활용 가능. Controller 하위 계층에서 작업을 진행하다가 중간에 예외가 발생하면 호출되지 않음.
  • afterCompletion(): 모든 작업이 완료된 후에 실행되는 메서드. (View 렌더링 후) 요청 처리 중에 사용한 리소스를 반환할 때 사용할 수 있음. Controller 하위 계층에서 작업을 진행하다가 중간에 예외가 발생해도 반드시 호출된다.
public class AuthCheckInterceptor implements HandlerInterceptor {

	@Override  // 컨트롤러 실행 전
	public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
			throws Exception {
		HttpSession session = request.getSession(false);
		if(session != null) {
			Object loginInfo = session.getAttribute("loginInfo");
			if(authInfo != null) {
				return true;
			}
		} 
		response.sendRedirect(request.getContextPath() + "/login");
		return false;
	}
}

 


Filter와 Interceptor 차이

  1. Filter는 Request, Response를 조작할 수 있지만, Interceptor는 조작할 수 없다. (내부 상태 변경한다X, 다른 객체로 바꿔친다O)
    • 필터는 다음 필터 호출을 위해 필터 체이닝(다음 필터 호출)을 해주는데 이때 request, response를 넘겨주므로 원하는 request, response를 바꿔치기 하는게 가능하다.
    • 인터셉터는 Dispatcher Servlet이 여러 인터셉터 목록을 가지고 순차적으로 실행시키는 방식이고, 반환값이 true, false이기 때문에 다음 인터셉터의 실행여부만 리턴할 뿐 request, response 객체를 넘길 수 없다.
  2. Filter는 스프링과 무관하게 전역적으로 처리해야 하는 작업들을 처리하지만, Interceptor는 클라이언트의 요청과 관련되어 전역적으로 처리해야 하는 작업들을 처리할 수 있다.
    • Filter 사용 사례: 인증, 모든 요청에 대한 로깅 및 검사, 이미지/데이터 압축 및 문자열 인코딩, Spring과 분리되어야 하는 기능,  XSS방어, 인코딩 변환처리, 요청에 대한 인증, 권한 체크
    • Interceptor 사용 사례: 세부적인 인증, API 호출에 대한 로깅 또는 검사, Controller로 넘겨주는 정보의 가공, 로그인 체크, 권한 체크, 프로그램 실행시간 작업, 로그확인

AOP

추가로, Filter, Interceptor와 많이 비교되는 AOP는 비즈니스 로직에 좀 더 집중한다. 메소드 전 후로 자유롭게 설정할 수 있고, 주소로 대상을 구분하는 Filter, Interceptor와 다르게 주소나 파라미터, 어노테이션 등 다양한 방법으로 대상을 지정할 수 있다.

 

Filter가 스프링빈으로 관리될 수 있는 이유

과거에는 Filter는 스프링 빈으로 관리되지 않았다. 하지만 위에서 언급했듯이 지금은 등록이 가능하고 빈 주입도 가능한데, 이는 DelegatingFilterProxy가 등장했기 때문이다. 다만 현재는 SpringBoot를 쓰면서 웹서버를 직접 관리하고, DelegatingFilterProxy조차 필요없게 되었다.


Reference

https://mangkyu.tistory.com/14

 

[JSP] 서블릿(Servlet)이란?

1. Servlet(서블릿) 서블릿을 한 줄로 정의하자면 아래와 같습니다. 클라이언트의 요청을 처리하고, 그 결과를 반환하는 Servlet 클래스의 구현 규칙을 지킨 자바 웹 프로그래밍 기술 간단히 말해서,

mangkyu.tistory.com

 

728x90
반응형