예를 들어, 시간을 측정하는 로직이 추가되어야 한다는 지시사항을 받았을 때..
public class MemberService {
/**
* 회원가입
*/
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");
}
}
/**
* 전체 회원 조회
*/
public List<Member> findMembers() {
long start = System.currentTimeMillis();
try {
return memberRepository.findAll();
} finally {
long finish = System.currentTimeMillis();
long timeMs = finish - start;
System.out.println("findMembers " + timeMs + "ms");
}
}
}
→ 이렇게 각 로직마다 finish ,timeMs 사용해 시간 계산
→ 이는 코드 중복과 유지보수 어려워짐 !!!
→ 그래서 AOP를 적용하는 것
→ 관심사의 분리(Separation of Concerns)과 유지보수에 큰 차이 존재
공통점
차이점
// 시간 측정을 위한 유틸 클래스
public class TimeLogger {
public static void measure(Runnable task) {
long start = System.currentTimeMillis();
try {
task.run();
} finally {
long end = System.currentTimeMillis();
System.out.println("실행 시간: " + (end - start) + "ms");
}
}
}
// 서비스 클래스
@Service
public class MemberService {
public void joinMember() {
// 비즈니스 로직 전에 직접 TimeLogger 호출
TimeLogger.measure(() -> {
// 실제 로직
System.out.println("회원 가입 처리 로직 실행");
});
}
} 혹은 TimeLogger.measure(() -> logic()); 같은 식으로 직접 호출해야 함.// 시간 측정을 담당하는 AOP 클래스
@Aspect
@Component
public class TimeTraceAop {
// 적용할 대상 지정 -> 패키지 명으로 지정 가능
@Around("execution(* com.example..*(..))")
public Object execute(ProceedingJoinPoint joinPoint) throws Throwable {
long start = System.currentTimeMillis();
try {
return joinPoint.proceed(); // 실제 타깃 메서드 실행
} finally {
long end = System.currentTimeMillis();
System.out.println(joinPoint.getSignature() + " 실행 시간: " + (end - start) + "ms");
}
}
}
// com.example 패키지 안에 들어있다면 프록시가 자동으로 호출 가로채서 시간 측정
// 서비스 클래스
@Service
public class MemberService {
public void joinMember() {
// 비즈니스 로직만 남음
System.out.println("회원 가입 처리 로직 실행");
}
}| 구분 | 유틸 클래스(시간 측정기) | AOP |
|---|---|---|
| 적용 방식 | 코드 안에 직접 호출 | 프록시가 자동 적용 |
| 코드 중복 | 호출부마다 필요 | 없음 (핵심 로직만 유지) |
| 유지보수 | 호출부 전체 수정 필요 | 어드바이스만 수정하면 전역 반영 |
| 확장성 | 시간 측정만 | 트랜잭션, 보안, 로깅 등 다양하게 확장 |
| 제어 범위 | 호출한 부분만 | Pointcut으로 패턴 지정 가능 |
@Before: 메서드 실행 전@AfterReturning: 메서드 정상 종료 후@AfterThrowing: 예외 발생 시@After: 무조건 실행@Around: 메서드 실행 전/후를 모두 제어Spring AOP는 런타임 프록시 기반이므로 실행 중에 프록시 객체를 생성
프록시란?
자신이 클라이언트가 사용하려고 하는 실제 대상인 것처럼 위장해 클라이언트의 요청을 받아주는 것 (대리인, 대리자)
- 클라이언트가 타킷에 접근하는 방법 제어 위해 (프록시 패턴)
- 타깃에 부가적인 기능 부여를 위해 (데코레이터 패턴)
com.example.MyService$$EnhancerBySpringCGLIB... → 내부적으로 원본 객체를 들고 있으며, 호출 시 부가기능을 삽입.횡단 관심사?
핵심 비즈니스 로직과는 연관은 없지만 여러 모듈이나 계층에 공통적으로 사용되는 기능
- 핵심 관심사
- 상품 주문
- 결제 처리
- 배송 관리
- 횡단 관심사
- 로깅(logging) → 누가 언제 어떤 메서드를 호출했는가
- 보안(security) → 로그인 사용자만 접근 가능
- 트랜잭션(transaction) → DB 작업이 중간에 끊기면 롤백
- 성능 모니터링(performance monitoring) → 실행 시간 측정
- 예외 처리(exception handling)
→ 이런 횡단 관심사를 Apsect로 분리해 AOP 프록시가 대신 처리하도록 함

@Around 어드바이스 진행joinPoint.proceed() 호출 시:proceed()를 호출하지 않으면 타깃 메서드는 실행되지 않음@AfterReturning → 정상 종료 시@AfterThrowing → 예외 발생 시@After → 예외 여부와 관계없이 항상 실행