1. AOP 소개 - 핵심 기능과 부가 기능
1.1. 애플리케이션 로직의 두 가지 측면
1.1.1. 핵심 기능(Core Concerns)

OrderService의 주문 로직 혹은 UserService의 사용자 관리 로직 등 비즈니스 요구사항을 직접적으로 구현한 기능을 말한다. 즉, 객체가 제공하는 고유의 본질적 기능이다.
1.1.2. 부가 기능(Cross-cutting Concerns)

로그 추적, 트랜잭션 관리, 보안 검증, 캐싱, 성능 모니터링 등 핵심 기능을 보조하기 위해 제공되는 기능을 말한다. 부가 기능은 단독으로 사용되지 않고 항상 핵심 기능과 함께 사용된다.
1.2. 여러 곳에서 공통으로 사용하는 부가 기능

부가 기능의 중요한 특징은 여러 클래스에 걸쳐 공통으로 사용된다는 점이다. 이는 단순히 하나의 클래스 내에서만 사용되는 것이 아니라, 애플리케이션 전반에 걸쳐 반복적으로 사용된다.
실무 예시:
- 로깅 기능
- 모든 컨트롤러: 요청/응답 로깅
- 모든 서비스: 비즈니스 로직 실행 로깅
- 모든 리포지토리: 데이터 접근 로깅
- 트랜잭션 관리
- 모든 데이터 변경 작업: 트랜잭션 시작/커밋/롤백
- 보안 검증
- 모든 접근 제어가 필요한 메서드: 권한 확인
- 성능 모니터링
- 모든 중요한 메서드: 실행 시간 측정
1.3. 횡단 관심사(Cross-cutting Concerns)

이러한 부가 기능을 횡단 관심사라고 부른다. "횡단"이라는 표현은 부가 기능이 애플리케이션의 여러 계층(레이어)을 가로질러 적용되기 때문이다.
애플리케이션 계층 구조:
┌─────────────────────┐
│ Presentation │ ← 로깅, 보안, 트랜잭션 적용
├─────────────────────┤
│ Service │ ← 로깅, 트랜잭션, 캐싱 적용
├─────────────────────┤
│ Repository │ ← 로깅, 트랜잭션 적용
└─────────────────────┘
횡단 관심사는 애플리케이션을 수직이 아닌 수평으로 가로지르는 관심사이다.
1.4. 전통적 접근 방식의 문제점
부가 기능이 필요한 경우, 일반적으로 다음과 같은 방식으로 구현한다:
1.4.1. 반복과 중복 (Repeated Code)
// 클래스 A
public void methodA() {
log.info("methodA 시작");
try {
// 핵심 로직 A
} finally {
log.info("methodA 종료");
}
}
// 클래스 B
public void methodB() {
log.info("methodB 시작"); // 동일한 로깅 코드
try {
// 핵심 로직 B
} finally {
log.info("methodB 종료"); // 동일한 로깅 코드
}
}
// 클래스 C, D, E... 모두 동일한 패턴
- 100개의 클래스에 부가 기능을 적용하려면 100번의 코드 복사가 필요하다
- 부가 기능을 유틸리티 클래스로 분리해도 호출 코드는 여전히 필요하다
- 복잡한 구조(try-catch-finally)가 필요한 부가 기능은 더욱 번거롭다
1.4.2. 유지보수의 악몽 (Maintenance Nightmare)
시나리오: 로깅 포맷 변경
- 기존 로그 포맷: "method 시작"
- 새로운 요구사항: "[시간] method 시작 - 파라미터: {}"
- 변경 작업: 모든 적용된 100개 위치를 찾아 수정해야 함
- 실수 가능성: 일부 위치를 놓칠 가능성이 높음
시나리오: 적용 범위 변경
- 기존: 모든 계층에 로깅 적용
- 새로운 요구사항: 서비스 계층에만 로깅 적용 (로그가 너무 많이 남음)
- 변경 작업: 컨트롤러, 리포지토리의 로깅 코드를 모두 제거해야 함
- 작업량: 엄청난 수의 코드 수정이 필요함
1.4.3. 코드 품질 저하 (Code Quality Degradation)
1) 가독성 저하: 핵심 로직과 부가 로직이 섞여 읽기 어려워진다
public void businessLogic() {
// 5줄의 부가 기능 코드
log.info("시작");
startTimer();
checkPermission();
beginTransaction();
// 3줄의 핵심 기능 코드 (중요한 부분이 가려짐)
실제_비즈니스_로직();
// 5줄의 부가 기능 코드
commitTransaction();
stopTimer();
log.info("종료");
}
2) 단일 책임 원칙(SRP) 위반: 하나의 클래스가 여러 책임을 가지게 된다
3) 테스트 어려움: 부가 기능과 핵심 기능이 결합되어 단위 테스트가 어려워진다
1.4.4. 일관성 유지의 어려움
서로 다른 개발자가 비슷한 부가 기능을 구현할 때:
- 구현 방식이 다를 수 있다 (예외 처리 방식, 로그 포맷 등)
- 코드 품질이 일관되지 않다
- 버그 발생 시 추적이 어렵다
1.5. OOP의 한계
객체지향 프로그래밍은 다음과 같은 원칙을 강조한다:
- 단일 책임 원칙(SRP): 하나의 클래스는 하나의 책임만 가져야 한다
- 관심사의 분리: 다른 관심사는 다른 클래스로 분리해야 한다
그러나 횡단 관심사는 이 원칙들과 충돌한다:
- 로깅, 트랜잭션, 보안 같은 부가 기능은 여러 클래스에 걸쳐 존재한다
- 이를 OOP 방식으로 깔끔하게 모듈화하기 어렵다
- 결국 코드 중복이나 SRP 위반을 감수해야 한다
이러한 문제를 해결하기 위해 AOP(Aspect-Oriented Programming)가 등장했다. AOP는 횡단 관심사를 깔끔하게 모듈화하고, 핵심 기능과 완전히 분리할 수 있는 방법을 제공한다.
2. AOP 소개 - 애스펙트
2.1. AOP의 등장: 횡단 관심사의 해결책
AOP(Aspect-Oriented Programming, 관점 지향 프로그래밍)는 이 문제를 해결하기 위해 등장했다. AOP의 핵심 아이디어는 다음과 같다: 부가 기능을 핵심 기능에서 완전히 분리하고, 한 곳에서 통합 관리하자
2.2. 애스펙트(Aspect)란?
애스펙트는 부가 기능과 그 적용 대상을 정의한 모듈이다. 쉽게 말해 "무엇을(부가 기능) 어디에(적용 대상) 적용할 것인가"를 정의하는 것이다.
애스펙트의 구성 요소:
- 어드바이스(Advice): 적용할 부가 기능
- 포인트컷(Pointcut): 적용할 위치(대상)
// 애스펙트의 예
@Aspect
public class LoggingAspect {
// 포인트컷: "hello.proxy.app 패키지의 모든 메서드"
// 어드바이스: 메서드 실행 시간 측정 및 로깅
@Around("execution(* hello.proxy.app..*(..))")
public Object logExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable {
// 어드바이스 구현
long startTime = System.currentTimeMillis();
Object result = joinPoint.proceed();
long elapsedTime = System.currentTimeMillis() - startTime;
log.info("{} executed in {} ms", joinPoint.getSignature(), elapsedTime);
return result;
}
}
2.3. 관점(Aspect)의 의미
"Aspect"는 "관점"이라는 뜻이다. AOP는 애플리케이션을 바라보는 관점을 바꾼다:
- 기존 관점: 기능 단위로 바라보기
- AOP 관점: 횡단 관심사 단위로 바라보기
예를 들어, 로깅이라는 관점에서 애플리케이션을 바라보면 각각의 비즈니스 클래스가 아니라 "어디에 로깅을 적용할 것인가"에 집중하게 된다.
2.4. AOP와 OOP의 관계
AOP는 OOP를 대체하려는 것이 아니다. 오히려 OOP의 부족한 부분을 보완하는 것이다:
- OOP: 세로(수직) 관심사의 분리에 강점 (비즈니스 도메인 모델링)
- AOP: 가로(수평) 관심사의 분리에 강점 (로깅, 트랜잭션, 보안 등)
두 패러다임은 상호 보완적이다.


2.5. AspectJ 프레임워크
AOP의 대표적인 구현체는 AspectJ 프레임워크이다. AspectJ는 자바 언어에 대한 완전한 관점 지향 확장을 제공한다:
- 공식 설명: "자바 프로그래밍 언어에 대한 완벽한 관점 지향 확장"
- 주요 기능:
- 횡단 관심사의 깔끔한 모듈화
- 오류 검사 및 처리
- 동기화
- 성능 최적화(캐싱)
- 모니터링 및 로깅
스프링 AOP는 AspectJ의 문법을 차용하지만, 실제 구현은 프록시 기반으로 AspectJ의 일부 기능만 제공한다.
3. AOP 적용 방식
3.1. 세 가지 적용 시점
AOP를 구현하는 방식은 적용 시점에 따라 세 가지로 나뉜다:
3.1.1. 컴파일 시점(Compile-time)

- .java 소스 코드를 컴파일할 때 부가 기능을 추가한다
- AspectJ 컴파일러(ajc)를 사용한다
- 컴파일된 .class 파일을 디컴파일해보면 부가 기능 코드가 삽입되어 있다
- 장점: 런타임 오버헤드가 없다
- 단점: 특별한 컴파일러 필요, 설정이 복잡하다
3.1.2. 클래스 로딩 시점(Load-time)

- 자바 실행 시 .class 파일을 JVM에 로드하기 전에 조작한다
- java.lang.instrument 패키지의 Instrumentation API를 사용한다
- javaagent 옵션으로 클래스 로더 조작기를 지정해야 한다
- 장점: 소스 코드 수정 없이 적용 가능
- 단점: JVM 옵션 설정 필요, 운영이 복잡하다
3.1.3. 런타임 시점(Runtime)

- 애플리케이션이 실행된 후에 부가 기능을 적용한다
- 프록시 패턴을 사용한다
- 스프링 AOP가 이 방식을 사용한다
- 장점: 설정이 간단하다, 스프링 생태계와 잘 통합된다
- 단점: 프록시 제한 사항이 있다, 약간의 런타임 오버헤드가 있다
3.2. 위빙(Weaving)
위빙은 "직조하다"라는 뜻으로, 애스펙트와 실제 코드를 연결하는 과정을 말한다. 위빙 방식에 따라 AOP 구현이 달라진다:
- 컴파일 타임 위빙: AspectJ 컴파일러가 담당
- 로드 타임 위빙: 클래스 로더가 담당
- 런타임 위빙: 스프링 컨테이너가 담당
3.3. 스프링 AOP의 선택: 런타임 시점
스프링은 왜 런타임 시점 방식을 선택했을까?
장점:
- 설정 간소화: 특별한 컴파일러나 JVM 옵션이 필요 없다
- 통합 용이: 스프링 컨테이너, DI, 빈 후처리기와 자연스럽게 통합된다
- 학습 곡선 완만: 새로운 개념이나 도구를 배울 필요가 적다
- 운영 단순: 개발/테스트/운영 환경에서 일관된 방식으로 동작한다
단점:
- 제한적 적용: 메서드 실행 시점에만 적용 가능
- 스프링 빈 제한: 스프링이 관리하는 빈에만 적용 가능
- 성능 오버헤드: 프록시를 통한 간접 호출로 인한 약간의 성능 저하
3.4. 적용 위치 비교
적용 방식 적용 가능 위치 스프링 지원
| 컴파일 시점 | 생성자, 필드 접근, static 메서드, 일반 메서드 | AspectJ 필요 |
| 클래스 로딩 시점 | 생성자, 필드 접근, static 메서드, 일반 메서드 | AspectJ 필요 |
| 런타임 시점 | 일반 메서드만 가능 | 스프링 AOP 지원 |
스프링 AOP는 프록시 기반이므로 메서드 오버라이딩 개념으로 동작한다. 따라서 다음에는 적용할 수 없다:
- 생성자
- static 메서드
- 필드 접근
- private 메서드
4. AOP 적용 위치
4.1. 조인 포인트(Join Point)
조인 포인트는 AOP가 적용될 수 있는 프로그램 실행 지점을 말한다. AspectJ는 다음과 다양한 조인 포인트를 지원한다:
- 메서드 실행: 메서드가 호출될 때
- 생성자 실행: 객체가 생성될 때
- 필드 접근: 객체의 필드에 접근할 때
- static 메서드 접근: static 메서드가 호출될 때
- 예외 처리: 예외가 발생할 때
- 초기화: 객체가 초기화될 때
4.2. 스프링 AOP의 제한
스프링 AOP는 프록시 방식을 사용하므로 조인 포인트가 제한된다:
- 지원: 일반 메서드 실행
- 미지원: 생성자, static 메서드, 필드 접근, 객체 초기화
이 제한은 프록시의 기본 동작 방식에서 기인한다:
// 프록시는 인터페이스 구현이나 상속을 통해 동작한다
class Proxy extends TargetClass {
// 오버라이드할 수 있는 메서드만 적용 가능
@Override
public void method() {
// 부가 기능
super.method(); // 실제 메서드 호출
// 부가 기능
}
// 생성자, static 메서드, 필드는 오버라이드 불가
}
4.3. 실무적 고려사항
스프링 AOP의 제한은 실무에서 큰 문제가 되지 않는다:
- 대부분의 비즈니스 로직은 인스턴스 메서드로 구현된다
- 생성자에 부가 기능이 필요한 경우는 드물다
- static 메서드는 보통 유틸리티 클래스에 사용되며, AOP 적용이 적합하지 않다
- 필드 접근 제어는 게터/세터 메서드를 통해 우회 적용할 수 있다
5. AOP 용어 정리
5.1. 핵심 용어

5.1.1. 조인 포인트(Join Point)
- 어드바이스가 적용될 수 있는 프로그램 실행 지점
- 메서드 실행, 생성자 호출, 필드 접근 등
- 추상적 개념: AOP가 적용될 수 있는 모든 지점
- 스프링 AOP: 메서드 실행 지점만 해당
5.1.2. 포인트컷(Pointcut)
- 조인 포인트 중에서 어드바이스를 실제로 적용할 위치를 선별하는 기능
- 주로 AspectJ 표현식을 사용
- 예: execution(* com.example.service.*.*(..))
5.1.3. 타겟(Target)
- 어드바이스를 받는 객체
- 포인트컷으로 선정된 실제 비즈니스 객체
- 프록시가 감싸는 실제 대상 객체
5.1.4. 어드바이스(Advice)
- 적용할 부가 기능
- 특정 조인 포인트에서 실행되는 코드
- 종류: @Around, @Before, @After, @AfterReturning, @AfterThrowing
5.1.5. 애스펙트(Aspect)
- 어드바이스 + 포인트컷을 모듈화한 것
- @Aspect 애노테이션이 붙은 클래스
- 여러 어드바이스와 포인트컷을 포함할 수 있다
5.1.6. 어드바이저(Advisor)
- 하나의 어드바이스 + 하나의 포인트컷으로 구성
- 스프링 AOP에서만 사용되는 특별한 용어
- @Aspect가 등장하기 전에 사용되던 방식
5.1.7. 위빙(Weaving)
- 포인트컷으로 결정된 타겟의 조인 포인트에 어드바이스를 적용하는 과정
- 애스펙트와 실제 코드를 연결하는 작업
- 방식: 컴파일 타임, 로드 타임, 런타임
5.1.8. AOP 프록시
- AOP 기능을 구현하기 위해 생성된 프록시 객체
- 스프링에서는 JDK 동적 프록시 또는 CGLIB 프록시
- 클라이언트는 프록시를 통해 타겟에 접근한다
5.2. 용어 관계 정리
애스펙트(Aspect)
├── 어드바이스(Advice): "무엇을" - 부가 기능
└── 포인트컷(Pointcut): "어디에" - 적용 위치
└── 조인 포인트(Join Point) 중 선별
└── 타겟(Target) 객체의 메서드
└── AOP 프록시로 감싸짐
└── 위빙(Weaving) 과정을 거쳐 생성
5.3. 스프링 vs AspectJ 용어 비교
| 개념 | 스프링 AOP | AspectJ |
| 기본 구현 | 프록시 | 바이트코드 조작 |
| 적용 시점 | 런타임 | 컴파일/로드/런타임 |
| 적용 범위 | 메서드 실행 | 모든 조인 포인트 |
| 성능 | 약간의 오버헤드 | 최적화 가능 |
| 학습 곡선 | 낮음 | 높음 |
| 설정 | 간단 | 복잡 |
6. 정리
6.1. AOP의 본질
AOP는 횡단 관심사의 문제를 해결하기 위한 패러다임이다. 그 본질은 다음과 같다:
- 관심사의 분리: 핵심 비즈니스 로직과 부가 기능을 완전히 분리
- 모듈화: 재사용 가능한 AOP 모듈 생성
- 중앙화: 부가 기능을 한 곳에서 통합 관리
6.2. 스프링 AOP의 실용성
스프링이 프록시 기반 AOP를 선택한 이유는 실용성 때문이다:
- 설정의 간결성: 복잡한 도구나 설정 없이 사용 가능
- 통합의 용이성: 스프링 생태계와 자연스럽게 통합
- 실무 적합성: 대부분의 실무 요구사항을 충족
- 학습의 용이성: 비교적 쉽게 학습하고 적용 가능
6.3. 진화의 완성
프록시 기술의 진화 과정은 AOP의 완성을 향해 진행되었다:
1. 프록시 패턴 → 2. 동적 프록시 → 3. 프록시 팩토리 →
4. 빈 후처리기 → 5. @Aspect → 6. 완전한 AOP
각 단계마다:
- 추상화 수준이 높아졌다
- 설정 코드가 줄어들었다
- 선언적 프로그래밍에 가까워졌다
- 유지보수성이 향상되었다
6.4. 실무 적용 가이드
- 시작은 간단하게: @Around와 기본 포인트컷 표현식으로 시작
- 필요에 따라 확장: 다양한 어드바이스 타입과 포인트컷 표현식 활용
- 성능 고려: 너무 광범위한 포인트컷은 성능 저하를 초래할 수 있다
- 적절한 추상화: 공통 포인트컷은 @Pointcut으로 추상화하여 재사용
- 테스트 용이성: AOP 로직도 테스트 가능하도록 설계
'Spring > Core' 카테고리의 다른 글
| [Advanced-11] 스프링 AOP - 포인트 컷 (0) | 2026.01.02 |
|---|---|
| [Advanced-10] 스프링 AOP 구현 (0) | 2026.01.02 |
| [Advanced-8] @Aspect AOP (0) | 2026.01.02 |
| [Advanced-7] 빈 후처리기 (0) | 2026.01.02 |
| [Advanced-6] 스프링 지원 프록시 (0) | 2026.01.02 |
