들어가며
Spring을 공부하다 보면 @Transactional이나 @Cacheable을 아무 생각 없이 붙여쓰게 된다.
그런데 이게 대체 어떻게 동작하는 걸까? 내가 호출한 메서드에 트랜잭션 처리 코드를 직접 작성한 적이 없는데.
그 비밀이 바로 AOP(Aspect Oriented Programming)다. 오늘은 AOP가 왜 필요한지부터, 실제로 내부에서 어떻게 동작하는지까지 정리해본다.
문제 상황 — 반복되는 코드가 나를 괴롭힌다
메서드를 하나 작성한다고 생각해보자.
public void insert(Buddy buddy) {
log.debug("buddy: " + buddy); // 로깅
long start = System.currentTimeMillis(); // 성능 측정 시작
con.setAutoCommit(false); // 트랜잭션 시작
try {
repo.insert(con, buddy); // ← 핵심 로직 (이것만 바뀜)
con.commit();
} catch (Exception e) {
con.rollback();
} finally {
con.close();
}
log.debug("소요시간: {}", System.currentTimeMillis() - start);
}
로직의 95%가 로깅, 트랜잭션, 성능 측정이다. 비즈니스 로직은 딱 한 줄. 그리고 이 패턴이 delete(), update()에도 똑같이 반복된다.
이런 코드를 횡단 관심사(Cross-cutting Concern)라고 한다. 여러 메서드를 가로질러 반복되는 부가 기능이다. 로깅, 트랜잭션, 보안, 성능 측정이 대표적이다.
AOP는 이 횡단 관심사를 핵심 로직에서 분리해서, 비즈니스 메서드에는 핵심 로직만 남기는 패러다임이다.
핵심 용어 정리
처음엔 용어가 헷갈린다. 딱 이렇게 기억하면 된다.
| 용어 | 한 줄 설명 |
|---|---|
| Target | AOP가 적용될 진짜 객체 (비즈니스 로직 보유) |
| Aspect | 횡단 관심사를 모듈화한 클래스 (해결책) |
| Advice | Aspect 안에서 실행될 실제 부가 로직 코드 |
| Pointcut | Advice를 어느 메서드에 적용할지 결정하는 필터 |
| Weaving | Aspect를 Target에 적용하는 행위 자체 |
헷갈리기 쉬운 것: "횡단 관심사 = Aspect"가 아니다.
횡단 관심사는 문제고, Aspect는 그 문제를 모듈화해서 담은 해결책이다.
Spring AOP의 동작 원리 — Proxy
Spring AOP는 Proxy 기반으로 동작한다. Proxy는 대리인이다. Target 앞에 세워두는 대리 객체로, 호출자는 Proxy인지 모르고 그냥 호출하지만 Proxy가 중간에서 Advice를 실행한 뒤 Target을 부른다.
// 호출자는 그냥 mDao를 부르는 것 같지만
mDao.selectDetail(email);
// 실제로는 이렇게 동작한다
// 1. Proxy가 받아서 @Before Advice 실행
// 2. 진짜 selectDetail() 호출
// 3. @After Advice 실행
// mDao의 실제 타입: BasicMemberDao$$SpringCGLIB$$0
Spring은 @Aspect가 붙은 클래스를 감지하면, Pointcut에 해당하는 Target 빈 대신 CGLib이 생성한 Proxy 빈을 컨테이너에 등록한다. 그래서 개발자가 명시적으로 부가 로직을 호출하지 않아도 자동으로 끼어드는 것이다.
Aspect 작성 방법
의존성부터 추가한다.
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
그리고 Aspect 클래스를 작성한다.
@Component // 빈 등록
@Aspect // AOP Aspect 선언
@Slf4j
public class LoggingAspect {
@Before("execution(* com.ssafy..dao.*.*(..))")
public void loggingDao(JoinPoint jp) {
log.debug("method: {}, args: {}", jp.getSignature(), Arrays.toString(jp.getArgs()));
}
}
execution(* com.ssafy..dao.*.*(..))는 Pointcut 표현식이다. 순서대로 읽으면
"리턴타입 무관 / com.ssafy 하위 dao 패키지 / 모든 클래스 / 모든 메서드 / 모든 파라미터"다.
Advice 5가지 타입
언제 실행되느냐에 따라 5종류가 있다.
| Advice | 실행 시점 | 특징 |
|---|---|---|
@Before | 호출 전 | 파라미터 객체 속성 변경 가능 |
@AfterReturning | 정상 반환 후 | 리턴 값 객체 속성 변경 가능 |
@AfterThrowing | 예외 발생 후 | 예외 참조만 전달 (catch 불가) |
@After | 항상 (finally) | 성공·예외 무관하게 실행 |
@Around | 전후 전체 | 파라미터·리턴값 완전 대체, Target 실행 제어 |
특히 @Around는 가장 강력하다. proceed()를 호출해야 Target이 실행되므로, 호출하지 않으면 Target을 막을 수도 있다.
@Around("execution(* com.ssafy.service.*.*(..))")
public Object measureTime(ProceedingJoinPoint pjp) throws Throwable {
long start = System.currentTimeMillis();
Object result = pjp.proceed(); // Target 실행
log.debug("소요시간: {}ms", System.currentTimeMillis() - start);
return result;
}
@Around의 대표 활용 사례:
- 성능 측정 —
proceed()전후 시간 측정 - 캐싱 (
@Cacheable) — 캐시 히트 시proceed()없이 바로 반환 - 트랜잭션 (
@Transactional) —proceed()전후로 begin/commit/rollback - 재시도(Retry) — 예외 시
proceed()N번 재호출 - 인증/권한 — 조건 불충족 시
proceed()차단
정리
AOP를 한 줄로 요약하면: "비즈니스 메서드는 핵심 로직만, 부가 기능은 Aspect에게"다.
평소에 그냥 붙여쓰던 @Transactional이 사실 @Around Advice로 구현된 AOP라는 걸 알고 나면, 이 애너테이션이 어떤 시점에 어떻게 동작하는지 더 명확하게 보인다.
Spring이 마법처럼 처리해주는 것들 대부분은 AOP Proxy가 조용히 중간에 끼어들어 하는 일이다.
참고
- 삼성청년 SW·AI아카데미 Framework 강의 — AOP 실습 자료
- Spring 공식 문서 — Aspect Oriented Programming with Spring
'Project > Spring' 카테고리의 다른 글
| Spring MVC 심화 — @ModelAttribute부터 @ControllerAdvice, MockMvc 테스트 전략까지 (0) | 2026.05.13 |
|---|---|
| Spring MVC — DispatcherServlet이 요청을 처리하는 11단계 흐름 (0) | 2026.05.12 |
| Spring DI/어노테이션 (2) | 2026.05.06 |
| Spring DI·AOP·POJO/TEST (0) | 2026.05.04 |
| Choreography Saga 패턴으로 MSA 분산 트랜잭션 구현하기 (0) | 2026.05.03 |