[Spring] Spring AOP(Aspect-Oriented Programming)
1. 스프링 AOP란?
AOP는 관점 지향 프로그래밍(Aspect-Oriented Programming)이라고 부르며, 프로그램의 핵심 기능 외에 부가적인 기능을 분리해서 모듈화 하는 기법이다. 핵심 비즈니스 로직과는 직접적인 관계가 없는 로직들(ex. 로깅, 트랜잭션)이 코드의 여러 부분에서 반복될 수 있다. AOP는 비즈니스 로직과 상관없는 반복적인 코드들을 별도의 모듈로 관리하여 코드의 유지 보수를 쉽게 하고 핵심 비즈니스 로직에 집중할 수 있도록 한다.
ex) @Transactional은 트랜잭션 처리라는 부가적인 기능을 애너테이션으로 적용하는 방식이다. 원래는 트랜잭션을 사용하려면 transaction.begin(), transaction.commit(), transaction.rollback() 같은 코드를 비즈니스 로직마다 작성해야 하지만, AOP를 사용하면 트랜잭션 처리를 애너테이션으로 간단하게 적용할 수 있다.
@Transactional은 비즈니스 로직의 앞뒤에 트랜잭션을 자동으로 설정한다. 그래서 개발자는 비즈니스 로직에만 집중하고 트랜잭션과 관련된 코드를 신경 쓸 필요가 없다. 즉, AOP는 횡단 관심사(Cross-Cutting Concerns)를 비즈니스 로직에서 분리하여 관리할 수 있다.
횡단 관심사란 핵심 비즈니스 로직이 아닌, 여러 곳에서 공통적으로 필요한 부가 기능을 의미한다. 우리는 트랜잭션을 시작하고 종료하기 위해 @Transactional을 사용할 수 있다.
- 핵심 관심사(Core Concerns) : 비즈니스 로직에 직접적으로 관련된 부분
- ex) 입금, 출금, 이체와 같은 실제 업무 로직
- 횡단 관심사(Cross-Cutting Concerns) : 비즈니스 로직과는 독립적인 기능
- ex) 로깅, 보안, 트랜잭션과 같은 코드가 각 비즈니스 로직에 걸쳐 반복적으로 사용된다.
트랜잭션이라는 관심사는 @Transactional 덕분에 비즈니스 로직 안에 직접 넣을 필요 없이 자동으로 처리된다. 그러나 관심사가 분리되어 있지 않다면 DB에 접근할 때마다 begin(), commit(), rollback()과 같은 트랜잭션 코드를 반복하여 직접 작성해야 한다.
- AOP가 적용된 코드
@Transactional
public void save() {
memberRepository.save(member);
}
@Transactional
public void update() {
member.updateUsername(username);
}
- AOP가 적용되지 않은 코드
public void save() {
transaction.begin();
memberRepository.save(member);
transaction.commit();
}
public void update() {
transaction.begin();
member.updateUsername(username);
transaction.commit();
}
2. Spring AOP 동작 방식
Spring AOP는 프록시(Proxy) 기반으로 동작하며 메서드 호출 전후에 부가 기능을 적용한다. 프록시 기반 AOP는 런타임 시점에 동작하며, 메서드 레벨에서만 AOP를 적용한다.
2-1. 프록시(Proxy)란?
프록시는 실제 객체를 대신하여 메서드 호출을 처리하는 대리 객체이다. Spring AOP에서 프록시는 비즈니스 로직의 실행 전후로 부가적인 기능을 추가할 수 있도록 한다.
예를 들어, @Transactional이 붙어있는 메서드가 호출될 때, 프록시 객체는 메서드를 호출하기 전에 트랜잭션을 시작하고, 메서드 실행 후에 트랜잭션을 커밋하거나 롤백하는 역할을 한다. 프록시는 비즈니스 로직을 실행하기 전후로 부가 기능을 실행하는 방식으로 동작한다.
2-2. 프록시 생성 방식
Spring AOP에서는 JDK 동적 프록시 또는 CGLIB(Code Generator Library) 프록시를 사용하여 프록시 객체를 생성한다.
- JDK 동적 프록시 : 인터페이스를 구현한 객체에 대해 프록시 객체를 생성한다. 인터페이스를 구현한 클래스에서만 동작한다.
- CGLIB 프록시 : 클래스 상속을 통해 프록시 객체를 생성하며, 주로 인터페이스가 없는 클래스에 사용된다.
2-3. @Transactional에서 프록시의 동작 흐름
- 클라이언트가 @Transactional이 적용된 메서드를 호출한다.
- 프록시 객체가 호출을 가로챈다. 이때, AOP의 부가 기능이 필요하다면 프록시에서 처리한다.
- 프록시는 메서드 실행 전에 트랜잭션을 실행한다. 트랜잭션이 시작된 상태에서 프록시 객체가 Target 객체의 메서드를 실제로 호출한다.
- 메서드의 실행 결과에 따라 트랜잭션을 커밋하거나 롤백한다.
프록시 객체 덕분에 개발자는 트랜잭션 관리에 신경 쓰지 않고 비즈니스 로직만 작성하면 된다.
3. 스프링 AOP의 핵심 개념
3-1. Aspect
Aspect는 AOP에서 횡단 관심사(Cross-Cutting Concern)를 모듈화 한 것을 의미한다. 트랜잭션 관리(시작, 종료), 로깅 등 비즈니스 로직과는 별개로 여러 곳에서 반복하여 사용될 수 있는 부가적인 기능에 해당한다. Aspect는 Advice + PointCut으로 구성되며, 무슨 메서드(PointCut)에 언제(Advice) 부가 기능(Asepct)을 적용할지 정의한다.
ex) @Trasnactional은 트랜잭션 관리를 위한 Aspect로, 트랜잭션의 시작, 커밋, 롤백 등의 부가 기능을 제공한다. 트랜잭션은 비즈니스 로직의 전후로 적용된다.
3-2. JoinPoint
JointPoint는 부가 기능(Aspect)이 적용될 수 있는 특정 실행 지점을 의미한다. 스프링 AOP에서는 주로 메서드 실행 지점을 JoinPoint로 취급한다. 따라서 메서드 호출 시 그 지점에서 부가 기능이 실행될 수 있다. Spring AOP는 메서드 호출만 JointPoint로 지원한다.
ex) @Transactional이 적용되는 JointPoint는 메서드 실행 시점이다. 트랜잭션이 메서드 호출 전에 시작되고, 메서드 실행이 끝나면 트랜잭션이 커밋 또는 롤백된다.
3-3. Advice
Advice는 부가 기능(Aspect)이 언제 실행될지를 정의한다. JointPoint에서 부가 기능이 적용될 수 있는 지점을 정의하고, Advice에서 부가 기능을 언제 실행할지를 결정한다.
- Before Advice : 메서드 실행 전에 실행된다.
- After Advice : 메서드 실행 후에 실행된다. (비즈니스 로직 성공 또는 예외 여부 상관없이 실행됨)
- AfterReturning Advice : 메서드가 성공적으로 리턴된 후 실행된다.
- AfterThrowing Advice : 메서드 실행 중 예외가 발생했을 때 실행된다.
- Around Advice : 메서드 실행 전후에 모두 실행되며, 메서드 자체를 제어할 수 있다.
ex) @Transactional은 Around Advice 방식으로 동작한다. 트랜잭션의 시작과 종료를 메서드 실행 전후로 제어할 수 있다.
3-4. Pointcut
PointCut은 JointPoint 중에서 어떤 지점에 부가 기능(Aspect)을 적용할지 결정하는 필터 역할을 한다. AspectJ 표현식을 사용하여 PointCut를 정의한다.
ex) execution(* com.example.service.*.*(..))는 특정 패키지 내의 모든 메서드에 Aspect를 적용하는 PointCut이다. 여기서 *는 모든 메서드를 의미하며, (..)는 매개변수의 타입과 개수에 관계없이 메서드를 선택하는 표현이다.
3-5. Weaving
Weaving은 부가 기능(Aspect)을 핵심 비즈니스 로직에 적용하는 과정이다. 런타임, 컴파일, 클래스 로딩 시점 등 다양한 시점에서 Weaving이 가능하다. 스프링 AOP는 Proxy 기반으로 동작하므로 런타임에 Proxy 객체를 생성하여 Weaving이 이루어진다.
ex) @Transcational은 Weaving을 통해 트랜잭션 관리를 제공한다. 메서드 호출 시 스프링 AOP Proxy 객체가 런타임 시점에 생성되고, 이 객체가 비즈니스 로직 실행 전후에 트랜잭션을 시작하고 종료한다.
3-6. 결론
- @Transactional은 트랜잭션 관리라는 Aspect를 제공한다.
- 메서드가 호출되는 지점에서 트랜잭션을 관리하는 JoinPoint가 발생한다.
- Advice는 메서드 실행 전후에 트랜잭션을 시작하고, 성공 시 커밋, 예외 시 롤백을 담당한다.
- 트랜잭션이 적용될 메서드를 Pointcut으로 지정한다.
- Weaving 과정을 통해 트랜잭션 관리가 실행된다.