서버에서 Log 기능은 매우 중요합니다. 예상치 못한 에러로 인해 서버가 다운되거나, 유저 요청이 특정 로직을 통과하지 못했을 때 Log를 통해 데이터를 복구하고 원인을 분석할 수 있습니다.
그렇다면 모든 로직마다 Log 기능을 추가해야 할까요?
매번 동일한 코드를 구현한다면 개발자가 실수할 가능성이 높아지고, 코드를 이해하기도 어려워질 것입니다. 이를 해결하기 위한 패러다임이 바로 AOP(Aspect-Oriented Programming)입니다.
AOP(Aspect-Oriented Programming)란?
AOP는 관심사 분리(Separation of Concerns)를 통해 코드의 유지보수성을 높이는 프로그래밍 패러다임입니다.
관심사 분리란 무엇인가?
코드를 설계할 때는 보통 두 가지로 구분하여 고민합니다:
- 핵심 로직: 요구사항을 처리하는 주요 로직.
- 부가 로직: 서비스의 정확한 동작을 지원하기 위한 부가적인 로직.
핵심 로직은 요구사항마다 달라지므로 개발자가 집중해서 개발해야 합니다. 반면, 부가 로직은 여러 요구사항에서 공통적으로 사용되는 기능입니다. 부가 로직을 매번 구현하기보다는 한 번 구현하여 핵심 로직에 적용할 수 있다면, 개발의 효율성과 유지보수성이 높아질 것입니다.
이러한 아이디어에서 출발한 패러다임이 바로 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);
}
}
코드 분석
- @Around:
- 부가 로직이 핵심 로직의 전후에 실행됩니다.
- 가장 강력한 Advice 어노테이션입니다.
- 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 |
---|