본문 바로가기

SpringBoot

ch2 13. filter와 interceptor

1. Filter

- 공통적인 요청 전처리와 응답 후처리에 사용. 로깅, 인코딩 등 

서블릿이 있을 때 공통적으로 똑같이 전처리 작업, 후처리 작업이 들어간다면 이는 '중복'이다. 

이렇게 중복 코드들이 여러 서블릿에 공통적으로 들어갈 때 중복을 제거해야한다. 

 

-> 어떻게 제거?

서블릿들 앞에 Filter를 두면 된다! (서블릿은 컨트롤러라고 생각하기)

Filter가 발전한게 Interceptor이듯이 서블릿이 발전한게 컨트롤러이다. 

 

요청이 왔을 때 요청을 필터가 먼저 받아서 전처리를 수행하고 서블릿을 호출한다. 

(3개 중 두 번째 서블릿으로 간다고 가정) 호출한 서블릿으로 요청이 가고 해당 서블릿이 처리를 하고 처리가 끝나면
다시 돌아와서 후처리 작업을 수행하고 응답하게 된다. (DispatcherServlet이랑 유사하다)

 

 

- 보통은 필터가 1개이지만 2개 이상의 필터를 사용할 수도 있다. (필터가 여러 개일때)

* 처리 과정

클라이언트 요청이 오면 앞에 있는 필터 Filter1이 먼저 받고 전처리를 한다. 어떤 필터일지는 모르지만 그 다음 연결된 
필터를 호출하면 다음 필터 Filter2의 전처리가 호출이 된다. 

그 다음에 다음 필터 혹은 서블릿을 호출한다. (다음에 연결된 필터가 없다면 서블릿 호출)

그러면 서블릿이 처리를 한 다음 돌아와서 Filter2에서 후처리를 하고 다시 그 앞으로 돌아와서 Filter1에서 후처리를 한 후 
응답을 하게 된다. 

서블릿에서 응답을 하는건데 응답을 하면서도 2번의 후처리를 겪게 된다. 

1) filter1 전처리
2) filter2 전처리

3) 서블릿 처리

4) filter2 후처리

5) filter3 후처리 

 

 

 

* 아래 예제에서는 3개의 관심사가 존재한다. 

하나의 메소드에 여러 개의 관심사가 존재하므로 분리가 필요하다. 

urlPatterns = /* : 필터의 적용대상 지정(*찍으면 모든 요청에 필터 적용)

 

여러 서블릿(컨트롤러)에 공통 코드를 추가하여 분리해준다. 

 

 

<실습>

- PerformanceFilter 클래스 생성

package com.fastcampus.ch2;

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;

@WebFilter(urlPatterns = "/*") // 모든 요청에 PerformanceFilter 적용.
public class PerformanceFilter implements Filter {
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException, ServletException {
        // 1. 전처리 작업
        long startTime = System.currentTimeMillis();

        // 2. 서블릿(컨트롤러)또는 다음 필터
        filterChain.doFilter(request, response);

        // 3. 후처리 작업
        long endTime = System.currentTimeMillis();
        System.out.print("["+ ((HttpServletRequest)request).getRequestURI() + "]"); // 어떤걸 호출했을 때 소요시간이 얼마인지 알도록 함
        System.out.println("time=" + (endTime-startTime));

    }
}
@WebFilter(urlPatterns = "/*")

이건 서블릿에서 쓰는 필터이기 때문에 한 가지 작업을 더 해줘야한다. 

Ch2Application의 클래스 앞에 @ServletComponentScan을 지정해주어야 한다.

이 어노테이션을 넣어야 PerformanceFilter를 자동으로 스캔해서 등록해줄 수 있다.   

 

이후 실행한 결과를 확인해보자. 

루트를 요청했지만 페이지와 링크되어 있는 파일들도 다 요청이 따로 간다. 
페이지를 하나 요청한다고 자동으로 같이 오는게 아니라 별개의 개별 요청이고, 개별 요청이 따로 자동으로 가는 것을 알 수 있다. 

 

 

2. Interceptor

- Filter와 유사한 기능. Filter와 달리 WAC내에 위치. 빈 주입 가능

Filter는 Servlet Context 안에 있는데, Interceptor는 WebApplicationContext 내에 있다. 

둘 다 저장소지만 Servlet Context는 Servlet 저장소이고, WebApplicationContext는 Spring에서 사용하는 저장소이다. 

거의 Interceptor나 Filter 둘 다 거의 같지만, Interceptor에서는 WebApplicationContext안에 존재하는 Bean(자바 객체)를 
다 이용할 수 있다. 
하지만, Servlet Context에서는 Bean에 접근할 수 없다. 
+ Interceptor가 매개 변수도 더 많다. 
그래서 Filter가 발전된 것이 Interceptor라고 볼 수 있다. 

 

Spring에서는 둘 다 써도 되지만, 기본적으로 Interceptor를 더 많이 사용한다. 

요청이 오면 DispatcherServlet이 받아서 해당 컨트롤러의 메소드로 가기 전에 Interceptor를 거친다. 
전처리 후 Controller 메소드 호출하고 처리한 다음에 다시 돌아와서 후처리한 다음 그 결과를 DispatcherServlet에게 
전달을한다. 
WebApplicationContext은 Spring 컨테이너라고도 한다. 

 

<실습>

- PerformanceInterceptor 생성

package com.fastcampus.ch2;

import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@Component
public class PerformanceInterceptor implements HandlerInterceptor { // 단일 책임의 원칙(SRP) - 하나의 메소드는 하나의 책임만 갖는다.
//    long startTime; // iv  인스턴스 변수. 싱글톤(하나의 객체)이라서 여러 쓰레드가 하나의 객체를 공유.

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 1. 전처리 작업
        long startTime = System.currentTimeMillis();
        request.setAttribute("startTime", startTime); // request객체에 startTime을 저장

        // handler - 요청하고 연결된 컨트롤러의 메소드
        HandlerMethod method = (HandlerMethod) handler;
        System.out.println("method.getMethod() = " + method.getMethod()); // URL하고 연결된 메소드
        System.out.println("method.getBean() = " + method.getBean()); // URL하고 연결된 메소드

        // return true; // 다음 인터셉터나 컨트롤러를 호출 false면 호출 안함.
        return HandlerInterceptor.super.preHandle(request, response, handler);
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        // 2. 후처리 작업
        long startTime = (long)request.getAttribute("startTime"); // 객체를 저장하기 때문에 long타입 형변환 필요
        long endTime = System.currentTimeMillis();
        System.out.print("["+ ((HttpServletRequest)request).getRequestURI() + "]"); // 어떤걸 호출했을 때 소요시간이 얼마인지 알도록 함
        System.out.println("time=" + (endTime-startTime));

        HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
    }
}

HandlerInterceptor 인터페이스 구현 후 메소드 구현
preHandle - 전처리 / postHandle - 후처리 메소드를 구현한다. 

-> 메소드가 분리된다.  

 

preHandle의 지역 변수를 postHandle로 전달해야 한다. request객체에 저장하면 그 결과가 postHandle까지 전달이 되기 때문에 request객체에 저장해준다. 

 

handler - 요청하고 연결된 컨트롤러의 메소드. handler 매개변수를 통해 메소드에 대한 정보를 얻을 수 있다. 

@Component 어노테이션을 이용해서 자동으로 PerformanceInterceptor가 ApplicationContext의 Bean이 되게 하는게 
먼저이다. 


그 다음 설정 파일 WebMvcConfig 클래스 하나 생성한다. 

package com.fastcampus.ch2;

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new PerformanceInterceptor()) // interceptor로 등록
                .addPathPatterns("/**") // interceptor가 적용될 대상을 정함. -> 모든 대상
                .excludePathPatterns("/css/**", "/js/**"); // 제외할 것. css파일이나 js파일 제외
    }
}

설정 파일임을 알려주는 @Configuration 어노테이션을 붙여준다음 WebMvcConfigurer인터페이스를 구현한 후 

addInterceptors() 메소드를 구현한다. 

콘솔창에서 결과를 확인할 수 있는데, [/]time=9는 interceptor가 출력한것이고, 그 아래는 filter가 출력한 것이다. 

filter는 2개의 요청을 처리했는데 interceptor는 하나의 요청만 처리한 것은 적용 제외 대상 파일들을 지정해줬기 때문이다.