AOP(Aspect Oriented Programming)은 흩어진 관심사를 별도의 클래스로 모듈화하는 방식을 말한다.
Spring AOP는 프록시 객체를 자동으로 생성해서 Aspect/Advice에 직접적으로 의존하지 않도록 해준다.
객체를 대리(proxy)하는 객체를 통해 대상 객체에 접근하는 방식
상위 인터페이스를 상속하는 프록시 클래스 생성, 여기서 부가 기능 관련된 기능을 처리한다.
→ target에서 aspect를 알 필요 없이 순수한 비즈니스 로직에 집중할 수 있다
트랜잭션 처리를 위한 @Transactional 애노테이션은 Spring AOP의 대표적인 예이다.
@Transactional 역시 Proxy 형태로 동작한다.
Spring은 JDK Proxy, Spring Boot는 CGLIb Proxy를 기본으로 하기 때문에, 사용하는 것에 따라 생성된 프록시 객체 형태는 다를 수 있다.
JDK Proxy, CGLib Proxy: Target의 어떤 부분을 상속 받아서 프록시를 구현하느냐?
프록시의 문제점: 클라이언트가 매번 프록시를 작성해야한다.
그러니까, Target 수만큼 프록시 클래스가 많아져야한다.
JDK Reflect를 이용해서 매번 프록시를 작성하지 않고, 런타임에서 프록시를 동적(dynamic) 생성
스프링 ProxyFactoryBean은 Advice와 Pointcut으로 구성된다.
1개 이상의 어드바이스와 포인트컷이 만나면, 하나의 Advisor가 되어서 ProxyFactoryBean을 통해 다이나믹 프록시를 생성한다.
근데 일반적으로는 프록시 기반 AOP 정도면 충분하고, 아주 드물게 AspectJ를 사용할 수 있다.
AspectJ가 유연하고 강력한 특징이 있지만.. 스프링 컨테이너나 DI 도움 없이 가능해서 좋다.
핵심 로직과 별개의 관심사(트랜잭션 처리, 로깅)을 분리해서, 자동으로 끼워넣는 AOP
Spring은 AOP를 이용해서 트랜잭션 제어를 런타임시 감싸는 프록시 객체를 생성한다.
@Transactional
public void doSomething() {
userRepository.save(...);
}
이 메소드를 실행하면 직접 실행되지 않고, 스프링이 만든 프록시 객체가 먼저 호출된다.
YourClassProxy (프록시 객체)
└── TransactionAspect (before advice: 트랜잭션 시작을 수행)
└── YourClass.doSomething()
└── TransactionAspect (after advice: 커밋 or 롤백을 수행)

1. private 메소드는 트랜잭션 처리할 수 없다: 당연히 AOP 원리를 이해한다면 납득할 수 있다!
해당 메서드를 외부에서 호출할 수 있어야 프록시 객체도 메서드를 가로챌 수 있다.
private 메서드는 클래스 내부에서만 접근 가능하기에 프록시 객체를 생성할 수 없다.
2. 같은 클래스 내의 메서드 호출은 트랜잭션 처리되지 않음:
생성된 프록시 객체는 클래스 내부의 다른 메서드를 직접 호출하기에 가로챌 수 없다.
예시를 들어보자.
@Service
public class MyService {
@Transactional
public void outerMethod() {
...
innerMethod();
}
@Transactional
public void innerMethod() {
...
}
}
다음과 같은 경우에는 outerMethod()를 호출하기 위해서 프록시 객체가 생성된다.
하지만 내부에서 호출되는 innerMethod()는 같은 클래스라 프록시 객체가 새로이 가로챌 수 없고 기존 메서드를 호출한다.
outerMethod()에 대한 트랜잭션은 정상적으로 시작되지만, innerMethod()는 프록시 객체를 거치지 않고 실제 인스턴스의 메서드를 직접 호출하기에 프록시 우회가 발생한다.
이는 @Transactional의 옵션을 Propagation.REQUIRES_NEW로 설정해도 innerMethod()의 트랜잭션은 똑같이 무시된다.
프록시를 통하지 않는 내부 호출로 인해서 @Transactional 자체가 활성화되지 않기에 propagation 옵션이 뭐던간에 무시된다.
하위 메서드에서 @Transactional 어노테이션이 존재해도, 같은 트랜잭션 범위에서 작업이 일어난다. (default= Propagation.REQUIRED)
물리 트랜잭션: 실제 데이터베이스에 적용되는 실질 트랜잭션이 커밋되거나 롤백되는 단위
논리 트랜잭션: 스프링에서 트랜잭션 매니저를 통해 관리되는 프로그래밍 수준의 트랜잭션
Propagation.REQUIRED : 기존 트랜잭션이 있으면 사용하되, 없으면 새로 만든다.
Propagation.REQUIRES_NEW: 항상 새로운 트랜잭션이 필요하다.
Propagation.MANDATORY
Propagation.SUPPORTS: 기존 트랜잭션이 있으면 사용하되, 없으면 없이 진행한다.
Propagation.NOT_SUPPORTED: 기존 트랜잭션이 있어도 없이 진행한다.
| JVM Metaspace가 단순히 이름만 바꾼건 아닐텐데요 (0) | 2026.01.14 |
|---|---|
| 토스가 겪은 Reactor Netty의 Memory Leak 이슈를 알아보자 (4) | 2025.08.18 |
| JVM 튜닝 목적으로 Heap 덤프 분석은 처음 해봐요 (feat. Eclipse MAT) (0) | 2025.06.13 |
| What Color is Your Function? (feat. 함수의 색 전염성) (0) | 2025.03.12 |
| Java도 한다 경량 쓰레드 (Virtual Thread) (0) | 2025.02.26 |