항상 서두에 글을 쓰지만 이글은 Spring AOP 를 정의하는 글이 아닌
그 동안의 경험을 바탕으로 Spring AOP를 이해하기 위해 실제 구동원리를 바탕으로
이렇게도 생각하는구나 또는 이렇게 이해를 하는구나라고 생각을 확장하는 의미로 봐주시면 좋을 것 같습니다.
1. AOP의 본질: 횡단 관심사의 분리와 프록시 패턴
- Aspect Oriented Programming 으로 사전적 정의를 찾아보면
"횡단 관심사(cross-cutting concern)의 분리를 허용함으로써 모듈성을 증가시키는 것이 목적인 프로그래밍 패러다임"
관점형 프로그래밍이라고 설명을 하며 대표적으로 설명되는 그림이 프로그램 로직의 가로 flow 에 세로로 뭔가를 수행하는 이미지를 많이 참고하는데

관점형으로 실제 비지니스 수행과 관계없는 영역의 관심사를 분리하여 코드를 줄일 수 있는 강력한 방법이기에 Spring을 대표하는 기능중 하나로 AOP가 많이 언급이 됩니다만 위의 설명만으로는 사실상 제가 주니어일때 관점형 프로그래밍을 이해하는게 개념적으로 쉽지만은 않았습니다~ 뭔말이야-_-
제 기준에서 좀 더 잘 이해했던 구동하는 원리(코드)를 가지고 개념을 역으로 설명 해 보겠습니다.
기능적인 부분을 기준으로 생각할때 Spring Bean으로 등록된 class를 wrapping을 해서 해당 클래스에 함수나
기능실행 직전이나 또는 끝날때 뭔가를 제어 할 수 있도록 해놓은 것으로 이해를 했고
이 과정에서 프록시 패턴(Proxy Pattern)이 사용됩니다.
프록시 패턴을 직접 코드로 구현해 보면 AOP의 원리를 조금 더 쉽게 이해할 수 있습니다.
// User Service Interface
interface IUserService {
void userSearch();
}
// 실제 UserService 를 구현한 Class
class UserService implements IUserService {
public void userSearch() {
System.out.println("유저 조회!!");
}
}
// ProxyUserService: AOP 개념을 적용한 Proxy
class ProxyUserService implements IUserService {
private final IUserService userService;
ProxyUserService(IUserService userService) {
this.userService = userService;
}
@Override
public void userSearch() {
// 실행 전 로직
System.out.println("조회하기 전 시간 확인!! or 조회하기 전 Transaction 시작");
userService.userSearch(); // 실제 유저 조회!!
// 실행 후 로직
System.out.println("조회 후 경과 시간 확인!! or 조회 후 Transaction 완료");
}
}
public class ProxyPatternExample {
public static void main(String[] args) {
// 위에서 말한 Class를 Wrapping 한다는 의미
IUserService userService = new ProxyUserService(new UserService());
userService.userSearch();
// 예상 결과
// "조회하기 전 시간 확인!! or 조회하기 전 Transaction 시작"
// "유저 조회!!"
// "조회 후 경과 시간 확인!! or 조회 후 Transaction 완료"
}
}
위의 코드가 일반적인 Proxy Pattern을 단순하게 구현한 것인데
예제에서 userSearch Method를 수행하기전 과 후에
해당 함수에서 무언가를 확인 하거나 제어한다는 기능적인 부분을 곰곰히 생각해보면
Spring에서의 Transaction Annotation ( begin , commit ) 이나 Method 수행 시간확인을 하기 위해서 구현하는
AOP 의 기능과 유사하다는 것을 알 수 있습니다.
2. Spring AOP의 내부 구현: JDK Dynamic Proxy vs CGLIB
실제로 Spring AOP는 위에서 설명한 프록시 패턴을 기반으로 동작합니다.
좀 더 세부적으로 들여다보면, Spring에서 제공하는 AOP 구현 방식은 크게 두 가지로 나뉩니다.
1> JDK Dynamic Proxy: 인터페이스 기반 프록시
JDK Dynamic Proxy는 대상 클래스가 인터페이스를 구현하고 있을 때 사용됩니다.
InvocationHandler 인터페이스를 구현하여 프록시 로직을 정의하고, 런타임에 동적으로 프록시 객체를 생성합니다.
// InvocationHandler 구현
class UserServiceInvocationHandler implements InvocationHandler {
private final Object target;
public UserServiceInvocationHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("AOP Before Logic");
Object result = method.invoke(target, args);
System.out.println("AOP After Logic");
return result;
}
}
public class JdkDynamicProxyExample {
public static void main(String[] args) {
IUserService realService = new UserService();
IUserService proxyInstance = (IUserService) Proxy.newProxyInstance(
IUserService.class.getClassLoader(),
new Class[]{IUserService.class},
new UserServiceInvocationHandler(realService)
);
proxyInstance.userSearch();
// 예상 결과
// "AOP Before Logic"
// "유저 조회!!"
// "AOP After Logic"
}
}
위의 첫 번째 프록시 구현처럼 모든 인터페이스 구현마다 수동으로 프록시 클래스를 만들 필요 없이, JDK Dynamic Proxy는 이 과정을 자동화하여 코드 양과 복잡도를 줄여줍니다. Spring의 실제 코드는 훨씬 복잡하지만, 기본적인 메커니즘은 이와 크게 다르지 않습니다.
(더 자세한 내용은 Spring Framework Reference: Proxying Mechanisms 문서를 참고해주세요.)
2> CGLIB (Code Generation Library): 인터페이스 없는 클래스 프록시
CGLIB는 대상 클래스가 인터페이스를 구현하지 않았을 때 사용됩니다.
이 방식은 런타임에 대상 클래스의 바이트코드를 조작하여 새로운 서브클래스(자식 클래스)를 생성하고,
이 서브클래스가 프록시 역할을 수행하게 합니다.
// 인터페이스 없이 구현된 서비스 클래스
class UserServiceWithoutInterface {
public void userSearch() {
System.out.println("유저 조회!!");
}
}
public class CglibProxyExample {
public static void main(String[] args) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(UserServiceWithoutInterface.class);
enhancer.setCallback((MethodInterceptor) (obj, method, args, proxy) -> {
System.out.println("AOP Before Logic");
Object result = proxy.invokeSuper(obj, args);
System.out.println("AOP After Logic");
return result;
});
UserServiceWithoutInterface proxy = (UserServiceWithoutInterface) enhancer.create();
proxy.userSearch();
// 예상 결과
// "AOP Before Logic"
// "유저 조회!!"
// "AOP After Logic"
}
}
인터페이스를 구현하지 않은 클래스에 대해 프록시 객체를 만들기 위해서는 Enhancer 클래스를 사용합니다.
Spring의 내부 구현은 물론 더 복잡하겠지만,
CGLIB를 통한 프록시 생성 메커니즘은 이 예시와 크게 다르지 않습니다.
3.Spring Boot의 AOP 기본 동작과 숨겨진 우선순위
Spring Boot에서는 @EnableAspectJAutoProxy의 proxyTargetClass옵션 값(기본값은 false)을 기준으로
두 가지 프록시 생성 방법 중 하나를 선택합니다.
- proxyTargetClass = false일 경우: JDK Dynamic Proxy (인터페이스 기반)
- proxyTargetClass = true일 경우: CGLIB (클래스 상속 기반)
만약 proxyTargetClass = false 상태에서 인터페이스 구현체가 아닌 서비스 클래스에 AOP를 적용하려고 하면
런타임에 AopConfigException이 발생할 수 있습니다.
이는 위에서 설명한 JDK Dynamic Proxy의 동작 원리(인터페이스 필요) 때문입니다.
그런데 여기서 재미있는 점은,
SpringBoot의 AopAutoConfiguration이 proxyTargetClass값을 true로 설정하고 CGLIB를 우선으로 작동하며
이 설정은 Spring 프레임워크 자체의 @EnableAspectAutoProxy 기본값이 false 인 것과는 대조적이며
두 설정 중 AopAutoConfiguration의 설정이 우선순위를 가집니다

즉, @EnableAspectJAutoProxy(proxyTargetClass = false)를 선언하더라도,
Spring Boot 환경에서는 기본적으로 CGLIB 방식의 프록시가 생성됩니다.
물론 application.properties 또는 application.yml 파일에
spring.aop.proxy-target-class= false로 셋팅을 하게되면 강제를 할 수 있긴 하나
이 부분은 쉽게 알기 힘든 숨겨진 동작 방식이기도 합니다.
그리고 Spring 공식 문서에서는 CGLIB보다 JDK Dynamic Proxy를 권장하고 있는데,
참고(https://docs.spring.io/spring-framework/reference/core/aop/introduction-proxies.html)
SpringBoot가 CGLIB를 우선으로 동작시킨다는 점이 다소 아이러니하게 느껴질 수 있습니다.
개인적으로 생각을 좀 해본다면
Spring을 Base로 Application의 설계할때는
Interface를 구현하는 형태가 Spring의 사상에 더 부합하기 때문에 JDK Dynamic proxy 를 추천하지만
SpringBoot 는 설정에 관련된 일은 신경쓰지마세요~ 기본적인 설정은 내가 다 알아서 해줄께라는
설정을 최소화하는 사상이라 좀 더 넓게 포함할 수 있는 CGLIB의 형태가 기본값이 된 것으로 생각됩니다.
이는 Spring이 추구하는 근본적인 구현 사상(JDK Dynamic Proxy)을 유지하면서도,
Spring Boot가 지향하는 편의성과 자동화라는 현실적인 부분을 고민하여 균형점을 찾은
즉 큰 방향성을 버리지 않되 현실적인 부분을 고민해둔 흔적이라고 생각합니다.
앞으로 Spring 진영에서 AOP 프록시 방식에 대한 개발 방향이 어떻게 변화할지 궁금해지는 부분이기도 합니다.
(뇌피셜이지만, Spring과 Spring Boot 진영에서 이 부분에 대해 많은 토론이 오가지 않았을까? 라고 생각해보기도 했습니다.)
무튼 AOP의 개념을 코드베이스와 동작원리를 통해 대략적으로 이해한걸 공유하였고
다음 글에서는 실 서비스에서 사용 및 구현한 부분을 해 보도록 하겠습니다.
'개발 > SpringBoot' 카테고리의 다른 글
| ApplicationEventPublisher의 @Async 사용 시 주의점 (0) | 2025.05.13 |
|---|---|
| SpringBoot의 ApplicationEventPublisher 를 활용한 비즈니스 분리 (0) | 2025.04.25 |
| Springboot Custom Annotation (0) | 2025.02.17 |