포인트컷 지시자
- AspectJ는 포인트컷을 편리하게 표현하기 위한 특별한 표현식을 제공한다.
- 포인트컷 표현식은 execution같은 포인트컷 지시자(PCD, Pointcut Designator)로 시작한다.
- 종류
- execution
- 메소드 실행 조인 포인트를 매칭한다.
- 스프링 AOP에서 가장 많이 사용한다.
- 기능이 복잡하다.
- within
- args
- this
- 스프링 빈 객체(스프링 AOP 프록시)를 대상으로 하는 조인 포인트
- target
- Target 객체(스프링 AOP 프록시가 가리키는 실제 대상)를 대상으로 하는 조인 포인트
- @target
- 실행 객체의 클래스에 주어진 타입의 애노테이션이 있는 조인 포인트
- @within
- 주어진 애노테이션이 있는 타입 내 조인 포인트
- @annotation
- 메서드가 주어진 애노테이션을 가지고 있는 조인 포인트를 매칭
- @args
- 전달된 실제 인수의 런타임 타입이 주어진 타입의 애노테이션을 갖는 조인 포인트
- bean
- 스프링 전용 포인트컷 지시자, 빈의 이름으로 포인트컷을 지정한다.
예제 만들기
어노테이션
서비스
테스트 생성
- AspectJExpressionPointcut이 바로 포인트컷 표현식을 처리해주는 클래스다.
- 여기에 포인트컷 표현식을 지정하면 된다.
- AspectJExpressionPointcut는 상위에 Pointcut 인터페이스를 가진다.
execution 1
- 문법
execution(modifiers-pattern? ret-type-pattern declaring-type-pattern?name-pattern(param-pattern)throws-pattern?)
execution(접근제어자? 반환타입 선언타입?메서드이름(파라미터) 예외?)
- 설명
- 메소드 실행 조인 포인트를 매칭한다.
- ?는 생략할 수 있다.
- * 같은 패턴을 지정할 수 있다.
가장 정확한 포인트컷
- ExecutionTest에 테스트를 추가해보자.
- AspectJExpressionPointcut에 pointcut.setExpression을 통해서 포인트컷 표현식을 적용할 수 있다.
- pointcut.matches(메서드, 대상 클래스)를 실행하면 지정한 포인트컷 표현식의 매칭 여부를 true/false로 반환한다.
- 매칭 조건
- 접근제어자?
- 반환타입
- 선언타입
- com.example.app.MemberServiceImpl
- 메서드이름
- 파라미터
- 예외?
가장 많이 생략한 포인트컷
- ExecutionTest에 테스트를 추가해보자.
- 매칭 조건
- 접근제어자?
- 반환타입
- 선언타입?
- 메서드이름
- 파라미터
- 예외?
- 은 아무 값이 들어와도 된다는 것을 의미한다.
- 파라미터에서
..
은 파라미터의 타입과 파라미터 수가 상관없다는 것을 의미한다.
메서드 이름 매칭 관련 포인트컷
- ExecutionTest에 테스트를 추가해보자.
- 메서드 이름 앞 뒤에 * 을 사용해서 매칭할 수 있다. (LIKE 연산)
패키지 매칭 관련 포인트컷
- ExecutionTest에 테스트를 추가해보자.
com.example.app.*(1).*(2)
- 패키지에서
.
와 ..
의 차이를 이해해야 한다.
execution 2
타입 매칭 - 부모 타입 허용
- ExecutionTest에 테스트를 추가해보자.
- typeExactMatch()는 타입 정보가 정확하게 일치하기 때문에 매칭된다.
- typeMatchSuperType() 을 주의해서 보아야 한다.
- execution에서는 MemberService처럼 부모 타입을 선언해도 그 자식 타입은 매칭된다.
- 다형성에서 부모타입 = 자식타입 이 할당 가능하다는 점을 떠올려보면 된다.
타입 매칭 - 부모 타입에 있는 메서드만 허용
- ExecutionTest에 테스트를 추가해보자.
- typeMatchInternal()의 경우 MemberServiceImpl를 표현식에 선언했기 때문에 그 안에 있는 internal(String) 메서드도 매칭 대상이 된다.
- typeMatchNoSuperTypeMethodFalse() 를 주의해서 보아야 한다.
- 이 경우 표현식에 부모 타입인 MemberService 를 선언했다.
- 그런데 자식 타입인 MemberServiceImpl 의
- internal(String) 메서드를 매칭하려 한다.
- 이 경우 매칭에 실패한다.
- MemberService 에는 internal(String) 메서드가 없기 때문이다. 부모 타입을 표현식에 선언한 경우 부모 타입에서 선언한 메서드가 자식 타입에 있어야 매칭에 성공한다.
- 그래서 부모 타입에 있는 hello(String) 메서드는 매칭에 성공한다.
- 부모 타입에 없는 internal(String) 는 매칭에 실패한다.
파라미터 매칭
- ExecutionTest에 테스트를 추가해보자.
- execution 파라미터 매칭 규칙은 다음과 같다.
(String)
()
(*)
- 정확히 하나의 파라미터
- 단, 모든 타입을 허용한다.
(*, *)
- 정확히 두 개의 파라미터
- 단, 모든 타입을 허용한다.
(..)
- 숫자와 무관하게 모든 파라미터
- 단, 모든 타입을 허용한다.
- 참고로 파라미터가 없어도 된다.
0..*
로 이해하면 된다.
(String, ..)
- String 타입으로 시작해야 한다.
- 숫자와 무관하게 모든 파라미터, 모든 타입을 허용한다.
- 예시
(String)
(String, Xxx)
(String, Xxx, Xxx)
within
- within 지시자는 특정 타입 내의 조인 포인트들로 매칭을 제한한다.
- 해당 타입이 매칭되면 그 안의 메소드(조인 포인트)들이 자동으로 매칭된다.
- execution에서 타입 부분만 사용한다고 보면 된다
테스트 생성
주의사항
- 표현식에 부모 타입을 지정하면 안 된다.
- 정확하게 타입이 맞아야 한다.
- 이 부분이 execution과의 차이점이다.
- 부모 타입(여기서는 MemberService 인터페이스) 지정시 within 은 실패하고,
execution 은 성공하는 것을 확인할 수 있다.
args
args
- 인자가 주어진 타입의 인스턴스인 조인 포인트로 매칭
- 기본 문법은 execution의 args 부분과 같다.
- execution과 args의 차이점
- execution
- 파라미터 타입이 정확하게 매칭되어야 한다.
- 클래스에 선언된 정보를 기반으로 판단한다.
- args는 부모 타입을 허용한다.
- args는 실제 넘어온 파라미터 객체 인스턴스를 보고 판단한다.
테스트 생성
- pointcut()
- AspectJExpressionPointcut에 포인트컷은 한번만 지정할 수 있다.
- 테스트를 편리하게 진행하기 위해 포인트컷을 여러번 지정하기 위해 포인트컷 자체를 생성하는 메소드
- 자바가 기본으로 제공하는 String은 Object와 java.io.Serializable의 하위 타입이다.
- 정적으로 클래스에 선언된 정보만 보고 판단하는 execution(* *(Object))는 매칭에 실패한다.
- 동적으로 실제 파라미터로 넘어온 객체 인스턴스로 판단하는 args(Object)는 매칭에 성공한다.
- args 지시자는 단독으로 사용되기 보다는 파라미터 바인딩에서 주로 사용된다.
@target, @within
- 정의
- @target
- 실행 객체의 클래스에 주어진 타입의 애노테이션이 있는 조인 포인트
- at target이라고 읽는다.
- @within
- 주어진 애노테이션이 있는 타입 내 조인 포인트
- at within이라고 읽는다.
- 설명
- @target과 @within은 다음과 같이 타입에 있는 애노테이션으로 AOP 적용 여부를 판단한다.
@target(com.example.annotation.ClassAop)
@within(com.example.annotation.ClassAop)
- @target vs @within
- @target
- 인스턴스의 모든 메서드를 조인 포인트로 적용한다.
- 부모 클래스의 메서드까지 어드바이스를 다 적용한다.
- @within
- 해당 타입 내에 있는 메서드만 조인 포인트로 적용한다.
- 자기 자신의 클래스에 정의된 메서드에만 어드바이스를 적용한다.
테스트 생성
- parentMethod()는 Parent 클래스에만 정의되어 있고,
Child 클래스에 정의되어 있지 않기 때문에 @within에서 AOP 적용 대상이 되지 않는다. - 실행결과를 보면 child.parentMethod()를 호출 했을 때 @within이 호출되지 않은 것을 확인할 수 있다.
주의사항
- 다음 포인트컷 지시자는 단독으로 사용하면 안된다.
- 이번 테스트의 코드를 보면
execution(* com.example..*(..))
를 통해 적용 대상을 줄여준 것을 확인할 수 있다. args
, @args
, @target
은 실제 객체 인스턴스가 생성되고 실행될 때
어드바이스 적용 여부를 확인할 수 있다.- 실행 시점에 일어나는 포인트컷 적용 여부도 결국 프록시가 있어야 실행 시점에 판단할 수 있다.
- 프록시가 없다면 판단 자체가 불가능하다.
- 그런데 스프링 컨테이너가 프록시를 생성하는 시점은 스프링 컨테이너가 만들어지는 애플리케이션 로딩 시점에 적용할 수 있다.
- 따라서
args
, @args
, @target
같은 포인트컷 지시자가 있으면
스프링은 모든 스프링 빈에 AOP를 적용하려고 시도한다. - 프록시가 없으면 실행 시점에 판단 자체가 불가능하다.
- 이렇게 모든 스프링 빈에 AOP 프록시를 적용하려고 하면
스프링이 내부에서 사용하는 빈 중에는 final로 지정된 빈들도 있기 때문에 오류가 발생할 수 있다. - 따라서 이러한 표현식은 최대한 프록시 적용 대상을 축소하는 표현식과 함께 사용해야 한다.
@annotation, @args
@annotation
- 메소드가 주어진 애노테이션을 가지고 있는 조인 포인트를 매칭한다.
테스트 생성
@args
- 전달된 실제 인수의 런타임 타입이 주어진 타입의 애노테이션을 갖는 조인 포인트
- 전달된 인수의 런타임 타입에 @Check 애노테이션이 있는 경우에 매칭한다.
bean
- 스프링 전용 포인트컷 지시자
- 빈의 이름으로 지정한다.
- 스프링 빈의 이름으로 AOP 적용 여부를 지정한다.
- * 과 같은 패턴을 사용할 수 있다.
- 예시
bean(orderService)
bean(*Repository)
테스트 생성
매개변수 전달
- 포인트컷 표현식을 사용해서 어드바이스에 매개변수를 전달할 수 있다.
this
target
args
@target
@within
@annotation
@args
- 포인트컷의 이름과 매개변수의 이름을 맞추어야 한다.
- 타입이 메서드에 지정한 타입으로 제한된다.
테스트 생성
this, target
- 정의
- this
- 스프링 빈 객체(스프링 AOP 프록시)를 대상으로 하는 조인 포인트
- target
- Target 객체(스프링 AOP 프록시가 가리키는 실제 대상)를 대상으로 하는 조인 포인트
- 설명
- this , target 은 다음과 같이 적용 타입 하나를 정확하게 지정해야 한다
- * 같은 패턴을 사용할 수 없다.
- 부모 타입을 허용한다.
- this vs target
- 스프링에서 AOP를 적용하면 실제 target 객체 대신에 프록시 객체가 스프링 빈으로 등록된다.
- this는 스프링 빈으로 등록되어 있는 프록시 객체를 대상으로 포인트컷을 매칭한다.
- target은 실제 target 객체를 대상으로 포인트컷을 매칭한다.
- 프록시 생성 방식에 따른 차이
- 스프링은 프록시를 생성할 때 JDK 동적 프록시와 CGLIB를 선택할 수 있다.
- 둘의 프록시를 생성하는 방식이 다르기 때문에 차이가 발생한다.
- JDK 동적 프록시
- 인터페이스 필수
- 인터페이스를 구현한 프록시 객체를 생성한다.
- CGLIB
- 인터페이스가 있어도 구체 클래스를 상속 받아서 프록시 객체를 생성한다.
- 프록시를 대상으로 하는 this 의 경우 구체 클래스를 지정하면
프록시 생성 전략에 따라서 다른 결과가 나올 수 있다. - this , target 지시자는 단독으로 사용되기 보다는 파라미터 바인딩에서 주로 사용된다.
JDK 동적 프록시 적용
- MemberService 인터페이스 지정
this(com.example.app.MemberService)
- proxy 객체를 보고 판단한다.
- this는 부모 타입을 허용하기 때문에 AOP가 적용된다.
target(com.example.app.MemberService)
- target 객체를 보고 판단한다.
- target 은 부모 타입을 허용하기 때문에 AOP가 적용된다.
- MemberServiceImpl 구체 클래스 지정
this(com.example.app.MemberServiceImpl)
- proxy 객체를 보고 판단한다.
- JDK 동적 프록시로 만들어진 proxy 객체는 MemberService 인터페이스를 기반으로 구현된 새로운 클래스다.
- 그래서 MemberServiceImpl를 전혀 알지 못하므로 AOP 적용 대상이 아니다.
target(com.example.app.MemberServiceImpl)
- target 객체를 보고 판단한다.
- target 객체가 MemberServiceImpl 타입이므로 AOP 적용 대상이다.
CGLIB 적용
- MemberService 인터페이스 지정
this(com.example.app.MemberService)
- proxy 객체를 보고 판단한다.
- this 는 부모 타입을 허용하기 때문에 AOP가 적용된다.
target(com.example.app.MemberService)
- target 객체를 보고 판단한다.
- target은 부모 타입을 허용하기 때문에 AOP가 적용된다.
- MemberServiceImpl 구체 클래스 지정
this(com.example.app.MemberServiceImpl)
- proxy 객체를 보고 판단한다.
- CGLIB로 만들어진 proxy 객체는 MemberServiceImpl를 상속 받아서 만들었기 때문에 AOP가 적용된다.
- this가 부모 타입을 허용하기 때문에 포인트컷의 대상이 된다.
target(com.example.app.MemberServiceImpl)
- target 객체를 보고 판단한다.
- target 객체가 MemberServiceImpl 타입이므로 AOP 적용 대상이다.
테스트 생성
출처