저번 포스팅에서 AOP의 개념과 간단한 예제 코드를 통해 동작방법과 흐름에 집중해서 알아보았습니다. 이번 포스팅에서는 AOP를 사용하여 Logging을 구현해 보고 동작원리 또한 자세하게 알아보겠습니다.
implementation 'org.springframework.boot:spring-boot-starter-aop'
@SpringBootApplication
@EnableJpaAuditing
@EnableAspectJAutoProxy
public class ToDoApplication {
public static void main(String[] args) {
SpringApplication.run(ToDoApplication.class, args);
}
}
@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에 저장하는 것을 의미합니다.
따라서 이 코드는 com.example.todo.controller 패키지 아래의 모든 메소드를 호출할 때마다 메소드 이름, 파라미터, 반환값을 로깅하는 기능을 수행합니다.
todo를 등록하는 /todo [POST]요청을 보냈을 때
todo를 조회하는 /todo/{id} [GET]요청을 보냈을 때
메소드의 파라미터 정보와 반환값에 대한 정보가 로그로 찍히는 것을 확인할 수 있습니다.
이전 AOP 관련 글에서 스프링 AOP는 실제 빈이 아닌 프록시 빈을 주입하여 사용한다고 언급했는데, 스프링 AOP의 동작 원리를 이해하기 위해 실제로 어떤 빈이 주입되는지 확인해보겠습니다.
@RestController
@RequestMapping("/todo")
public class TodoController {
private final TodoService todoService;
public TodoController(TodoService todoService) {
this.todoService = todoService;
System.out.println("todoService = " + todoService.getClass());
}
...
}
위 코드에서는 TodoController의 생성자에 TodoService 빈이 주입되는데, 실제로는 스프링 AOP가 적용되면 실제 TodoService 빈 대신 프록시 객체가 주입됩니다. 프록시 객체는 TodoService를 구현하므로, TodoController는 이 객체가 실제 객체인지 프록시 객체인지 구분하지 않고 사용할 수 있습니다.
실제 빈이 아닌 프록시 빈이 주입된 것을 통해 AOP가 정상적으로 적용되었음을 확인할 수 있습니다.