자바 백엔드 입문 23편. AOP가 도대체 뭐고, 왜 트랜잭션·로깅·보안 검증을 모든 메서드에 박지 않고 한 곳에 모으는 발상을 횡단 보도 CCTV 비유로 풀어쓴 학습 노트.
이 글은 자바 백엔드 입문 시리즈 59편 중 23편이에요. 22편에서 SpEL을 풀었다면, 이번 23편은 AOP(Aspect-Oriented Programming) — Spring의 가장 강력한 무기 중 하나의 개념을 들여다봅니다. 18편에서 코드로 다루기 전 "발상부터" 잡고 가요.
AOP가 어렵게 들리는 이유
처음 "AOP", "Aspect", "횡단 관심사(Cross-Cutting Concerns)" 같은 단어를 만나면 모두 추상적이라 안 잡혀요. 한국말로 풀어도 "관점 지향 프로그래밍", "가로지르는 관심사" — 여전히 와닿지 않습니다.
이 글에서는 횡단 보도 CCTV 비유로 풀어요. 모든 횡단 보도에 CCTV를 따로 박지 않고, 한 카메라가 여러 도로를 한꺼번에 감시하는 그림. 끝까지 따라오시면 "AOP가 왜 발명됐는지" 가 한 줄로 정리돼요.
횡단 관심사 — 모든 메서드에 박혀야 하는 똑같은 코드
자바 백엔드 코드를 짜다 보면 다음 같은 "공통 작업" 이 모든 메서드 앞뒤에 똑같이 박혀요.
public void placeOrder(Order order) {
log.info("placeOrder 시작 — userId={}", order.getUserId());
long start = System.currentTimeMillis();
try {
if (!currentUser.hasRole("USER")) throw new AccessDeniedException(); // 권한 검증
beginTransaction(); // 트랜잭션 시작
// ↓ 진짜 비즈니스 로직 (5줄)
validateOrder(order);
saveOrder(order);
notifyUser(order);
commitTransaction();
log.info("placeOrder 성공 — {}ms", System.currentTimeMillis() - start);
} catch (Exception e) {
rollbackTransaction();
log.error("placeOrder 실패", e);
throw e;
}
}
진짜 비즈니스 로직은 5줄. 나머지 15줄은 로깅·시간 측정·권한 검증·트랜잭션 관리 — 거의 모든 메서드에 똑같이 박혀야 하는 코드예요. 이걸 횡단 관심사(Cross-Cutting Concerns) 라고 불러요.
"가로지르는" 이라는 단어가 그림으로 들어와요. 비즈니스 로직 메서드가 세로로 쌓여 있다면, 로깅·트랜잭션은 모든 메서드를 가로로 가로지르며 박힙니다.
로깅 트랜잭션 권한
↓ ↓ ↓
─── placeOrder() ────────────────── ← 비즈니스 로직 메서드 1
─── cancelOrder() ──────────────── ← 비즈니스 로직 메서드 2
─── refundOrder() ──────────────── ← 비즈니스 로직 메서드 3
횡단 관심사가 코드에 박힐 때 문제 3가지
위 같이 모든 메서드에 횡단 관심사를 직접 박으면 세 가지 문제가 생겨요.
1. 코드 중복 — 같은 패턴이 N번 반복
회사 시스템에 메서드가 500개 있다면 — 똑같은 "로그 + 시간 측정 + try-catch" 코드가 500번 박혀요. 한 줄만 바꾸려 해도 500곳을 고쳐야 합니다.
2. 비즈니스 로직 가독성 폭락
진짜 "이 메서드가 무슨 일을 하는가" 가 로깅·트랜잭션 코드 사이에 묻혀요. 위 예제에서 비즈니스 로직 5줄이 15줄의 잡음 사이에 끼어 있는 그림.
3. 변경 폭발 — 정책 한 번에 N곳 수정
"앞으로 모든 메서드에 사용자 ID 로깅 추가" 같은 정책 변경이 들어오면 500개 메서드를 다 손봐야 해요. 회사 시스템 마비.
이 세 문제가 1990년대 후반 OOP만으로는 안 풀리는 한계로 떠올랐어요. 그 답이 AOP.
AOP — 한 곳에 모아 자동으로 박기
AOP(Aspect-Oriented Programming, 관점 지향 프로그래밍) 의 핵심 발상은 한 줄이에요. 횡단 관심사를 한 곳에 모아 정의하고, 컨테이너가 자동으로 모든 메서드에 끼워 넣게 한다.
위 placeOrder 코드를 AOP로 정리하면 이렇게 돼요.
// 비즈니스 로직 클래스 — 깔끔
@Service
public class OrderService {
@LogExecution
@PreAuthorize("hasRole('USER')")
@Transactional
public void placeOrder(Order order) {
validateOrder(order);
saveOrder(order);
notifyUser(order);
}
}
// 횡단 관심사 — 별도 클래스에 한 번만 정의
@Aspect
@Component
public class LoggingAspect {
@Around("@annotation(LogExecution)")
public Object logExecution(ProceedingJoinPoint pjp) throws Throwable {
long start = System.currentTimeMillis();
log.info("{} 시작", pjp.getSignature());
try {
Object result = pjp.proceed();
log.info("{} 성공 — {}ms", pjp.getSignature(), System.currentTimeMillis() - start);
return result;
} catch (Exception e) {
log.error("{} 실패", pjp.getSignature(), e);
throw e;
}
}
}
@LogExecution 어노테이션만 박으면 — LoggingAspect 가 자동으로 그 메서드를 감싸서 로깅·시간 측정·예외 처리를 다 처리해줘요. 비즈니스 로직 클래스는 깨끗하게 본인 일에만 집중. 트랜잭션도 마찬가지 — @Transactional 한 줄로 트랜잭션 관리 전체가 자동 처리.
횡단 보도 CCTV 비유 — 한 카메라가 여러 도로 감시
비유로 풀어볼게요. 도시의 모든 횡단 보도에 카메라가 필요하다고 합시다.
시나리오 A (AOP 없이) — 각 횡단 보도에 카메라를 따로 한 대씩 설치. 100개 횡단 보도 = 100대 카메라. 각각 별도 관리, 정책 변경 시 100대 다 손봐야.
시나리오 B (AOP) — 도시 중앙에 한 대의 거대한 감시 시스템 구축, 100개 횡단 보도를 자동으로 감시. 정책 변경은 중앙 한 곳에서. 새 횡단 보도가 생기면 자동으로 감시망에 포함.
자바 코드에서도 똑같아요. "모든 메서드에 박을 로깅" 을 메서드마다 박지 않고 — @Aspect 클래스 한 곳에 정의, 어노테이션 박힌 모든 메서드에 자동 적용. 새 메서드가 추가돼도 어노테이션만 박으면 자동으로 로깅 처리.
Spring AOP가 자주 활약하는 5곳
실무에서 AOP가 자동으로 도는 시나리오 5개.
| 횡단 관심사 | Spring에서 자동 처리 |
|---|---|
| 트랜잭션 관리 | @Transactional 어노테이션 (26편 깊이) |
| 보안 검증 | Spring Security의 @PreAuthorize·@PostAuthorize |
| 캐싱 | @Cacheable·@CacheEvict (36편) |
| 재시도 | @Retryable (Spring Retry) |
| 메트릭 수집 | Micrometer의 @Timed·@Counted |
이 5개 모두 사실은 AOP로 구현됨. 우리가 어노테이션 한 줄만 박으면 Spring AOP가 자동으로 메서드를 가로채 추가 로직을 끼워 넣어요. "마법처럼 동작" 의 정체가 AOP인 거예요.
Spring AOP의 동작 원리 — 프록시 패턴
Spring AOP는 어떻게 "메서드를 가로채는" 마법을 부리나? 답은 프록시(Proxy).
Spring이 @Aspect + 적용 대상을 발견하면, 원본 클래스 대신 "감싼" 프록시 클래스를 자동 생성해 컨테이너에 등록해요. 우리 코드가 OrderService 를 호출하는 척하지만 — 사실은 프록시 객체. 프록시가 횡단 관심사를 처리한 뒤 진짜 OrderService 메서드를 호출해줘요.
[클라이언트] → [Proxy(자동 생성)] → [실제 OrderService]
↑
여기서 로깅·트랜잭션·보안 자동 처리
프록시 생성 방식 2가지: - JDK Dynamic Proxy — 인터페이스 기반. 인터페이스 구현체에 적용 - CGLib Proxy — 클래스 상속 기반. 인터페이스 없는 클래스에 적용
Spring Boot 기본은 CGLib. 우리가 신경 쓸 일은 거의 없어요.
프록시 방식의 한계 한 가지 — 같은 클래스 안에서 메서드를 직접 호출하면 AOP가 안 먹혀요. 프록시를 안 거치고 this 호출로 가니까요. @Transactional 박은 메서드를 같은 클래스의 다른 메서드가 호출하면 트랜잭션 안 열림. 흔한 버그라 면접에서 자주 나옵니다.
AOP 5대 용어 — 미리 알아두기
18편에서 본격적으로 코드를 다루기 전, 용어 5개를 미리 만나둬요.
| 용어 | 의미 | 한국말 |
|---|---|---|
| Aspect | 횡단 관심사를 모은 단위 | "관점" — @Aspect 클래스 |
| Join Point | AOP가 끼어들 수 있는 모든 지점 | "결합점" — 메서드 호출 시점 등 |
| Pointcut | Join Point 중 "여기 끼겠다" 선언 | "절단점" — 표현식으로 지정 |
| Advice | 끼어들어 실제 실행할 코드 | "조언" — Before·After·Around |
| Weaving | Aspect와 비즈니스 코드를 결합 | "엮기" — Spring은 런타임 weaving |
지금 다 외울 필요 없어요. 18편에서 코드와 함께 다시 봐요.
한 줄 정리 — AOP = 횡단 관심사를 한 곳에 모아 자동으로 모든 메서드에 끼워 넣는 발상. Spring AOP는 프록시 패턴으로 구현. @Transactional·@PreAuthorize·@Cacheable 모두 AOP 기반.
시험 직전 한 번 더 — AOP 입문자가 매번 헷갈리는 것
- AOP = Aspect-Oriented Programming. 관점 지향 프로그래밍
- 횡단 관심사(Cross-Cutting Concerns) = 여러 메서드에 똑같이 박혀야 하는 공통 코드
- 대표 횡단 관심사 = 로깅 / 트랜잭션 / 보안 / 캐싱 / 메트릭
- 횡단 관심사를 코드에 직접 박을 때 문제 3가지 = 중복 / 가독성 / 변경 폭발
- AOP 핵심 발상 = 한 곳에 모아 정의, 컨테이너가 자동 적용
- 비유 = 횡단 보도 CCTV. 100개 카메라 X, 중앙 1대로 자동 감시
- Spring AOP는 프록시 패턴으로 구현
- 프록시 종류 2가지 = JDK Dynamic Proxy / CGLib Proxy
- Spring Boot 기본 = CGLib
- 프록시 한계 = 같은 클래스 안 메서드 자기 호출 시 AOP 안 먹힘
- 자기 호출 함정 =
@Transactional메서드를 같은 클래스 다른 메서드가 호출 시 트랜잭션 안 열림 - 해결법 = 다른 클래스로 분리 /
AopContext.currentProxy()(비권장) - AOP 5대 용어 = Aspect / Join Point / Pointcut / Advice / Weaving
- Aspect =
@Aspect박힌 클래스 — 횡단 관심사의 모음 - Pointcut = 어느 메서드에 적용할지 표현식으로 지정
- Advice = 실제 실행할 코드. Before·After·Around 3종
- Weaving = Aspect를 비즈니스 코드와 결합. Spring은 런타임 weaving
- Spring AOP는 메서드 호출만 가로챔 — 필드 접근·생성자는 가로채기 X
- 더 강력한 AOP = AspectJ — 컴파일 시점 weaving, 필드 접근도 가로채기 가능
- 99% 시나리오에서 Spring AOP로 충분 — AspectJ까지 갈 일 드물어
시리즈 다른 편 (앞뒤 글 모음)
이전 글:
- 18편 — @Component @Autowired 한 번에
- 19편 — @Configuration @Bean Java Config
- 20편 — Bean Scope Singleton과 Prototype
- 21편 — Bean 생명주기 PostConstruct PreDestroy
- 22편 — SpEL 표현식 언어
다음 글: