서버에서 Log 기능은 매우 중요합니다. 예상치 못한 에러로 인해 서버가 다운되거나, 유저 요청이 특정 로직을 통과하지 못했을 때 Log를 통해 데이터를 복구하고 원인을 분석할 수 있습니다.

그렇다면 모든 로직마다 Log 기능을 추가해야 할까요?

매번 동일한 코드를 구현한다면 개발자가 실수할 가능성이 높아지고, 코드를 이해하기도 어려워질 것입니다. 이를 해결하기 위한 패러다임이 바로 AOP(Aspect-Oriented Programming)입니다.


AOP(Aspect-Oriented Programming)란?

AOP관심사 분리(Separation of Concerns)를 통해 코드의 유지보수성을 높이는 프로그래밍 패러다임입니다.

관심사 분리란 무엇인가?

코드를 설계할 때는 보통 두 가지로 구분하여 고민합니다:

  1. 핵심 로직: 요구사항을 처리하는 주요 로직.
  2. 부가 로직: 서비스의 정확한 동작을 지원하기 위한 부가적인 로직.

핵심 로직은 요구사항마다 달라지므로 개발자가 집중해서 개발해야 합니다. 반면, 부가 로직은 여러 요구사항에서 공통적으로 사용되는 기능입니다. 부가 로직을 매번 구현하기보다는 한 번 구현하여 핵심 로직에 적용할 수 있다면, 개발의 효율성과 유지보수성이 높아질 것입니다.

이러한 아이디어에서 출발한 패러다임이 바로 AOP입니다.


Spring AOP 설치하기

Gradle를 통해서 간단하게 라이브러리를 설치할 수 있습니다.

implementation 'org.springframework.boot:spring-boot-starter-aop'

PointCut: 부가 로직 적용 대상 지정

AOP에서는 PointCut을 사용하여 부가 로직이 적용될 대상을 명시합니다. 쉽게 말해, 부가 로직이 실행될 객체와 메서드를 지정하는 것입니다.

예제 코드

@Slf4j
@Aspect
@Component
public class LogPointcut {
    @Pointcut("execution(* eumsun.backend.controller.NotTokenController.*(..))")
    public void notToken(){
    }
}

 

코드 분석

  • @Aspect: 해당 클래스가 AOP 관련 코드를 포함하고 있음을 명시합니다. 이 어노테이션이 붙은 클래스는 Advisor로 등록됩니다.
  • @Pointcut: 부가 로직이 적용될 대상을 지정하며, AspectJ 표현식을 사용합니다.

Advice: 부가 로직 실행 시점 지정

Advice는 부가 로직이 실행되는 시점을 지정하는 어노테이션입니다.

@Slf4j
@Aspect
@Component
@RequiredArgsConstructor
public class LogAspect {
    private final LogService logService;

    @Around("eumsun.backend.config.log.LogPointcut.notToken()")
    public Object beforeAdviceNotTokenLog(ProceedingJoinPoint joinPoint) throws Throwable {
        return tryLog(joinPoint, this::notTokenLogging);
    }
    
    private Object tryLog(ProceedingJoinPoint joinPoint, Consumer<ProceedingJoinPoint> logMethod) throws Throwable {
        Object result = joinPoint.proceed();
        logMethod.accept(joinPoint);
        return result;
    }
    
    private void notTokenLogging(ProceedingJoinPoint joinPoint) {
        String requestBody = getRequestBody();
        viewLog(joinPoint, requestBody);
        createLog(joinPoint, requestBody);
    }
}

 

코드 분석

  1. @Around:
    • 부가 로직이 핵심 로직의 전후에 실행됩니다.
    • 가장 강력한 Advice 어노테이션입니다.
  2. ProceedingJoinPoint:
    • 유저 요청에 대한 정보를 담고 있는 객체로, 핵심 로직을 실행하거나 파라미터 정보를 얻을 수 있습니다.

만약 RequestBody를 Log에 찍고 싶다면 다음과 같은 클래스를 추가해야 한다.

더보기
더보기

RequestBody 캐싱을 위한 설정

유저의 RequestBody를 로그에 기록하려면 추가 설정이 필요합니다. 기본적으로 RequestBody는 한 번 읽으면 사라지기 때문에 캐싱해야 합니다.

필터 추가

@Component
public class RequestCacheFilter extends OncePerRequestFilter {
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
            throws ServletException, IOException {
        if (!(request instanceof ContentCachingRequestWrapper)) {
            request = new ContentCachingRequestWrapper(request);
        }
        filterChain.doFilter(request, response);
    }
}

코드 분석

  • OncePerRequestFilter를 상속하여 RequestBody를 캐싱합니다.
  • ContentCachingRequestWrapper를 사용해 데이터를 한 번 읽어도 이후에 다시 사용할 수 있도록 만듭니다.

마무리

AOP를 활용하여 부가 로직핵심 로직을 분리하면, 개발자는 부가 로직에 시간을 할애하지 않고 핵심 로직에 집중할 수 있습니다. 이를 통해 더욱 완성도 높은 서비스를 제공할 수 있습니다.

 

혹시라도 틀린 내용이 있다면 댓글로 알려주시면 감사하겠습니다!!

'Spring > AOP' 카테고리의 다른 글

[Spring] AOP로 MultiThread 구현하기  (1) 2025.05.28

+ Recent posts