🤔 AOP(Aspect-Oriented Programming)란 ?
관점 지향 프로그래밍을 뜻함
공통적으로 사용하는 코드(로깅, 트랜잭션, 보안 검사 등)를 깔끔하게 분리해서 관리하는 방법
💡 왜 AOP가 필요할까?
예를 들어 모든 서비스 메서드마다 로그 찍는 코드가 있다면?
모든 곳에 저 로그 넣으면… 중복 + 유지보수 힘듦 → 이걸 한 곳에서 해결하는 게 AOP
✅ 스프링 AOP 동작 방식
스프링은 프록시 패턴을 사용해서
원래 객체 앞에 가짜(프록시) 객체를 둬서
메서드 실행 전/후에 부가기능을 추가
> 의미는 이렇다는데 나중에 추가로 공부해봐야겠.
💠 핵심 태그들
@Aspect | 이 클래스는 AOP 기능을 가진 클래스임을 명시 |
@Around | 대상 메서드 실행 전/후 모두 제어 |
@Before | 메서드 실행 전 실행 |
@After | 메서드 실행 후 무조건 실행 |
@AfterReturning | 메서드 정상 종료 후 실행 |
@AfterThrowing | 예외 발생 시 실행 |
1. @Aspect
이 클래스는 AOP 기능을 가진 클래스임을 명시
2. @Around
대상 메서드 실행 전/후 모두 제어 + 예외 발생 시 도 제어 함
좀 있다 나올 ProceedingJoinPoint와 필수로 같이 사용하는데 ProceedingJoinPoint.proceed()로 타겟 메서드를 실행 가능
이 메소드 실행 전,후로 나눠서 제어한다
사용방식은 위의 사진처럼 "execution()" 형식
/**
* 설명 @Around * -> 와일드 카드 모든 값 리턴 즉 Object org.뭐시기 -> 경로, deleteComment(..)의 .. 와일드 카드 0개 이상의 매개변수
*
* @param point 대상 메서드 실행을 제어할 때 사용
* @return 응답 본문
*/
@Around("execution(* org.example.expert.domain.comment.controller.CommentAdminController.deleteComment(..))")
public Object deleteCommentAuthCheck(ProceedingJoinPoint point) throws Throwable {
return setLog(point);
}
아래 와일드 카드를 보며 어떻게 대상 메소드를 지정하는지 보면 된다
✅ 와일드 카드
표현식의미
* | "하나의 요소"를 의미 (예: 메서드 이름, 리턴 타입) |
.. | "0개 이상"의 요소를 의미 (예: 패키지 깊이 or 파라미터 개수) |
🔍 자세히 비교
1. * : 하나의 아무거나
리턴 타입 | 모든 리턴 타입 | execution(* com.example.MyService.myMethod(..)) |
메서드 이름 | 어떤 이름이든 | execution(* com.example.MyService.*(..)) |
리턴 타입은 어떤 리턴타입을 가지는 메소드를 대상할 지 정하는 것 즉 *는 모두
2. .. : 0개 이상 / 가변 길이
위치의미예시
패키지 경로 | 하위 패키지 전부 | com.example..* → com.example.service, com.example.service.sub, 등 |
파라미터 | 0개 이상 인자 | (..) → (), (String), (String, int) 모두 매칭 |
파라미터는 어떤 파라미터를 가지는 메소를 대상으로 하는지 정하는 것 ..은 모두
com.example.. 하면 하위 패키지 전부
🔥 어노테이션 사용도 가능
@Around(@annotation(어노테이션 경로)) 형식으로도 사용가능
커스텀 어노테이션 만들고 해당 어노테이션이 적용된 메소드만 aop를 적용하겠다는 의미
✅ JoinPoint
스프링 AOP에서 Advice가 적용될 수 있는 지점 (메서드 호출 등)을 의미하는 개념
✅ 스프링에서의 JoinPoint와 ProceedingJoinPoint 차이
JoinPoint | 메서드 정보, 파라미터 등 확인 가능. @Before, @After 등에서 사용 |
ProceedingJoinPoint | JoinPoint 확장형으로, proceed()로 실제 메서드 실행 가능. @Around에서만 사용 가능 |
✅ ProceedingJoinPoint
- JoinPoint를 상속한 인터페이스
- 유일하게 proceed() 메서드가 있음 → 타겟 메서드 직접 실행
- @Around에서만 사용 가능
🔎 ProceedingJoinPoint 주요 기능
🧩 주요 메서드 정리
메서드설명
Object proceed() | 타겟 메서드 실행 |
Object[] getArgs() | 타겟 메서드에 전달된 인자 값들 |
Signature getSignature() | 실행될 메서드의 시그니처(이름, 리턴타입 등) |
Object getTarget() | 실제 대상 객체 (예: UserService 인스턴스) |
Object getThis() | 프록시 객체 (스프링 AOP가 감싼 proxy) |
SourceLocation getSourceLocation() | 소스 코드 위치 (디버깅용) |
StaticPart getStaticPart() | 정적인 정보 (시그니처 등 요약본) |
📖 사용예시
public Object setLog(ProceedingJoinPoint point) throws Throwable {
// 타겟 메소드 실행전
HttpServletRequest request = // 스프링이 현재 요청 컨텍스트에서 자동으로 꺼내줌
((ServletRequestAttributes) Objects.requireNonNull(
RequestContextHolder.getRequestAttributes())).getRequest();
Object[] arg = point.getArgs(); // 대상 메소드의 매개변수에 들어온 값 가져옴 배열형태
log.info("요청한 사용자의 ID -> {}", arg[0]);
log.info("API 요청 시각 -> {}", LocalDateTime.now());
log.info("API 요청 URL -> {}", request.getRequestURI());
log.info("요청 본문 -> {}", arg);
Object responseObj = point.proceed(); // 대상 메소드 실행
log.info("응답 본문 -> {}", responseObj); // 타겟 메소드 실행 후
return responseObj;
}