본문 바로가기

백엔드/Spring

[Spring] 스프링 AOP를 이용한 메서드 로그 추적기 구현

 

 

 🔍 서론

 

메서드 로그 추적기를 구현하기 전에 스프링 AOP의 개념에 대해 간단히 말하자면, 스프링 AOP(Aspect-Oriented Programming)는 관점 지향 프로그래밍을 의미하며, 프로그래밍에서 공통적으로 사용되는 기능을 분리하여 관리할 수 있게 해주는 기능을 수행한다. 공통 관심 사항으로는 로깅, 보안, 트랜잭션 등이 있다.

 

AOP는 특정 작업을 수행하는 애플리케이션의 공통된 코드를 별도의 모듈(Aspect)로 분리하고, 애플리케이션 실행 중에 특정 지점(Join Point)에서 이 모듈의 코드가 실행되도록 한다. 이러한 방식은 소프트웨어 개발에서 중요한 원칙 중 하나인 "관심사의 분리(Seperation of Concerns, SoC)"를 실현한다.

 

직접 구현한 메서드 로그 추적기 코드는 아래와 같다.

 

 

 

 💻 프로젝트

 

@Slf4j
@Aspect
@Component
public class MyAspect {

    @Around("execution(* com.khi.server..*(..))")
    public Object doLog(ProceedingJoinPoint joinPoint) throws Throwable {

        String[] fullLogArr = String.valueOf(joinPoint.getSignature()).split("\\.");
        String className = fullLogArr[fullLogArr.length-2];
        String methodName = fullLogArr[fullLogArr.length-1].split("\\(")[0];

        log.info("[MyLog] " + "(" + className + ") " + methodName + " 메서드 호출");

        return joinPoint.proceed();
    }
}

 

위의 로그 추적기는 joinPoint.getSignature() 메서드를 호출했을 때 얻어지는 값을 조작해서 내가 원하는 값인 클래스 명과 메서드 명 만 가공해서 로그를 남긴다. 포인트 컷은 내 패키지의 프로젝트가 전부 포함되도록 작성하였다. 

 

 

AuthenticationManager com.khi.server.security.configuration.SecurityConfig.authManagerProvider(HttpSecurity)

 

메서드가 호출된 상황에서 joinPoint.getSignature() 메서드를 호출하면 위와 같은 값이 출력되는데, 여기서 내가 알고 싶은 값은 뒤에서 두 번째 값인 클래스 이름과, 뒤에서 첫 번째 값인 메서드 이름이다. 따라서 값을 분리해서 className, methodName 변수에 넣어서 이 값만 출력되게끔 하였다.

 

 

Aspect를 등록하는 방법으로는 Aspect에 @Component 어노테이션을 붙여서 Aspect 자체를 스프링 빈으로 등록하는 방식과, Aspect를 빈으로 등록하는 Configuration을 구성하는 방식 등이 있는데, 따로 Configuration을 구성하면 아래와 같이 포인트 컷을 지정할 때 Configuration 클래스는 제외시켜야 한다.

@Around("execution(* com.khi.server..*(..)) && !target(com.khi.server.aop.configuration.AopConfig)")

 

포인트 컷 지정에서 Configuration 클래스를 제외하지 않을 경우, Configuration 클래스에서 자기 자신인 Aspect를 생성하는 메서드도 AOP로 처리되므로 순환 참조에 빠지게 된다. 따라서, 컴포넌트 스캔 방식으로 Aspect를 스프링 빈에 직접 등록하면, Configuration 클래스를 따로 만들 필요도 없고, 포인트 컷을 복잡하게 작성하지 않아도 된다.

 

 

 

 🎛️ 동작 확인 및 결론

 

2024-02-14T14:10:29.075+09:00  INFO 2005 --- [nio-8080-exec-2] com.khi.server.aop.MyAspect              : [MyLog] (UsernamePasswordAuthProvider) supports 메서드 호출
2024-02-14T14:10:29.075+09:00  INFO 2005 --- [nio-8080-exec-2] com.khi.server.aop.MyAspect              : [MyLog] (UsernamePasswordAuthProvider) authenticate 메서드 호출
2024-02-14T14:10:29.075+09:00  INFO 2005 --- [nio-8080-exec-2] com.khi.server.aop.MyAspect              : [MyLog] (UserDetailsServiceImpl) loadUserByUsername 메서드 호출
2024-02-14T14:10:29.075+09:00  INFO 2005 --- [nio-8080-exec-2] com.khi.server.aop.MyAspect              : [MyLog] (UserRepository) findUserByEmail 메서드 호출
2024-02-14T14:10:29.215+09:00  INFO 2005 --- [nio-8080-exec-2] com.khi.server.aop.MyAspect              : [MyLog] (JwtTokenProvider) createJwt 메서드 호출
2024-02-14T14:10:29.251+09:00  INFO 2005 --- [nio-8080-exec-2] com.khi.server.aop.MyAspect              : [MyLog] (UserController) signin 메서드 호출
2024-02-14T14:10:29.251+09:00  INFO 2005 --- [nio-8080-exec-2] com.khi.server.aop.MyAspect              : [MyLog] (UserService) signin 메서드 호출

 

로그인을 시도 할 경우 위와 같이 의도한 대로 클래스명과 메서드명이 출력되는 것을 확인할 수 있다. 하지만, 이상한 부분이 있다. 직접 프로젝트에 적용해 보면 알 수 있지만, 동작 과정 중 실행되는 모든 클래스 또는 메서드가 출력되지 않는다는 것이다. 왜 이런 것일까?

 

그 이유는, 객체들이 스프링 빈으로 등록되는 과정에서 프록시 객체로 변경되어 등록되는데, 여기서 프록시를 통해 AOP를 적용시키기 때문이다. 즉 프록시 방식을 사용하는 스프링 AOP는 스프링 컨테이너가 관리할 수 있는 스프링 빈에만 AOP를 적용할 수 있다. 따라서, 스프링 컨테이너에 의해 관리되지 않는 객체들은 스프링 AOP의 프록시 기반 접근 방식을 통한 AOP 처리를 적용받을 수 없으므로, 로그에 남지 않는 것이다.