인프런 - 라이프타임 커리어 플랫폼
프로그래밍, 인공지능, 데이터, 마케팅, 디자인등 입문부터 실전까지 업계 최고 선배들에게 배울 수 있는 곳.
www.inflearn.com
인프런 사이트의 김영한님의 강의를 보면서 작성한 글입니다.
애플리케이션을 개발하다 보면, 핵심 비즈니스 로직은 아니지만 여러 메소드에 걸쳐 공통적으로 적용해야 하는 부가 기능들이 있습니다. 예를 들어, 모든 메소드의 실행 시간을 측정하거나, 모든 API 호출에 대한 로그를 남기는 경우입니다.
이러한 부가 기능들을 **공통 관심 사항(Cross-Cutting Concern)**이라고 하며, 회원 가입이나 조회 같은 본연의 기능을 **핵심 관심 사항(Core Concern)**이라고 합니다. 스프링 AOP는 이 둘을 깔끔하게 분리하여 코드의 유지보수성을 획기적으로 높여주는 기술입니다.
## 1. 문제 상황: 모든 메소드의 실행 시간 측정하기
만약 우리가 모든 서비스 메소드의 실행 시간을 측정해야 한다면, AOP 없이는 다음과 같이 모든 메소드에 시간 측정 코드를 직접 추가해야 합니다.
public Long join(Member member) {
long start = System.currentTimeMillis(); // 시간 측정 시작
try {
validateDuplicateMember(member); // 핵심 관심 사항
memberRepository.save(member);
return member.getId();
} finally {
long finish = System.currentTimeMillis(); // 시간 측정 종료
long timeMs = finish - start;
System.out.println("join " + timeMs + "ms");
}
}
이 방식의 문제점:
- 코드 중복: 시간 측정 코드가 모든 메소드에 중복으로 들어갑니다.
- 핵심 로직 오염: 비즈니스 로직과 부가 기능 로직이 섞여 있어 코드가 지저분해집니다.
- 유지보수 어려움: 시간 측정 로직을 변경하려면 관련된 모든 메소드를 일일이 수정해야 합니다.
## 2. 해결책: 스프링 AOP 도입
**AOP(Aspect Oriented Programming, 관점 지향 프로그래밍)**는 위와 같은 공통 관심 사항을 **'Aspect'**라는 별도의 모듈로 분리하여 관리하는 프로그래밍 패러다임입니다.
### TimeTraceAop 구현하기
aop 패키지를 하나 생성하고, 시간 측정을 담당할 TimeTraceAop 클래스를 작성합니다.
package hello.hello_spring.aop;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
@Aspect // 이 클래스가 AOP의 Aspect임을 나타냅니다.
@Component // 스프링 빈으로 등록합니다.
public class TimeTraceAop {
// "hello.hello_spring" 패키지와 그 하위 패키지에 모두 적용하라는 의미입니다.
@Around("execution(* hello.hello_spring..*(..))")
public Object execute(ProceedingJoinPoint joinPoint) throws Throwable {
long start = System.currentTimeMillis();
System.out.println("START: " + joinPoint.toString());
try {
// 이 코드가 실제 타겟 메소드(핵심 관심 사항)를 실행하는 부분입니다.
return joinPoint.proceed();
} finally {
long finish = System.currentTimeMillis();
long timeMs = finish - start;
System.out.println("END: " + joinPoint.toString() + " " + timeMs + "ms");
}
}
}
- @Aspect: 이 클래스가 공통 관심 사항을 담고 있는 Aspect 클래스임을 명시합니다.
- @Component: 스프링이 이 Aspect를 빈으로 관리하도록 합니다.
- @Around(...): AOP의 Advice 타입 중 하나로, 타겟 메소드의 실행 전과 후 모두에 개입하여 부가 기능을 추가할 수 있습니다. 괄호 안의 Pointcut 표현식은 "어디에 적용할 것인가"를 정의합니다.
- ProceedingJoinPoint: @Around 어드바이스에서 사용되는 특별한 파라미터로, 실제 타겟 메소드에 대한 정보를 담고 있습니다. joinPoint.proceed()를 호출해야 비로소 원래의 비즈니스 메소드가 실행됩니다.
이제 우리는 비즈니스 로직 코드는 단 한 줄도 건드리지 않고, 모든 메소드의 실행 시간을 측정하는 공통 기능을 깔끔하게 적용했습니다.
## 3. 스프링 AOP의 동작 원리: 프록시 (Proxy)
AOP는 어떻게 이런 마법 같은 일을 할 수 있을까요? 바로 **프록시(Proxy)**라는 기술을 사용하기 때문입니다.
- AOP 적용 전: 컨트롤러가 서비스의 실제 객체를 직접 호출하는, 단순한 의존관계였습니다.

AOP 적용 전 의존관계 - AOP 적용 후: 스프링은 시작 시점에 MemberService의 가짜(Proxy) 객체를 하나 만듭니다. 그리고 컨트롤러에는 실제 객체 대신 이 프록시 객체를 주입해 줍니다.

AOP 적용 후 의존관계
동작 흐름:
- 컨트롤러가 memberService.join()을 호출하면, 실제로는 프록시 객체의 join() 메소드가 호출됩니다.
- 프록시 객체는 TimeTraceAop의 execute() 메소드를 먼저 실행합니다.
- execute() 메소드 내부에서 joinPoint.proceed()가 호출되면, 그제서야 실제 MemberService 객체의 join() 메소드가 실행됩니다.
- 실제 메소드 실행이 끝나면, 다시 execute() 메소드의 finally 블록으로 돌아와 시간 측정 로직을 마저 수행하고 최종 결과를 컨트롤러에 반환합니다.
이처럼 스프링 AOP는 가짜 프록시 객체를 내세워, 핵심 기능 실행 전후에 공통 관심 사항을 자연스럽게 끼워 넣는 방식으로 동작합니다. @Transactional 어노테이션 역시 AOP를 기반으로 동작하는 대표적인 예입니다.

'개발 공부 > 백엔드' 카테고리의 다른 글
| 제미나이와 게시판 만들기: (1) 프로젝트 설정과 도메인 설계 (0) | 2025.10.03 |
|---|---|
| REST API, 개념부터 설계 원칙까지 한 번에 이해하기 (0) | 2025.10.03 |
| 스프링 DB 접근 기술 : 4. 스프링 데이터 JPA (0) | 2025.09.29 |
| 스프링 DB 접근 기술 : 3. JPA (0) | 2025.09.29 |
| 스프링 DB 접근 기술 : 2. JdbcTemplate (0) | 2025.09.27 |