[Spring AOP] 스프링 AOP - 포인트컷

2025. 7. 6. 17:01·Spring/AOP

스프링 AOP - 포인트컷

애스펙트J는 포인트컷을 편리하게 표현하기 위한 특별한 표현식을 제공한다.

포인트컷 지시자

포인트컷 표현식은 execution 같은 포인트컷 지시자(Pointcut Designator)로 시작한다. 줄여서 PCD라 한다.

  • 포인트컷 지시자의 종류
    • execution: 메소드 실행 조인 포인트를 매칭한다. 스프링 AOP에서 가장 많이 사용하고, 기능도 복잡하다.
    • within: 특정 타입 내의 조인 포인트를 매칭한다.
    • args: 인자가 주어진 타입의 인스턴스인 조인 포인트
    • this: 스프링 빈 객체(스프링 AOP 프록시)를 대상으로 하는 조인 포인트
    • target: Target 객체(스프링 AOP 프록시가 가리키는 실제 대상)를 대상으로 하는 조인 포인트
    • @target: 실행 객체의 클래스에 주어진 타입의 애노테이션이 있는 조인 포인트
    • @within: 주어진 애노테이션이 있는 타입 내 조인 포인트
    • @annotation: 메서드가 주어진 애노테이션을 가지고 있는 조인 포인트를 매칭
    • @args: 전달된 실제 인수의 런타임 타입이 주어진 타입의 애노테이션을 갖는 조인 포인트
    • bean: 스프링 전용 포인트컷 지시자, 빈의 이름으로 포인트컷을 지정한다.

예제 만들기

메서드에 붙일 수 있는 애노테이션, 클래스에 붙일 수 있는 애노테이션을 만든다.

ClassAop

package hello.aop.member.annotation;

import java.lang.annotation.*;

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface ClassAop {
}

MethodAop

package hello.aop.member.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MethodAop {
    String value();
}

MemberServiceImpl

package hello.aop.member;

import hello.aop.member.annotation.ClassAop;
import hello.aop.member.annotation.MethodAop;
import org.springframework.stereotype.Component;

@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";
    }
}

execution1

execution 으로 시작하는 포인트컷 표현식은 메서드 메타 정보를 매칭해서 포인트컷 대상을 찾아낸다.

execution 문법

execution(modifiers-pattern? ret-type-pattern declaring-type-pattern?name-pattern(param-pattern) throws-pattern?)
execution(접근제어자? 반환타입 선언타입?메서드이름(파라미터) 예외?)
  • 메소드 실행 조인 포인트를 매칭한다.
  • ?는 생략할 수 있다.
  • * 같은 패턴을 지정할 수 있다.
  • * 은 아무 값이 들어와도 된다는 뜻이다.
  • 파라미터에서 ..은 파라미터의 타입과 파라미터 수가 상관없다는 뜻이다.

public java.lang.String hello.aop.member.MemberServiceImpl.hello(java.lang.String)

  • 가장 정확한 포인트 컷
    • "execution(public String hello.aop.member.MemberServiceImpl.hello(String))"
  • 가장 많이 생략한 포인트컷
    • "execution(* *(..))"
    • 접근제어자, 선언타입을 생략/ 반환타입, 메서드 이름: * / 예외: 없음 / 파라미터: (..)

패키지에서 . , .. 의 차이를 이해해야 한다.

  • .: 정확하게 해당 위치의 패키지
  • ..: 해당 위치의 패키지와 그 하위 패키지도 포함

execution2

타입 매칭 - 부모 타입 허용
public java.lang.String hello.aop.member.MemberServiceImpl.hello(java.lang.String)

  • "execution(* hello.aop.member.MemberServiceImpl.*(..))"
  • “execution(* hello.aop.member.MemberService.*(..))”
  • execution 에서는 MemberService처럼 부모 타입을 선언해도 그 자식 타입은 매칭된다. 다형성에서 부모타입 = 자식타입 이 할당 가능하다는 점을 떠올려보면 된다.

타입 매칭 - 부모 타입에 있는 메서드만 허용
public java.lang.String hello.aop.member.MemberServiceImpl.internal(java.lang.String)
internal() 메서드는 인터페이스에 정의되지 않은, 구체클래스 에만 존재하는 메서드이다.

  • "execution(* hello.aop.member.MemberServiceImpl.*(..))": 매칭 가능
  • "execution(* hello.aop.member.MemberService.*(..))": 매칭 실패

execution 파라미터 매칭 규칙은 다음과 같다.

  • (String): 정확하게 String 타입 파라미터
  • (): 파라미터가 없어야 한다.
  • (*): 정확히 하나의 파라미터, 단 모든 타입을 허용한다.
  • (*, *): 정확히 두 개의 파라미터, 단 모든 타입을 허용한다.
  • (..): 숫자와 무관하게 모든 파라미터, 모든 타입을 허용한다. 참고로 파라미터가 없어도 된다. 0..* 로 이해하면 된다.
  • (String, ..): String 타입으로 시작해야 한다. 숫자와 무관하게 모든 파라미터, 모든 타입을 허용한다.
    • 예) (String), (String, Xxx), (String, Xxx, Xxx) 허용

within

within 지시자는 특정 타입 내의 조인 포인트들로 매칭을 제한한다. 쉽게 이야기해서 해당 타입이 매칭되면 그 안의 메서드(조인 포인트)들이 자동으로 매칭된다.

public class WithinTest {
    AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
    Method helloMethod;

    @BeforeEach
    public void init() throws NoSuchMethodException {
        helloMethod = MemberServiceImpl.class.getMethod("hello", String.class);
    }

    @Test
    void withinExact() {
        pointcut.setExpression("within(hello.aop.member.MemberServiceImpl)");
        assertThat(pointcut.matches(helloMethod, MemberServiceImpl.class)).isTrue();
    }

    @Test
    void withinStar() {
        pointcut.setExpression("within(hello.aop.member.*Service*)");
        assertThat(pointcut.matches(helloMethod, MemberServiceImpl.class)).isTrue();
    }

    @Test
    void withinSubPackage() {
        pointcut.setExpression("within(hello.aop..*)");
        assertThat(pointcut.matches(helloMethod, MemberServiceImpl.class)).isTrue();
    }
}

within 사용시 주의해야 할 점이 있다. 표현식에 부모 타입을 지정하면 안된다는 점이다. 정확하게 타입이 맞아야 한다. 이 부분에서 execution 과 차이가 난다.

"within(hello.aop.member.MemberService)"는 매칭 실패한다.
(사실 within은 거의 사용하지 않는다. execution으로 대부분 기능이 가능하고, 인터페이스로 지정해줄 일이 생기기 때문에 사용할 일이 많지 않다.)

args

  • args: 인자가 주어진 타입의 인스턴스인 조인 포인트로 매칭
  • 기본 문법은 execution의 args 부분과 같다.

execution과 args의 차이점

  • execution 은 파라미터 타입이 정확하게 매칭되어야 한다. execution은 클래스에 선언된 정보를 기반으로 판단한다.
  • args 는 부모 타입을 허용한다. args 는 실제 넘어온 파라미터 객체 인스턴스를 보고 판단한다.

파라미터에서 부모를 허용하는지(런타임 인스턴스로 판단), 안하는지(정적 타임, 시그니처로 판단)에 차이가 있다고 보면 된다.
memberService.hello(String)을 예로 들면,

  • 정적으로 클래스에 선언된 정보만 보고 판단하는 execution(* *(Object))는 매칭에 실패한다.
  • 동적으로 실제 파라미터로 넘어온 객체 인스턴스로 판단하는 args(Object)는 매칭에 성공한다. (부모 타입 허용)

args 지시자는 단독으로 사용되기 보다는 뒤에서 설명할 파라미터 바인딩에서 주로 사용된다.

@target, @within

  • @target: 실행 객체의 클래스에 주어진 타입의 애노테이션이 있는 조인 포인트
    • @target 은 인스턴스의 모든 메서드를 조인 포인트로 적용한다.
  • @within: 주어진 애노테이션이 있는 타입 내 조인 포인트
    • @within은 해당 타입 내에 있는 메서드만 조인 포인트로 적용한다.

@target, @within 지시자는 뒤에서 설명할 파라미터 바인딩에서 함께 사용된다.

주의

다음 포인트컷 지시자는 단독으로 사용하면 안된다. args, @args, @target

이번 예제를 보면 execution(* hello.aop..*(..))를 통해 적용 대상을 줄여준 것을 확인할 수 있다.

args, @args, @target 은 실제 객체 인스턴스가 생성되고 실행될 때 어드바이스 적용 여부를 확인할 수 있다.

결국에, 프록시가 있어야 실행 시점에 판단이 가능한건데, args, @args, @target를 단독으로 사용하면 모든 스프링 빈에 AOP를 적용하려고 해서 오류가 발생해버린다.

@annotation, @args

@annotation 정의

  • @annotation: 메서드가 주어진 애노테이션을 가지고 있는 조인 포인트를 매칭

아까 예제를 만들 때, MemberService.hello() 메서드에 @MethodAop("test value")를 달아뒀다.
@Around("@annotation(hello.aop.member.annotation.MethodAop)") 이런식으로 매칭할 수 있다.

@args 정의

  • @args: 전달된 실제 인수의 런타임 타입이 주어진 타입의 애노테이션을 갖는 조인 포인트
  • ex) 전달된 인수의 런타임 타입에 @Check 애노테이션이 있는 경우에 매칭한다.

근데 이건 거의 사용할 일이 없다고 한다.

bean

정의
bean: 스프링 전용 포인트컷 지시자, 빈의 이름으로 지정한다.

설명

  • 스프링 빈의 이름으로 AOP 적용 여부를 지정한다.
  • bean(orderService) || bean(*Repository)
  • * 과 같은 패턴을 사용할 수 있다.

매개변수 전달

다음은 포인트컷 표현식을 사용해서 어드바이스에 매개변수를 전달할 수 있다.
this, target, args, @target, @within, @annotation, @args

매개변수를 전달할 수 있는 방법은 매우 많다.

@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]{}, obj={}", joinPoint.getSignature(), annotation);
}

@Before("allMember() && @within(annotation)")
public void atWithin(JoinPoint joinPoint, ClassAop annotation) {
    log.info("[@within]{}, obj={}", joinPoint.getSignature(), annotation);
}

@Before("allMember() && @annotation(annotation)")
public void atAnnotation(JoinPoint joinPoint, MethodAop annotation) {
    log.info("[@annotation]{}, annotationValue={}",
            joinPoint.getSignature(), annotation.value());
}
  • logArgs1: joinPoint.getArgs()[0]와 같이 매개변수를 전달 받는다.
  • logArgs2: args(arg,..)와 같이 매개변수를 전달 받는다.
  • logArgs3: @Before를 사용한 축약 버전이다. 추가로 타입을 String 으로 제한했다.
  • this: 프록시 객체를 전달 받는다.
  • target: 실제 대상 객체를 전달 받는다.
  • @target, @within: 타입의 애노테이션을 전달 받는다.
  • @annotation: 메서드의 애노테이션을 전달 받는다. 여기서는 annotation.value()로 해당 애노테이션의 값을 출력하는 모습을 확인할 수 있다.

this, target

정의

  • this: 스프링 빈 객체(스프링 AOP 프록시)를 대상으로 하는 조인 포인트
  • target: Target 객체(스프링 AOP 프록시가 가리키는 실제 대상)를 대상으로 하는 조인 포인트

설명

  • this, target 은 다음과 같이 적용 타입 하나를 정확하게 지정해야 한다.
    • * 같은 패턴을 사용할 수 없다.
    • 부모 타입은 허용한다.
  • this(hello.aop.member.MemberService), target(hello.aop.member.MemberService)

프록시를 조인 포인트로 지정하는 것과 실제 target을 조인 포인트로 지정하는 것은 어떤 차이가 있을까?

프록시 생성 방식에 따른 차이
스프링은 프록시를 생성할 때 JDK 동적 프록시와 CGLIB를 선택할 수 있다. 둘의 프록시를 생성하는 방식이 다르기 때문에 차이가 발생한다.

먼저 JDK 동적 프록시를 적용했을 때 this, target 을 알아보자.

MemberService 인터페이스 지정

  • this(hello.aop.member.MemberService)
    • proxy 객체를 보고 판단한다. this는 부모 타입을 허용하기 때문에 AOP가 적용된다.
  • target(hello.aop.member.MemberService)
    • target 객체를 보고 판단한다. target 은 부모 타입을 허용하기 때문에 AOP가 적용된다.

MemberServiceImpl 구체 클래스 지정

  • this(hello.aop.member.MemberServiceImpl): proxy 객체를 보고 판단한다. JDK 동적 프록시로 만들어진 proxy 객체는 MemberService 인터페이스를 기반으로 구현된 새로운 클래스다. 따라서 MemberServiceImpl를 전혀 알지 못하므로 AOP 적용 대상이 아니다.
  • target(hello.aop.member.MemberServiceImpl): target 객체를 보고 판단한다. target 객체가 MemberServiceImpl 타입이므로 AOP 적용 대상이다.

CGLIB 프록시라면?(스프링 AOP 디폴트)

MemberService 인터페이스 지정

  • this(hello.aop.member.MemberService): proxy 객체를 보고 판단한다. this는 부모 타입을 허용하기 때문에 AOP가 적용된다.
  • target(hello.aop.member.MemberService): target 객체를 보고 판단한다. target 은 부모 타입을 허용하기 때문에 AOP가 적용된다.

MemberServiceImpl 구체 클래스 지정

  • this(hello.aop.member.MemberServiceImpl): proxy 객체를 보고 판단한다. CGLIB로 만들어진 proxy 객체는 MemberServiceImpl를 상속 받아서 만들었기 때문에 AOP가 적용된다. this가 부모 타입을 허용하기 때문에 포인트컷의 대상이 된다.
  • target(hello.aop.member.MemberServiceImpl): target 객체를 보고 판단한다. target 객체가 MemberServiceImpl 타입이므로 AOP 적용 대상이다.

정리하자면, 프록시를 대상으로 하는 this 의 경우 구체 클래스를 지정하면 프록시 생성 전략에 따라서 다른 결과가 나올 수 있다는 점을 알아두자.

Ref) 스프링 핵심 원리 - 고급편 강의 | 김영한 - 인프런

'Spring > AOP' 카테고리의 다른 글

[Spring AOP] 스프링 AOP 적용 예시, 실무 주의 사항  (0) 2025.07.06
[Spring AOP] 스프링 AOP 구현  (0) 2025.07.06
[Spring AOP] 스프링 AOP 개념  (0) 2025.07.06
[Spring AOP] @Aspect AOP  (0) 2025.07.06
[Spring AOP] 빈 후처리기  (0) 2025.07.06
'Spring/AOP' 카테고리의 다른 글
  • [Spring AOP] 스프링 AOP 적용 예시, 실무 주의 사항
  • [Spring AOP] 스프링 AOP 구현
  • [Spring AOP] 스프링 AOP 개념
  • [Spring AOP] @Aspect AOP
lumana
lumana
배움을 나누는 공간 https://github.com/bebeis
  • lumana
    Brute force Study
    lumana
  • 전체
    오늘
    어제
    • 분류 전체보기 (456)
      • Software Development (27)
        • Performance (0)
        • TroubleShooting (1)
        • Refactoring (0)
        • Test (8)
        • Code Style, Convetion (0)
        • DDD (0)
        • Software Engineering (18)
      • Java (71)
        • Basic (5)
        • Core (21)
        • Collection (7)
        • 멀티스레드&동시성 (13)
        • IO, Network (8)
        • Reflection, Annotation (3)
        • Modern Java(8~) (12)
        • JVM (2)
      • Spring (53)
        • Framework (12)
        • MVC (23)
        • Transaction (3)
        • AOP (11)
        • Boot (0)
        • AI (0)
      • DB Access (1)
        • Jdbc (1)
        • JdbcTemplate (0)
        • JPA (14)
        • Spring Data JPA (0)
        • QueryDSL (0)
      • Computer Science (129)
        • Data Structure (27)
        • OS (14)
        • Database (10)
        • Network (21)
        • 컴퓨터구조 (5)
        • 시스템 프로그래밍 (23)
        • Algorithm (29)
      • HTTP (8)
      • Infra (1)
        • Docker (1)
      • 프로그래밍언어론 (15)
      • Programming Language(Sub) (77)
        • Kotlin (1)
        • Python (25)
        • C++ (51)
        • JavaScript (0)
      • FE (11)
        • HTML (1)
        • CSS (9)
        • React (0)
        • Application (1)
      • Unix_Linux (0)
        • Common (0)
      • PS (13)
        • BOJ (7)
        • Tip (3)
        • 프로그래머스 (0)
        • CodeForce (0)
      • Book Review (4)
        • Clean Code (4)
      • Math (3)
        • Linear Algebra (3)
      • AI (7)
        • DL (0)
        • ML (0)
        • DA (0)
        • Concepts (7)
      • 프리코스 (4)
      • Project Review (6)
      • LegacyPosts (11)
      • 모니터 (0)
      • Diary (0)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

  • 인기 글

  • 태그

  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.4
lumana
[Spring AOP] 스프링 AOP - 포인트컷
상단으로

티스토리툴바