Spring AOP

이창근·2023년 8월 28일
0

Spring공부

목록 보기
6/9

AOP는 기존 OOP(Object-Oriented-Programming)를 보조하기 위해 등장한 개념으로, 횡단 관심사를 깔끔하게 처리하기 위해 등장하였다.

우리가 만약 기존 코드에 반복적으로 등장하는 부가기능을 추가하고자 한다고 생각해보자. 기존의 OOP는 애플리케이션을 기능을 중심으로 바라보았다. 따라서 코드조각도 기능을 중심으로 나뉘어있고, 반복적으로 등장하는 부가기능을 추가하려면 그 모든 조각들을 하나씩 정성을 들여 수정을 해야한다.
이런 부가기능을 횡단관심사라고 부른다. 우리 애플리케이션의 주요한 핵심기능은 아니지만, 여러 부분에서 반복적으로 등장하는 코드. 이 코드들을 핵심기능에서 분리하고, 한 곳에서 관리하도록 만들어주는 것이 바로 AOP이다.
먼저 이 AOP의 핵심 개념을 짚어보겠다.

  • Aspect : 횡단 관심사, 비즈니스 로직이 아니며 여러 객체에 걸쳐 반복적으로 등장하는 관심 사항이다. 적용할 기능을 뜻하는 Advice와 적용할 위치를 뜻하는 Pointcut이 합쳐진 것이다.
  • JoinPoint : 추상적인 개념, AOP가 적용될 수 있는 위치를 모두 뜻한다. 메서드 실행, 생성자 호출 등등의 위치가 될 수 있다.
  • Pointcut : 애플리케이션의 여러 JoinPoint들 중에서 어디에 Aspect를 적용할지 선별하는 표현식. JointPoint와 Pointcut을 비교하여 일치할 경우에만 Aspect를 적용하게 된다.
  • Advice : Aspect의 기능이다. 즉, 횡단 관심사를 처리하는 코드이다. 원하는 코드 조각의 Arount(주변), Before(전), After(후)에 적용될 수 있다.
  • Weaving : Aspect를 적용하는 것, 즉 Pointcut으로 특정된 JointPoint에 Advice를 적용하는 일이다. 컴파일 타임, 로드 타임, 런타임에 할 수 있는데 Spring AOP는 런타임에 프록시 방식으로 적용한다.

Spring AOP의 개념을 이해했으니 우리의 애플리케이션에 이제 한번 적용해보겠다.

build.gradle

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

build.gradle에 위 코드를 추가하여 AOP기능을 사용할 수 있다.

Aspect.class

@Slf4j
@Aspect
public class Aspect {

	//dompoo.aop.order 패키지와 그 하위의 모든 클래스
    @Pointcut("execution(* dompoo.aop.order..*(..))")
    private void allOrder() {} //pointcut signature

    //클래스 이름 패턴이 *Service 인 것
    @Pointcut("execution(* *..*Service.*(..))")
    public void allService() {} //pointcut signature

    @Around("allOrder()")
    public Object doLog(ProceedingJoinPoint joinPoint) throws Throwable {
        log.info("[log] {}", joinPoint.getSignature());
        return joinPoint.proceed();
    }

    //dompoo.aop.order 패키지와 그 하위 패키지 이면서 클래스 이름 패턴이 *Service인 것
    @Around("allOrder() && allService()")
    public Object doTransaction(ProceedingJoinPoint joinPoint) throws Throwable {
        try {
            log.info("[트랜잭션 시작] {}", joinPoint.getSignature());
            Object result = joinPoint.proceed();
            log.info("[트랜잭션 커밋] {}", joinPoint.getSignature());
            return result;
        } catch (Exception e) {
            log.info("[트랜잭션 롤백] {}", joinPoint.getSignature());
            throw e;
        } finally {
            log.info("[리소스 릴리즈] {}", joinPoint.getSignature());
        }
    }
}

Aspect 작성법은 간단하다.

  • Pointcut은 AspectJ Pointcut문법에 따라 작성한다. 자세한 내용은 관련 문서를 참조하면 쉽게 이해할 수 있을 것이다. 또한 작성된 두 포인트컷의 교집합을 지정할 수도 있다.(위 예제의 doTransaction() )
  • Advice는 @Around, @Before, @After등의 어노테이션을 통해 작성한다. @Around가 가장 강력한 기능을 지녔으므로 여기에서는 @Around만을 설명하겠다.
    @Around에 아까 작성한 Pointcut을 추가해주고, 메서드는 ProceedingJoinPoint를 받게 한다. getSignature() 메서드를 통해 호출되는 메서드의 정보를 받을 수 있다. 또한 proceed() 메서드를 통해 원래 메서드의 로직을 언제 실행할지 지정해주어야 한다. 마지막에는 proceed() 메서드를 통해 받은 오브젝트를 리턴하면 된다.

이렇게 Aspect를 작성한 후, 컴포넌트 스캔이나 @Import를 해주면 된다.

결과

[log] void dompoo.aop.order.OrderService.orderItem(String)
[트랜잭션 시작] void dompoo.aop.order.OrderService.orderItem(String)
[orderService] 실행
[log] String dompoo.aop.order.OrderRepository.save(String)
[orderRepository] 실행
[트랜잭션 커밋] void dompoo.aop.order.OrderService.orderItem(String)
[리소스 릴리즈] void dompoo.aop.order.OrderService.orderItem(String)

위와 같이 Aspect가 잘 적용된 것을 볼 수 있다.

이렇게 성공적으로 AOP를 사용하여 우리의 비즈니스 로직에서부터 횡단 관심사를 성공적으로 분리하였고, 관리와 수정하기에도 매우 편리해졌다. Spring AOP는 그 내용이 방대하며 특히 AspectJ Pointcut문법을 이용하면 어노테이션 단위로 Advice를 적용하는 일도 가능하다. 다음 글에서 좀 더 깊은 사용법과 프록시 기술의 한계를 짚어보고자 한다.

profile
나중에 또 모를 것들 모음

0개의 댓글