새로운 내용을 공부할 때
새로운 내용의 공부를 시작할 때 용어의 정의를 이해하지 못하거나 정확하게 알지 못한다면 그 용어가 포함된 문장을 이해하지 못합니다.
작은 단어 하나가 내용을 이해하지 못하게 하기 때문에 용어를 정확하게 이해하는 것이 중요합니다.

3 분 소요

📌 2025-04-02 TIL

오늘의 학습 주제

  • 필터에서 예외가 발생 되면 어떻게 될까
  • 인터셉터에서 예외가 발생되면 어떻게 될까
  • 쓰레드풀과 예외처리

학습 내용

필터에서 예외가 발생하는 경우

필터에서 예외가 발생하는 경우를 눈으로 확인하려고 했습니다.

제가 기존에 가지고 있던 필터 처리 이후 동작 방식은 아래 방식으로 알고 있습니다.

WAS -> 필터 -> 서블릿-> 인터셉터 -> 컨트롤러 입니다.

여기서 필터나, 서블릿이나 , 인터셉터나 컨트롤러에서 예외가 발생하는 경우 또는 개발자가 직접 오류라로 지정할 수 있습니다.

public void errorage1(HttpRequest request){
  throw new IlligalArgmentsException("예외가 발생하면 처리가 된다.")
}
public void errorage2(HttpResponse httpResponse){
		httpResponse.sendError(HttpServletResponse.SC_NOT_FOUND);
}

예외를 던지게 되면 아래 로직은 실행되지 않고 오류 처리로 넘어 갑니다.

sendError은 트랜잭션 코드로 작성할때 롤백 선언하면 로직은 그대로 실행가능하지만 마스킹 처리되는 것과 동일합니다.

오류가 발생되면 발생 지점으로부터 스택 프레임을 타고 내려옵니다.

컨트롤러 > 인터셉터 > 서블릿 > 필터 > WAS

was(카타리나)는 예외가 발생되거나 response객체에 오류 마스킹을 있다면 예외 처리를 위해 포워딩을 합니다.

물론 톰캣이 지정한 방식이 있지만 커스텀도 가능합니다.

@Component
public class WebServerCustomizer implements WebServerFactoryCustomizer<ConfigurableWebServerFactory> {
	@Override
	public void customize(ConfigurableWebServerFactory factory) {
		ErrorPage errorPage404 = new ErrorPage(HttpStatus.NOT_FOUND, "/error-page/404");
		ErrorPage errorPage500 = new ErrorPage(HttpStatus.INTERNAL_SERVER_ERROR,"/error-page/500");
		ErrorPage errorPageEx = new ErrorPage(RuntimeException.class, "/error-page/500");
		factory.addErrorPages(errorPage404, errorPage500, errorPageEx);
	}
}

이렇게 웹서버 커스터마이징으로 에러를 처리하는 url을 지정하고 컨트롤러에서 처리하면 됩니다.

그런데 저 ErrorPage 첫번째 인자를 보면 상태코드가 보입니다.

// sendError 첫번째 인자도 status code 입니다.
httpResponse.sendError(HttpServletResponse.SC_NOT_FOUND);

그런데 뜬금없는 거지만 status code가 200번은 정상인데도 에러 처리로직을 태우도록 할 수 있습니다.

@Override
public void doFilter(jakarta.servlet.ServletRequest request, jakarta.servlet.ServletResponse response,
  FilterChain chain) throws IOException, ServletException {
  
  HttpServletRequest httpRequest = (HttpServletRequest)request;
  HttpServletResponse httpResponse = (HttpServletResponse)response;
  
  httpResponse.sendError(HttpServletResponse.SC_OK);
}

이렇게 요청을 하고 기다리면 응답은 아래와 같이 옵니다.

http GET http://localhost:8080/api/hello
HTTP/1.1 200 
Connection: keep-alive
Content-Type: application/json;charset=ISO-8859-1
Date: Wed, 02 Apr 2025 14:23:46 GMT
Keep-Alive: timeout=60
Transfer-Encoding: chunked

{
    "error": "OK",
    "path": "/api/hello",
    "status": 200,
    "timestamp": "2025-04-02T14:23:46.124+00:00"
}

오류라고 지정하게 되면 오류 객체에 양식에 맞는 반환 값과 코드를 만들어서 응답하게 됩니다.

인터셉터에서 예외가 발생되는 경우

@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse
  response, Object handler) throws Exception {
  String requestURI = request.getRequestURI();
  String uuid = UUID.randomUUID().toString();
  request.setAttribute(LOG_ID, uuid);
  //@RequestMapping: HandlerMethod
  //정적 리소스: ResourceHttpRequestHandler
  if (handler instanceof HandlerMethod) {
    HandlerMethod hm = (HandlerMethod) handler; //호출할 컨트롤러 메서드의 모든정보가 포함되어 있다.

  }
  log.info("REQUEST [{}][{}][{}]", uuid, requestURI, handler);
  response.sendError(HttpServletResponse.SC_OK);
  return true; //false 진행X
}

똑같이 호출이 됩니다.

http GET http://localhost:8080/api/hello
HTTP/1.1 200 
Connection: keep-alive
Content-Type: application/json
Date: Wed, 02 Apr 2025 14:38:03 GMT
Keep-Alive: timeout=60
Transfer-Encoding: chunked

{
    "error": "OK",
    "path": "/api/hello",
    "status": 200,
    "timestamp": "2025-04-02T14:38:03.634+00:00"
}

필터와 인터셉터의 차이

  • 필터는 서블릿 스펙에 속합니다. 주로 요청/응답의 전처리(인코딩, 로깅, 인증 등)에 사용되고, 인터셉터는 스프링 MVC에 특화되어 컨트롤러 호출 전/후에 세밀한 제어가 가능합니다.
  • 필터는 서블릿 컨테이너 레벨에서 동작하므로 예외가 발생하면 was가 직접 개입하게 됩니다.
  • 인터셉터는 스프링의 DispatcherServlet 내에서 동작하므로 ExceptionHandler와 같은 스프링 전용 예외 처리가 먼저 동작할 수 있습니다.

오류가 발생하는 경우

was는 오류가 발견하면 /error 주소로 포워드를 한다.

스프링 부트는 기본 에러 컨트롤러로 예외 포맷을 만들어서 반환을 한다.

이때 최초 사용자의 url을 coyoteRequest 객체를 통해서 확인할 수 있다.

그러면 was가 에러가 발생하여 저 방식이 아니라 다른 컨트롤러를 호출하게 하고 싶다면 방법이 있습니다.

@Component
public class WebServerCustomizer implements WebServerFactoryCustomizer<ConfigurableWebServerFactory> {
	@Override
	public void customize(ConfigurableWebServerFactory factory) {
		ErrorPage errorPage404 = new ErrorPage(HttpStatus.NOT_FOUND, "/error-page/404");
		ErrorPage errorPage500 = new ErrorPage(HttpStatus.INTERNAL_SERVER_ERROR,"/error-page/500");
		ErrorPage errorPageEx = new ErrorPage(RuntimeException.class, "/error-page/500");
		factory.addErrorPages( errorPage404, errorPage500, errorPageEx);
	}
}

was에서 오류로 dispatcherRequest를 할때 두번째 인자로 요청하게 됩니다.

톰캣 쓰레드풀에 대해서

톰캣은 쓰레드풀을 가지고 있습니다.

사용자의 요청일 올때마다 쓰레드풀에서 서블릿 컨테이너를 호출하여 소켓에서 요청 객체와 응답 객체를 만들어서 전달합니다.

// 톰캣 시작 시 (단일 인스턴스 초기화)
ServletContainer container = new ServletContainer();
container.initialize(); // 서버 시작 시 한 번만 실행

// 요청 처리 시
executor.execute(() -> {
    container.process(request, response); // 기존 인스턴스 사용
});

서블릿 컨테이너(카타리나)는 톰캣 서버가 시작될 때 한 번 초기화되며, 그 인스턴스가 쓰레드 풀의 각 쓰레드에서 재사용됩니다.

매 요청마다 새로 생성되지 않습니다.

정리

필터와 인터셉터에서 예외가 발생하면 was가 예외에 대한 값을 반환해준다.

댓글남기기