[Spring] AOP를 이용하여 Logging 구현

Donghoon Jeong·2023년 11월 20일
2

Spring

목록 보기
11/15

저번 포스팅에서 AOP의 개념과 간단한 예제 코드를 통해 동작방법과 흐름에 집중해서 알아보았습니다. 이번 포스팅에서는 AOP를 사용하여 Logging을 구현해 보고 동작원리 또한 자세하게 알아보겠습니다.


AOP를 이용하여 Logging 구현

1. 의존성 추가

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

2. @EnableAspectJAutoProxy 추가

@SpringBootApplication
@EnableJpaAuditing
@EnableAspectJAutoProxy
public class ToDoApplication {

    public static void main(String[] args) {
        SpringApplication.run(ToDoApplication.class, args);
    }

}

3. LoggingAspect.java 생성

@Aspect
@Component
@Slf4j
public class LoggingAspect {

    @Pointcut("execution(* com.example.todo.controller..*.*(..))")
    private void cut(){}

    @Around("cut()")
    public Object aroundLog(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
    
        // 메서드 정보 받아오기
        Method method = getMethod(proceedingJoinPoint);
        log.info("======= method name = {} =======", method.getName());

        // 파라미터 받아오기
        Object[] args = proceedingJoinPoint.getArgs();
        if (args.length == 0) log.info("no parameter");
        for (Object arg : args) {
            log.info("parameter type = {}", arg.getClass().getSimpleName());
            log.info("parameter value = {}", arg);
        }

        // proceed()를 호출하여 실제 메서드 실행
        Object returnObj = proceedingJoinPoint.proceed();

        // 메서드의 리턴값 로깅
        log.info("return type = {}", returnObj.getClass().getSimpleName());
        log.info("return value = {}", returnObj);

        return returnObj;
    }


    private Method getMethod(ProceedingJoinPoint proceedingJoinPoint) {
        MethodSignature signature = (MethodSignature) proceedingJoinPoint.getSignature();
        return signature.getMethod();
    }

}

해당 코드는 스프링 AOP를 이용하여 로깅 기능을 구현한 것입니다.

  • @Aspect, @Component, @Slf4j
    @Aspect은 이 클래스가 Aspect로서 동작할 수 있게 하고, @Component는 스프링 빈으로 등록될 것을 나타내는 어노테이션들입니다. 또한, @Slf4j를 통해 로깅 기능을 사용할 수 있습니다.

  • @Pointcut
    Pointcut을 설정해주는 부분입니다. 이 설정에 따라 com.example.todo.controller 패키지 아래의 모든 메소드가 AOP의 적용 대상이 됩니다.

  • @Around
    @Around가 붙은 aroundLog 메소드는 실제 메소드 호출 전후에 수행되는 Advice입니다. 여기서는 메소드의 이름과 파라미터를 로깅하고, proceed() 메소드를 통해 실제 메소드를 호출한 후, 그 결과를 로깅합니다.

  • proceed()
    실제로 프록시 객체가 원본 객체의 메소드를 호출하는 부분입니다. ProceedingJoinPoint 인터페이스의 메소드로, Around Advice에서 사용됩니다.
    이 메소드를 호출하면 프록시 객체는 원본 객체의 메소드를 실행하게 됩니다.
    즉, Object returnObj = proceedingJoinPoint.proceed(); 코드는 프록시 객체가 원본 TodoService의 메소드를 호출하고, 그 결과를 returnObj에 저장하는 것을 의미합니다.

  • getMethod(ProceedingJoinPoint proceedingJoinPoint)
    ProceedingJoinPoint로부터 메소드의 정보를 가져오는 메소드입니다.

따라서 이 코드는 com.example.todo.controller 패키지 아래의 모든 메소드를 호출할 때마다 메소드 이름, 파라미터, 반환값을 로깅하는 기능을 수행합니다.


결과

/todo

todo를 등록하는 /todo [POST]요청을 보냈을 때

/todo/{id}

todo를 조회하는 /todo/{id} [GET]요청을 보냈을 때

메소드의 파라미터 정보와 반환값에 대한 정보가 로그로 찍히는 것을 확인할 수 있습니다.


동작 원리

이전 AOP 관련 글에서 스프링 AOP는 실제 빈이 아닌 프록시 빈을 주입하여 사용한다고 언급했는데, 스프링 AOP의 동작 원리를 이해하기 위해 실제로 어떤 빈이 주입되는지 확인해보겠습니다.

Controller

@RestController
@RequestMapping("/todo")

public class TodoController {

    private final TodoService todoService;

    public TodoController(TodoService todoService) {
        this.todoService = todoService;
        System.out.println("todoService = " + todoService.getClass());
    }
    
    ...
    
}

Log

위 코드에서는 TodoController의 생성자에 TodoService 빈이 주입되는데, 실제로는 스프링 AOP가 적용되면 실제 TodoService 빈 대신 프록시 객체가 주입됩니다. 프록시 객체는 TodoService를 구현하므로, TodoController는 이 객체가 실제 객체인지 프록시 객체인지 구분하지 않고 사용할 수 있습니다.

실제 빈이 아닌 프록시 빈이 주입된 것을 통해 AOP가 정상적으로 적용되었음을 확인할 수 있습니다.

profile
정신 🍒 !

0개의 댓글