본문 바로가기

Java

Interceptor

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 컨테이너에 있다면 네트워크 통신 지연이 포함될 수 있다.

'Java' 카테고리의 다른 글

AOP  (1) 2025.07.24
N + 1  (2) 2025.07.22
Spring 정리  (0) 2025.07.19
자바 계산기 구현  (4) 2025.07.12
추상화와 상속  (2) 2025.07.10