2025. 6. 13. 08:46ㆍspring
"공통 관심 사항을 핵심 비즈니스 로직에서 분리"하기 위한 개념
핵심 로직(what)과 부가 로직(how)을 분리하여 코드의 관심사(Concern)를 분리함
예시로 이해해보자
예를 들어, 모든 서비스 로직 전에 로그를 출력하고, 끝나면 실행 시간을 측정하고 싶다고 해보자:
public void order() {
System.out.println("로그 출력");
// 주문 비즈니스 로직
System.out.println("실행 시간 측정");
}
이걸 모든 서비스 클래스에 반복하면 코드가 중복되고 복잡해짐.
→ 이걸 해결하기 위해 AOP를 사용!
핵심 용어 정리
Aspect => 공통 관심사 (예: 로그 출력, 트랜잭션, 보안 등)
Advice => 언제, 어떤 작업을 실행할지 정의 (메서드 실행 전/후 등)
JoinPoint => Advice가 적용될 수 있는 지점 (예: 메서드 실행 시점)
Pointcut => 어떤 JoinPoint에 Advice를 적용할지 정의 (예: 특정 패키지의 모든 메서드)
Weaving => Advice를 핵심 코드에 끼워 넣는 작업
Target => 공통 기능이 적용될 대상 객체
Proxy => AOP가 적용된 객체는 프록시 객체로 만들어져 동작
프록시(Proxy)란?
프록시(Proxy) 객체는 ‘진짜 객체(Target)’를 대신해서 동작하는 객체입니다.
프록시는 진짜 객체와 똑같은 메서드를 가지고 있지만, 내부적으로는 다음과 같이 동작합니다:
- 누군가가 프록시 객체의 메서드를 호출하면,
- 프록시 객체는 **AOP 공통 기능(Advice)**을 먼저 수행하거나 감싸고,
- 그 후에 진짜 객체의 메서드를 호출합니다.
- 그리고 결과를 그대로 돌려줍니다.
✅ 코드로 보는 프록시 동작
🎯 원래 객체 (Target)
public class MemberService {
public void join() {
System.out.println("회원 가입 처리");
}
}
👥 프록시 객체 (Proxy)
public class MemberServiceProxy extends MemberService {
private MemberService target;
public MemberServiceProxy(MemberService target) {
this.target = target;
}
@Override
public void join() {
// Before Advice (공통 기능)
System.out.println("[로그] 회원가입 시작");
// 실제 대상 메서드 실행
target.join();
// After Advice (공통 기능)
System.out.println("[로그] 회원가입 완료");
}
}
📞 사용하는 쪽
MemberService target = new MemberService();
MemberService proxy = new MemberServiceProxy(target);
proxy.join();
🔸 AOP 작동 방식
Spring AOP는 기본적으로 프록시 기반으로 동작합니다.
- Spring AOP는 JDK 동적 프록시 또는 CGLIB 프록시를 사용하여 대상 객체에 Advice를 적용
- 인터페이스 기반이면 JDK 동적 프록시 사용
- 클래스 기반이면 CGLIB 사용
구분적용 방식
@Aspect + @Before 등 AOP 구현 방식 | 프록시 기반 AOP (어노테이션 기반) |
타깃 객체가 인터페이스 있을 때 | JDK 동적 프록시 방식 |
타깃 객체가 인터페이스 없을 때 | CGLIB 방식 |
🔸 Advice 종류
@Before | 대상 메서드 실행 전에 실행 |
@After | 대상 메서드 실행 후에 실행 (정상/예외 관계없이 무조건 실행) |
@AfterReturning | 대상 메서드가 정상 종료 후 실행 |
@AfterThrowing | 대상 메서드에서 예외 발생 후 실행 |
@Around | 대상 메서드 실행 전/후 전체를 감싸는 Advice. 가장 강력 |
✅ @Aspect란?
@Aspect는 **공통 관심사(Advice)**를 담은 클래스를 나타내기 위한 Spring AOP의 어노테이션입니다.
즉, 이 어노테이션이 붙은 클래스는 AOP 로직을 담는 클래스이고, 하나 이상의 Advice (@Before, @After 등)를 포함합니다.
구성 예시
@Aspect
@Component // 빈으로 등록되어야 AOP가 적용됨
public class LoggingAspect {
@Before("execution(* com.example.service.*.*(..))")
public void logBefore(JoinPoint joinPoint) {
System.out.println("메서드 실행 전: " + joinPoint.getSignature().getName());
}
@AfterReturning(pointcut = "execution(* com.example.service.*.*(..))", returning = "result")
public void logAfter(JoinPoint joinPoint, Object result) {
System.out.println("메서드 실행 후: " + joinPoint.getSignature().getName() + ", 결과: " + result);
}
}
🔸 실습 예시
1. 의존성 설정 (spring-boot-starter-aop 사용 시)
<!-- build.gradle 또는 pom.xml -->
implementation 'org.springframework.boot:spring-boot-starter-aop'
2. Aspect 클래스 작성
@Aspect
@Component
public class LoggingAspect {
@Before("execution(* com.example.service.*.*(..))")
public void logBeforeMethod(JoinPoint joinPoint) {
System.out.println("Before Method: " + joinPoint.getSignature().getName());
}
@AfterReturning(pointcut = "execution(* com.example.service.*.*(..))", returning = "result")
public void logAfterMethod(JoinPoint joinPoint, Object result) {
System.out.println("After Method: " + joinPoint.getSignature().getName() + ", result: " + result);
}
@AfterThrowing(pointcut = "execution(* com.example.service.*.*(..))", throwing = "ex")
public void logException(JoinPoint joinPoint, Exception ex) {
System.out.println("Exception in Method: " + joinPoint.getSignature().getName() + ", exception: " + ex.getMessage());
}
@Around("execution(* com.example.service.*.*(..))")
public Object aroundAdvice(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("Around Before: " + pjp.getSignature().getName());
Object result = pjp.proceed(); // 실제 메서드 실행
System.out.println("Around After: " + pjp.getSignature().getName());
return result;
}
}
🔸 Pointcut 표현식 예시
execution(* com.example.service.*.*(..)) | com.example.service 패키지의 모든 메서드 |
execution(public void set*(*)) | set으로 시작하는 모든 public void 메서드 |
@annotation(org.springframework.transaction.annotation.Transactional) | 특정 어노테이션이 붙은 메서드 |
🔸 실무에서의 활용 예
트랜잭션 처리 | @Transactional 어노테이션 내부적으로 AOP로 처리 |
로깅 처리 | 요청, 응답 로그 기록 (예: RestControllerAdvice + AOP) |
보안 처리 | 권한 체크 등 (예: @PreAuthorize) |
성능 측정 | 메서드 실행 시간 측정 등 |
🔸 주의 사항
- Spring AOP는 Spring Bean에만 적용됨 (new로 생성한 객체에는 적용되지 않음)
- private, final 메서드에는 AOP가 적용되지 않음
- @Around Advice에서는 반드시 pjp.proceed()를 호출해야 실제 메서드가 실행됨
'spring' 카테고리의 다른 글
스프링 MVC (1) | 2025.06.13 |
---|---|
스프링 컨테이너 (0) | 2025.06.13 |
스프링 Bean 생명주기와 scope (3) | 2025.06.12 |
DI (의존성 주입, Dependency Injection) (0) | 2025.06.12 |
IoC (제어의 역전, Inversion of Control) (0) | 2025.06.12 |