Spring boot에서 Interceptor는 요청을 처리하기 전과 후에 실행되는 코드
- 컨트롤러에 도달하기 전과 후에 사용자 지정 작업을 수행할 수 있다.
- Interceptor은 로깅, 인증, 인가, 데이터 변환 등의 공통 작업을 수행하는데 사용
Interceptor 구현org.springframework.web.servlet.HandlerInterceptor
이 인터페이스에는 세 가지 메서드가 존재preHandle() : 컨트롤러가 실행되기 전에 호출되는 메서드postHandle() : 컨트롤러가 실행 후 호출되지만, View가 렌더링되기 전에 호출되는 메서드afterCompletion() : 뷰 렌더링이 완료된 후 호출되는 메서드
Filter과 차이점
Filter은 DispatchServlet 처리 전과 후에 동작하여 사용자의 요청이나 응답의 최전방에 존재
스프링의 독자적인 기능이 아닌 자바 서블릿에서 제공
공통점 : 특정 URI에 접근할 때 제어하는 용도로 사용된다는 공통점
차이점 : 실행 시점에 속하는 영역이 다르다
- Filter : 웹 애플리케이션 내에서 동작하므로 스프링의 Context를 접근하기 어렵다.
- Interceptor : 스프링 컨텍스트 내에 존재해서 생성된 빈들에 자유롭게 접근 가능
흐름
HTTP 요청 -> WAS -> 필터 -> 서블릿 -> 스프링 인터셉터 -> 컨트롤러
주요 사용 사례
인증 및 권한 검사
- 사용자가 요청한 리소스에 접근할 수 있는 권한이 있는지 확인
- 로그인 상태 확인
로깅 및 성능 측정 - 요청의 시작 시간과 종료 시간을 기록하여 처리 시간을 계산
- 요청 응답 로그를 기록
데이터 변환 - 요청 데이터나 응답 데이터를 동적으로 수정
- 헤더 추가, 요청 데이터 가공
로깅
소프트웨어 시스템에서 발생하는 모든 행위와 이벤트 정보를 시간 순서대로 기록하는 작업
프로그램의 실행 흔적
- 디버깅
- 성능 모니터링
- 감사 및 추적
- 로그 레벨*
- ERROR : 심각한 문제, 즉시 조치를 취해야하는 로그
- WARN : 로직 상 유효성 확인, 예외 처리가 필요한 경우 사용
- INFO : 운영에 참고할 만한 사항이나 중요한 비즈니스 프로세스가 완료되었을 때 사용
- DEBUG : 개발 단계에서 사용, SQL 쿼리 로깅 등을 기록, 시스템 내부 동작 이해하는데 사용
- TRACE : 개발 단계에서 사용, 모든 레벨에 대한 상세한 로깅 포함, 세부적인 실행 흐름 추적에 사용
- SLF4J*
다양한 로깅 라이브러리를 통합하여 인터페이스로 제공한 추상화 라이브러리
로깅 클래스
package com.example.springprepare.interceptor;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.MDC;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import java.util.UUID;
@Slf4j
@Component
public class LogInterceptor implements HandlerInterceptor {
private static final String TRACE_ID = "TRACE_ID";
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//고유 요청 ID 생성
String traceId = UUID.randomUUID().toString().substring(0, 8);
//MDC에 traceId 설정
MDC.put(TRACE_ID, traceId);
log.info("--> {} {} {}", request.getRequestURI(), request.getQueryString(), request.getMethod());
return HandlerInterceptor.super.preHandle(request, response, handler);
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
log.info("--> {} {} {} {}", request.getRequestURI(), request.getQueryString(), request.getMethod(), response.getStatus());
//요청 처리가 끝났으므로 MDC 정보 제거
MDC.remove(TRACE_ID);
}
}MDC
- 멀티 스레드 환경에서 로그를 효과적으로 추적하기 위한 기술
- 고유한 ID를 발급하여 해당 스레드에 모든 로그에 ID가 자동으로 찍히게 된다.
package com.example.springprepare.interceptor;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import java.util.Optional;
@Slf4j
@Component
public class PerformanceInterceptor implements HandlerInterceptor {
private static final String START_TIME = "startTime";
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
long startTime = System.currentTimeMillis();
request.setAttribute(START_TIME, startTime);
return true;
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
Optional<Long> startTime = Optional.ofNullable((Long)request.getAttribute(START_TIME));
if (startTime.isPresent()) {
long endTime = System.currentTimeMillis();
long elapsedTime = endTime - startTime.get();
log.info("Elapsed time: {} ms", elapsedTime);
} else {
log.info("--> {} {} [STATUS: {}] (No startTime recorded)",
request.getRequestURI(), request.getMethod(), response.getStatus());
}
}
}Component
- 다른 곳에서도 주입 받을 수 있도록 component로 등록
First Request Panalty
처음으로 API를 호출하면 수행시간이 오래 걸림
- JVM JIT 컴파일 : 자바는 코드를 처음 실행할 때 인터프리터 방식으로 동작하다 자주 사용되는 코드를 JIT 컴파일 하여 성능을 높인다.
- 스프링의 지연 로딩 : 특정 빈은 처음 사용될 때 초기화
- DB 커넥션 풀 초기화 : DB와 연결통로를 여러개 만들어두는 커넥션 풀 사용
그 밖에 원인 - N+1 : 연관관계의 엔티티를 조회할 때 N개의 추가 쿼리가 발생
- 비효율적 쿼리 : 인덱스(index)가 걸려있지 않거나 데이터가 많거나의 이유
- 네트워크 지연 : 애플리케이션과 DB가 다른 서버 또는 다른 DOCKER 컨테이너에 있다면 네트워크 통신 지연이 포함될 수 있다.