스프링 AOP 포인트컷의 이해
- 김영한님의 스프링 원리 - 고급편 강의를 바탕으로 스프링 AOP의 포인트컷 지시자의 종류와 사용법을 정리함
포인트컷 지시자
-
포인트컷 표현식은
execution같은 포인트컷 지시자(Pointcut Designator)로 시작함execution- 메서드 실행 조인 포인트 매칭
within- 특정 타입 내 조인 포인트 매칭
- 부모 타입 지정 불가
args- 인자 타입으로 매칭 (런타임 판단)
- 단독 사용 금지
this/targetthis는 프록시 객체,target은 실제 타겟 객체 대상- 주로 파라미터 바인딩용으로 사용됨
@target/@within- 클래스에 애노테이션 있는 경우 (
@target은 부모 포함,@within은 해당 클래스만) - 단독 사용 금지
- 클래스에 애노테이션 있는 경우 (
@annotation- 메서드에 애노테이션 있는 경우 매칭
@args- 인수 런타임 타입에 애노테이션
- 단독 사용 금지
bean- 빈 이름으로 지정 (스프링 전용)
@target,@args,args등은 스프링이 애플리케이션을 초기화할 때 모든 빈(스프링 내부 빈 포함)에 프록시를 적용하려 시도할 위험이 있음-
따라서 항상
execution같은 지시자와 조합해서 프록시 적용 범위를 제한한 후 사용해야 함1 2 3 4 5
// 잘못된 예: 모든 스프링 빈에 프록시 적용을 시도하여 빈 생성 오류 가능성 발생 @Around("@target(hello.aop.member.annotation.ClassAop)") // 올바른 예: execution으로 범위를 먼저 축소함 @Around("execution(* hello.aop..*(..)) && @target(hello.aop.member.annotation.ClassAop)")
예제 코드 세팅
커스텀 애노테이션
-
포인트컷 매칭을 테스트하기 위한 애노테이션을 정의함
1 2 3 4 5 6 7 8 9
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface ClassAop {} @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface MethodAop { String value(); }
대상 클래스
-
인터페이스와 구현체를 준비함
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
public interface MemberService { String hello(String param); } @ClassAop @Component public class MemberServiceImpl implements MemberService { @Override @MethodAop("test value") public String hello(String param) { return "ok"; } public String internal(String param) { return "ok"; } }
테스트 기본 설정
-
리플렉션을 사용해
helloMethod정보를 추출해둠1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
@Slf4j public class ExecutionTest { AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut(); Method helloMethod; @BeforeEach public void init() throws NoSuchMethodException { helloMethod = MemberServiceImpl.class.getMethod("hello", String.class); } @Test void printMethod() { log.info("helloMethod={}", helloMethod); } }
execution 지시자
문법 구조
-
메서드 실행 조인 포인트를 매칭하며 스프링 AOP에서 가장 많이 사용됨
1
execution(접근제어자? 반환타입 선언타입?메서드이름(파라미터) 예외?)

?가 붙은 속성은 생략 가능하며 타입과 파라미터는 필수임-
*같은 패턴이나..을 통해 매개변수와 패키지를 유연하게 지정할 수 있음1 2 3
pointcut.setExpression("execution(public String hello.aop.member.MemberServiceImpl.hello(String))"); pointcut.setExpression("execution(* *(..))");
메서드 이름 매칭
| 표현식 | 설명 | 매칭 예 |
|——–|——|———|
| execution(* hello(..)) | 정확히 hello | hello(String) 일치 |
| execution(* hel*(..)) | hel로 시작 | hello, help 일치 |
| execution(* *el*(..)) | el 포함 | hello, select 일치 |
| execution(* nono(..)) | 정확히 nono | hello 불일치 |
패키지 매칭
| 표현식 | 설명 | 결과 |
|——–|——|——|
| execution(* hello.aop.member.MemberServiceImpl.hello(..)) | 정확한 경로 | 일치 |
| execution(* hello.aop.member.*.*(..)) | member 패키지의 모든 클래스 | 일치 |
| execution(* hello.aop.*.*(..)) | aop 바로 아래만 (member 제외) | 불일치 |
| execution(* hello.aop.member..*.*(..)) | member 패키지 + 하위 패키지 | 일치 |
| execution(* hello.aop..*.*(..)) | aop 패키지 + 하위 패키지 전체 | 일치 |
.은 정확히 해당 위치의 패키지를 의미함..은 해당 위치의 패키지 및 그 하위 패키지 전체를 의미함
타입 매칭 (부모 타입 허용)
-
execution은 부모 타입을 선언해도 그 자식 타입까지 매칭됨1 2 3
pointcut.setExpression("execution(* hello.aop.member.MemberServiceImpl.*(..))"); pointcut.setExpression("execution(* hello.aop.member.MemberService.*(..))");
-
단 부모 타입에 선언되지 않은 자식 고유의 메서드는 매칭되지 않으므로 주의가 필요함
파라미터 매칭
| 표현식 | 의미 |
|——–|——|
| (String) | 정확히 String 타입 파라미터 1개 |
| () | 파라미터 없음 |
| (*) | 정확히 1개, 타입 무관 |
| (*, *) | 정확히 2개, 타입 무관 |
| (..) | 개수와 타입 모두 무관 (0개 이상) |
| (String, ..) | 첫 번째는 String, 이후 개수와 타입 무관 |
1
2
3
4
5
assertThat(pointcut("execution(* *(String))").matches(helloMethod, MemberServiceImpl.class)).isTrue();
assertThat(pointcut("execution(* *())").matches(helloMethod, MemberServiceImpl.class)).isFalse();
assertThat(pointcut("execution(* *(*))").matches(helloMethod, MemberServiceImpl.class)).isTrue();
assertThat(pointcut("execution(* *(..))").matches(helloMethod, MemberServiceImpl.class)).isTrue();
assertThat(pointcut("execution(* *(String, ..))").matches(helloMethod, MemberServiceImpl.class)).isTrue();
within 지시자
- 특정 타입 내의 모든 메서드를 조인 포인트로 지정함
-
execution에서 타입 부분만 사용하는 형태와 유사함1 2 3 4 5
pointcut.setExpression("within(hello.aop.member.MemberServiceImpl)"); pointcut.setExpression("within(hello.aop.member.*Service*)"); pointcut.setExpression("within(hello.aop..*)");
execution과 within의 차이
within은 부모 타입이나 인터페이스를 지정할 수 없고 정확한 구체 클래스 타입만 지정해야 함-
부모 타입으로 지정하면 매칭에 실패함
1 2 3
pointcut.setExpression("within(hello.aop.member.MemberService)"); pointcut.setExpression("execution(* hello.aop.member.MemberService.*(..))");
지시자 부모 타입 지정 특징 execution허용 메서드 시그니처 기반 정밀 매칭 within불가 타입만 지정, 정확히 일치해야 함
args 지시자
- 메서드의 인자가 주어진 타입의 인스턴스인 조인 포인트를 매칭함
- 문법은
execution의 파라미터 부분과 동일함
execution 파라미터와 args의 차이
-
execution은 파라미터의 정적 선언 타입을 기준으로 판단하지만args는 동적으로 넘어오는 인자의 런타임 타입을 기준으로 판단함1 2 3 4 5 6 7
pointcut("args(String)").matches(helloMethod, MemberServiceImpl.class) pointcut("args(Object)").matches(helloMethod, MemberServiceImpl.class) pointcut("args(java.io.Serializable)").matches(helloMethod, MemberServiceImpl.class) pointcut("execution(* *(String))").matches(helloMethod, MemberServiceImpl.class) pointcut("execution(* *(Object))").matches(helloMethod, MemberServiceImpl.class) pointcut("execution(* *(java.io.Serializable))").matches(helloMethod, MemberServiceImpl.class)
-
args는 단독으로 쓰이기보다는 주로 파라미터 바인딩 용도로 사용됨
@target과 @within 지시자
-
특정 애노테이션이 타입에 선언되어 있을 때 대상을 매칭함

@target은 인스턴스의 모든 메서드(부모 메서드 포함)를 어드바이스 적용 대상으로 함-
@within은 해당 애노테이션이 선언된 클래스의 메서드만 어드바이스 적용 대상으로 함1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
@Slf4j @Aspect static class AtTargetAtWithinAspect { @Around("execution(* hello.aop..*(..)) && @target(hello.aop.member.annotation.ClassAop)") public Object atTarget(ProceedingJoinPoint joinPoint) throws Throwable { log.info("[@target] {}", joinPoint.getSignature()); return joinPoint.proceed(); } @Around("execution(* hello.aop..*(..)) && @within(hello.aop.member.annotation.ClassAop)") public Object atWithin(ProceedingJoinPoint joinPoint) throws Throwable { log.info("[@within] {}", joinPoint.getSignature()); return joinPoint.proceed(); } }
실행 결과
1
2
3
[@target] void ...Child.childMethod()
[@within] void ...Child.childMethod()
[@target] void ...Parent.parentMethod()
| 지시자 | 적용 범위 | 부모 메서드 포함 |
|---|---|---|
@target |
인스턴스의 모든 메서드 | 포함 |
@within |
해당 타입에 선언된 메서드만 | 미포함 |
@annotation과 bean 지시자
@annotation
-
메서드에 특정 애노테이션이 선언되어 있을 때 매칭함
1 2
@MethodAop("test value") public String hello(String param) { ... }
1 2 3 4 5 6 7 8 9 10
@Slf4j @Aspect static class AtAnnotationAspect { @Around("@annotation(hello.aop.member.annotation.MethodAop)") public Object doAtAnnotation(ProceedingJoinPoint joinPoint) throws Throwable { log.info("[@annotation] {}", joinPoint.getSignature()); return joinPoint.proceed(); } }
bean 매칭
- 스프링 컨테이너의 빈 이름으로 대상을 지정하는 스프링 AOP 전용 지시자임
-
*패턴을 사용할 수 있음1 2 3 4 5 6 7 8 9
@Aspect static class BeanAspect { @Around("bean(orderService) || bean(*Repository)") public Object doLog(ProceedingJoinPoint joinPoint) throws Throwable { log.info("[bean] {}", joinPoint.getSignature()); return joinPoint.proceed(); } }
파라미터 바인딩
-
포인트컷 표현식을 사용해 어드바이스 메서드로 다양한 종류의 데이터를 직접 받을 수 있음
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
@Slf4j @Aspect static class ParameterAspect { @Pointcut("execution(* hello.aop.member..*.*(..))") private void allMember() {} @Around("allMember()") public Object logArgs1(ProceedingJoinPoint joinPoint) throws Throwable { Object arg1 = joinPoint.getArgs()[0]; log.info("[logArgs1] {}, arg={}", joinPoint.getSignature(), arg1); return joinPoint.proceed(); } @Around("allMember() && args(arg,..)") public Object logArgs2(ProceedingJoinPoint joinPoint, Object arg) throws Throwable { log.info("[logArgs2] {}, arg={}", joinPoint.getSignature(), arg); return joinPoint.proceed(); } @Before("allMember() && args(arg,..)") public void logArgs3(String arg) { log.info("[logArgs3] arg={}", arg); } @Before("allMember() && this(obj)") public void thisArgs(JoinPoint joinPoint, MemberService obj) { log.info("[this] {}, obj={}", joinPoint.getSignature(), obj.getClass()); } @Before("allMember() && target(obj)") public void targetArgs(JoinPoint joinPoint, MemberService obj) { log.info("[target] {}, obj={}", joinPoint.getSignature(), obj.getClass()); } @Before("allMember() && @target(annotation)") public void atTarget(JoinPoint joinPoint, ClassAop annotation) { log.info("[@target] {}, annotation={}", joinPoint.getSignature(), annotation); } @Before("allMember() && @within(annotation)") public void atWithin(JoinPoint joinPoint, ClassAop annotation) { log.info("[@within] {}, annotation={}", joinPoint.getSignature(), annotation); } @Before("allMember() && @annotation(annotation)") public void atAnnotation(JoinPoint joinPoint, MethodAop annotation) { log.info("[@annotation] {}, value={}", joinPoint.getSignature(), annotation.value()); } }
| 표현식 | 전달 내용 |
|---|---|
args(arg,..) |
메서드 인수 |
this(obj) |
프록시 객체 |
target(obj) |
실제 타겟 객체 |
@target(annotation) |
클래스 레벨 애노테이션 인스턴스 |
@within(annotation) |
클래스 레벨 애노테이션 인스턴스 |
@annotation(annotation) |
메서드 레벨 애노테이션 인스턴스 |
- 포인트컷의 이름과 어드바이스 메서드의 매개변수 이름은 정확히 일치해야 함
this와 target 지시자의 차이

this는 스프링 컨테이너에 등록되어 있는 프록시 객체를 매칭함target은 프록시가 가리키는 실제 대상 객체를 매칭함
프록시 생성 방식에 따른 차이점

- JDK 동적 프록시는 인터페이스를 구현해서 프록시를 생성하므로 구현 클래스(
MemberServiceImpl) 정보를 알 수 없음 -
CGLIB는 구체 클래스를 상속해서 프록시를 생성하므로 부모인 구체 클래스(
MemberServiceImpl) 타입까지 매칭될 수 있음1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
/** * application.properties * spring.aop.proxy-target-class=true CGLIB * spring.aop.proxy-target-class=false JDK 동적 프록시 */ @Slf4j @Aspect static class ThisTargetAspect { // 부모 타입 허용 @Around("this(hello.aop.member.MemberService)") public Object doThisInterface(ProceedingJoinPoint joinPoint) throws Throwable { log.info("[this-interface] {}", joinPoint.getSignature()); return joinPoint.proceed(); } // 부모 타입 허용 @Around("target(hello.aop.member.MemberService)") public Object doTargetInterface(ProceedingJoinPoint joinPoint) throws Throwable { log.info("[target-interface] {}", joinPoint.getSignature()); return joinPoint.proceed(); } //this: 스프링 AOP 프록시 객체 대상 //JDK 동적 프록시는 인터페이스를 기반으로 생성되므로 구현 클래스를 알 수 없음 //CGLIB 프록시는 구현 클래스를 기반으로 생성되므로 구현 클래스를 알 수 있음 @Around("this(hello.aop.member.MemberServiceImpl)") public Object doThis(ProceedingJoinPoint joinPoint) throws Throwable { log.info("[this-impl] {}", joinPoint.getSignature()); return joinPoint.proceed(); } //target: 실제 target 객체 대상 @Around("target(hello.aop.member.MemberServiceImpl)") public Object doTarget(ProceedingJoinPoint joinPoint) throws Throwable { log.info("[target-impl] {}", joinPoint.getSignature()); return joinPoint.proceed(); } }
| 매칭 표현 지정 타입 | JDK 동적 프록시 | CGLIB |
|---|---|---|
this(MemberService) |
매칭 성공 (인터페이스) | 매칭 성공 (상속 트리) |
this(MemberServiceImpl) |
매칭 방해 (Impl 정보 부재) | 매칭 성공 (Impl 상속) |
target(MemberService) |
매칭 성공 | 매칭 성공 |
target(MemberServiceImpl) |
매칭 성공 | 매칭 성공 |
연습 문제
-
스프링 AOP에서 포인트컷 지시자는 주로 무엇을 정의할까요?
a. 조인 포인트 대상 범위
- 포인트컷 지시자는 어디에 어드바이스를 적용할지 대상을 필터링하여 범위를 정하는 역할을 함
-
execution 포인트컷 지시자는 어떤 시점을 조인 포인트로 매칭할까요?
a. 메서드 실행
- execution은 이름 그대로 메서드가 실행되는 시점을 포착하는 가장 기본적인 포인트컷 지시자임
-
execution 포인트컷 표현식에서 매개변수 패턴 (…)는 무엇을 의미할까요?
a. 0개 이상의 임의 매개변수
- execution에서 ‘…‘은 매개변수의 타입과 개수에 상관없이 0개 이상 어떤 매개변수든 허용한다는 와일드카드임
-
Spring AOP에서 this와 target 포인트컷 지시자의 가장 큰 차이점은 무엇일까요?
a. this는 프록시, target은 실제 객체
- Spring AOP는 프록시 기반이라 this는 스프링 컨테이너에 등록된 프록시 객체를 가리키며 target은 프록시가 호출하는 원본 객체를 뜻함
-
execution 지시자를 사용할 때 지정한 선언 타입으로 인터페이스를 명시하면 어떻게 될까요?
a. 부모 타입에 선언된 메서드를 구현한 자식 타입의 메서드까지 매칭됨
- execution은 선언 타입으로 부모 타입을 지정해도 다형성이 적용되어 해당 부모에 정의된 메서드를 구현한 모든 자식의 메서드까지 매칭함
-
within 포인트컷 지시자의 주요 제약사항은 무엇일까요?
a. 부모 타입이나 인터페이스를 지정할 수 없음
- within은 특정 타입 내부에 있는 조인 포인트를 매칭하지만 부모 타입으로는 매칭할 수 없고 정확한 구체 타입만 지정해야 함
-
args 포인트컷 지시자와 execution 파라미터 매칭 방식의 주요 차이점은 무엇일까요?
a. args는 런타임 객체 기반, execution은 시그니처 기반
- args는 메서드 호출 시 전달되는 실제 객체의 런타임 타입을 보고 매칭하며 반면 execution은 메서드 시그니처의 선언된 타입만 보고 매칭함
-
@annotation 포인트컷 지시자는 무엇을 대상으로 조인 포인트를 매칭할까요?
a. 메서드 애노테이션
- @annotation 지시자는 조인 포인트가 되는 메서드 자체에 특정 애노테이션이 붙어있는 대상을 매칭할 때 사용됨
-
클래스에 특정 어노테이션이 적용되었을 때 @target과 @within의 범위 차이는 무엇일까요?
a. @target은 부모 포함 모든 메서드, @within은 해당 클래스 메서드만
- @target은 애노테이션이 붙은 객체 인스턴스의 상속을 포함한 모든 메서드를 매칭하지만 @within은 해당 애노테이션이 붙은 타입 정의 내부의 메서드만 매칭함
-
bean 포인트컷 지시자는 어떤 환경을 대상으로 사용될까요?
a. 스프링 빈 이름 단위로 적용 시 사용됨
- bean 지시자는 AspectJ 표준에는 없고 스프링 컨테이너에 등록된 빈의 이름을 기준으로 조인 포인트를 지정하기 위한 Spring AOP 전용 기술임
요약 정리
- execution은 스프링 AOP에서 가장 많이 쓰이는 포인트컷 지시자로 메서드 시그니처를 정교하게 묘사하여 조인 포인트를 찾기 위해 사용됨
- within, args, @annotation 등은 execution의 특정 부분을 대체하거나 보완하는 용도로 조인 포인트를 매칭함
- 매개변수 전달과 파라미터 바인딩을 통해 args, this, target 및 특정 애노테이션 객체 등을 어드바이스 메서드 안으로 가져와서 값을 자유롭게 쓸 수 있음
@target이나args등 단독으로 사용하면 스프링의 모든 컨테이너 빈에 프록시가 적용되어 심각한 오류가 날 수 있는 지시자들은 반드시execution등을 통해 대상의 범위를 좁힌 뒤 함께 사용해야 함