AOP (Aspect Oriented Programming) 개념 및 적용하기

BlackHan·2023년 11월 18일
0

AOP란

Aspect Oriented Programming의 약자로 관점 지향 프로그래밍을 말한다. 이는 핵심 비즈니스 로직과 공통 로직을 분리해서 응집도가 높게 개발할 수 있게한다. 핵심 기능을 관리하는 코드는 건드리지 않으면서 여러 핵심 기능들이 활용할 수 있는 기능을 만드는데 초점을 맞춘다. 예를 들면, 한 메서드의 실행 시간을 측정하고 싶다고 가정한다.
매번 메서드들에 시간 측정 로직을 넣어줄 수 있지만, 이 로직을 외부로 빼와서 각 메서드들에게 적용시켜 주는 방법을 말한다. 횡단 관점에서 바라보아서 공통된 로직을 외부로 꺼낸다.

장점

  • 같은 코드 조각을 여러 모듈에서 중복해서 작성하지 않도록 해 준다.
  • 코드의 재사용성 및 모듈화가 용이해진다.
  • 각각의 모듈에 수정이 필요하면 다른 모듈의 수정 없이 해당 로직만 변경하면 된다.
  • 핵심 로직과 부가 기능의 분리로 코드의 가독성을 향상시킨다.
  • 특정 관심사에 대한 단위 테스트를 쉽게 작성하고 유지할 수 있다.

AOP를 진행하기전에 용어부터 정리한다.

개발에서의 모듈화란 코드의기능의 논리적인 부분을 더 작은 조각으로 나누고, 각각의 조각을 따로 구현하고 관리함으로써 코드의 가독성, 재사용성, 유지보수성을 높이는 것을 의미한다.
여기서 JoinPoint란 프로그램에서 메소드 호출, 예외 발생 등의 시점을 지칭한다. 한 프로그램에는 여러 JoinPoint가 있고, 여기에서 Aspect를 적용할 수 있다.

Pointcut이란 여러 JoinPoint들 중 Advice가 실제로 적용되는 포인트를 정의한다.

Advice는 Join Point에서 실제로 실행할 기능을 나타내며, 부가 기능이 언제 실행될지를 정의하는 모듈이다. Join Point의 전(Before), 후(After), 전후(Around) 등의 정보가 있다.

Aspect는 어드바이스와 포인트컷을 합친 것으로 횡단 관점에 적용할 기능을 모듈화한 단위이다. 즉, 어떤 상황에서 어떤 부가 기능을 적용할 것인지를 정의한다.


이제 SpringBoot를 이용하여, AOP를 진행해 본다.

먼저 의존성을 추가한다.

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

그리고 Aspect를 정의한다. 로그를 남기는 기능을 만들어본다.

@Aspect
@Component
@Slf4j
public class LoggingAspect {
    @Before("this(com.example.aop.AopController)")
    public void logParameters(JoinPoint joinPoint) {
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        log.info("method description: [{}]", methodSignature.getMethod());
        log.info("method name: [{}]", methodSignature.getName());
        log.info("declaring type: [{}]", methodSignature.getDeclaringType());

        Object[] arguments = joinPoint.getArgs();
        if (arguments.length == 0) {
            log.info("no arguments");
        }
        for (Object argument: arguments){
            log.info("argument: [{}]", argument);
        }
    }
}

여기서 기억해야 할 것은 먼저 Aspect임을 알리는 @Aspect와 Spring Bean으로 등록하는 어노테이션이 있어야한다. 그리고 @Before와 같은 Advice를 사용하여 어느 시점에서 실행할지를 정한다.

Advice의 실행 시점 설정으로는 다음이 있다.

  • @Before : 특정 Join Point 이전에 Advice를 실행합니다. 이때 정의하는 메소드 인자는 Join Point에 대한 정보를 담고 있는 JoinPoint 를 추가한다.
  • @After : 특정 Join Point 이후에 Advice의 코드를 실행하도록 구성한다. 마찬가지로 JoinPoint 를 추가한다.

  • @Around : 특정 Join Point의 전, 후에 Advice의 코드를 실행하도록 구성한다. 이때, Join Point의 실제 코드를 실행하는 시점을 특정지어 주어야 하는데, 이를 위해 ProceedingJoinPoint 를 활용한다.

  • @AfterReturning : 특정 Join Point가 값을 반환한 뒤에 Advice를 실행한다. 이때, 메소드가 반환값을 가지고 있으면 해당 반환값을 메소드의 인자로 추가할 수 있다.
  • @AfterThrowing : 특정 Join Point가 예외를 발생시킬 때 Advice를 실행한다. 이때 발생한 예외를 메소드의 인자로 추가할 수 있다.

this("com.example.aop.AopController")는 Advice에 PointCut의 정보를 전달하며 Pointcut Designator라고 부른다. this 이외에도 within을 이용할 수 있고, within(com.example.aop..*) 처럼 패키지 단위로도 정의할 수 있다.

JointPoint는 이 Advice가 실행된 Join Point에 대한 정보가 담긴 객체이다.
MethodSignature는 실행된 메소드의 정보를 담는 객체이다. 여기서 메소드 이름 등 다양한 정보를 확인할 수 있다.

결과

이제 Postman으로 데이터를 넣고 해당 add를 실행시켜 준다.

결과는 아래처럼 나온다. 데이터가 잘 들어간 것과 method name, method description 등이 잘 나오는 것을 확인할 수 있다.

AOP를 사용하지 않고도, 요청과 응답의 전후로 코드를 실행할 수 있는 방법은 HandlerInterceptor와 Filter를 이용한 방법이 있다.
이는 다음에 Proxy가 무엇인지와 함께 알아본다.

profile
Slow-starter

0개의 댓글