[스프링 핵심 원리 - 고급편] #12. 스프링 AOP - 포인트컷 #758
Develop-KIM
started this conversation in
동환
Replies: 0 comments
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
포인트컷 지시자
애스펙트J는 포인트컷을 편리하게 표현하기 위한 특별한 표현식을 제공한다.
예)
@Pointcut("execution(* hello.aop.order..*(..))")포인트컷 표현식은 AspectJ pointcut expression 즉 애스펙트J가 제공하는 포인트컷 표현식을 줄여서 말하는 것이다.
포인트컷 지시자
포인트컷 표현식은
execution같은 포인트컷 지시자(Pointcut Designator)로 시작한다. 줄여서 PCD라 한다.execution: 메소드 실행 조인 포인트를 매칭한다. 스프링 AOP에서 가장 많이 사용하고, 기능도 복잡하다.within: 특정 타입 내의 조인 포인트를 매칭한다.args: 인자가 주어진 타입의 인스턴스인 조인 포인트this: 스프링 빈 객체(스프링 AOP 프록시)를 대상으로 하는 조인 포인트target: Target 객체(스프링 AOP 프록시가 가리키는 실제 대상)를 대상으로 하는 조인 포인트@target: 실행 객체의 클래스에 주어진 타입의 애노테이션이 있는 조인 포인트@within: 주어진 애노테이션이 있는 타입 내 조인 포인트@annotation: 메서드가 주어진 애노테이션을 가지고 있는 조인 포인트를 매칭@args: 전달된 실제 인수의 런타임 타입이 주어진 타입의 애노테이션을 갖는 조인 포인트bean: 스프링 전용 포인트컷 지시자, 빈의 이름으로 포인트컷을 지정한다.예제 만들기
ClassAop
MethodAop
MemberService
MemberServiceImpl
ExecutionTest
spectJExpressionPointcut
이 바로 포인트컷 표현식을 처리해주는 클래스다. 여기에 포인트컷 표현식을 지정하면 된다.AspectJExpressionPointcut는 상위에Pointcut` 인터페이스를 가진다.printMethod()테스트는MemberServiceImpl.hello(String)메서드의 정보를 출력해준다.실행 결과
이번에 알아볼
execution으로 시작하는 포인트컷 표현식은 이 메서드 정보를 매칭해서 포인트컷 대상을 찾아낸다.execution1
execution 문법
?는 생략할 수 있다.*같은 패턴을 지정할 수 있다.가장 정확한 포인트컷**
먼저
MemberServiceImpl.hello(String)메서드와 가장 정확하게 모든 내용이 매칭되는 표현식이다.ExecutionTest - 추가
AspectJExpressionPointcut에pointcut.setExpression을 통해서 포인트컷 표현식을 적용할 수 있다.pointcut.matches(메서드, 대상 클래스)를 실행하면 지정한 포인트컷 표현식의 매칭 여부를true,false로 반환한다.매칭 조건
publicStringhello.aop.member.MemberServiceImplhello(String)MemberServiceImpl.hello(String)메서드와 포인트컷 표현식의 모든 내용이 정확하게 일치한다. 따라서true를 반환한다.가장 많이 생략한 포인트컷
가장 많이 생략한 포인트컷이다.
매칭 조건
**(..)*은 아무 값이 들어와도 된다는 뜻이다...은 파라미터의 타입과 파라미터 수가 상관없다는 뜻이다.메서드 이름 매칭 관련 포인트컷
메서드 이름 앞 뒤에
*을 사용해서 매칭할 수 있다.패키지 매칭 관련 포인트컷
hello.aop.member.*(1).*(2)패키지에서
.,..의 차이를 이해해야 한다..: 정확하게 해당 위치의 패키지..: 해당 위치의 패키지와 그 하위 패키지도 포함execution2
타입 매칭 - 부모 타입 허용
typeExactMatch()는 타입 정보가 정확하게 일치하기 때문에 매칭된다.typeMatchSuperType()을 주의해서 보아야 한다.execution에서는MemberService처럼 부모 타입을 선언해도 그 자식 타입은 매칭된다.다형성에서
부모타입 = 자식타입이 할당 가능하다는 점을 떠올려보면 된다.타입 매칭 - 부모 타입에 있는 메서드만 허용
typeMatchInternal()의 경우MemberServiceImpl를 표현식에 선언했기 때문에 그 안에 있는internal(String)메서드도 매칭 대상이 된다.typeMatchNoSuperTypeMethodFalse()를 주의해서 보아야 한다.이 경우 표현식에 부모 타입인
MemberService를 선언했다.그런데 자식 타입인
MemberServiceImpl의internal(String)메서드를 매칭하려 한다. 이 경우 매칭에 실패한다.MemberService에는internal(String)메서드가 없다.부모 타입을 표현식에 선언한 경우 부모 타입에서 선언한 메서드가 자식 타입에 있어야 매칭에 성공한다.
그래서 부모타입에 있는
hello(String)메서드는 매칭에 성공하지만, 부모 타입에 없는internal(String)는 매칭에 실패한다.파라미터 매칭
execution 파라미터 매칭 규칙은 다음과 같다.
(String): 정확하게 String 타입 파라미터(): 파라미터가 없어야 한다.(*): 정확히 하나의 파라미터, 단 모든 타입을 허용한다.(*, *): 정확히 두 개의 파라미터, 단 모든 타입을 허용한다.(..): 숫자와 무관하게 모든 파라미터, 모든 타입을 허용한다. 참고로 파라미터가 없어도 된다.0..*로 이해하면 된다.(String, ..): String 타입으로 시작해야 한다. 숫자와 무관하게 모든 파라미터, 모든 타입을 허용한다.(String),(String, Xxx),(String, Xxx, Xxx)허용within
within
지시자는 특정 타입 내의 조인 포인트들로 매칭을 제한한다. 해당 타입이 매칭되면 그 안의 메서드(조인 포인트)들이 자동으로 매칭된다. 문법은 단순한데execution` 에서 타입 부분만 사용한다고 보면 된다.WithinTest
주의
그런데
within사용시 주의해야 할 점이 있다. 표현식에 부모 타입을 지정하면 안된다는 점이다. 정확하게 타입이 맞아야 한다.이 부분에서
execution과 차이가 난다.부모 타입(여기서는
MemberService인터페이스) 지정시within은 실패하고,execution은 성공하는 것을 확인할 수 있다.args
args: 인자가 주어진 타입의 인스턴스인 조인 포인트로 매칭execution의args부분과 같다.execution과 args의 차이점
execution은 파라미터 타입이 정확하게 매칭되어야 한다.execution은 클래스에 선언된 정보를 기반으로 판단한다.args는 부모 타입을 허용한다.args는 실제 넘어온 파라미터 객체 인스턴스를 보고 판단한다.ArgsTest
pointcut():AspectJExpressionPointcut에 포인트컷은 한번만 지정할 수 있다.이번 테스트에서는 테스트를 편리하게 진행하기 위해 포인트컷을 여러번 지정하기 위해 포인트컷 자체를 생성하는 메서드를 만들었다.
String은Object,java.io.Serializable의 하위 타입이다.execution(* *(Object))는 매칭에 실패한다.args(Object)는 매칭에 성공한다. (부모 타입 허용)@target, @Within
정의
@target: 실행 객체의 클래스에 주어진 타입의 애노테이션이 있는 조인 포인트@within: 주어진 애노테이션이 있는 타입 내 조인 포인트설명
@target,@within은 다음과 같이 타입에 있는 애노테이션으로 AOP 적용 여부를 판단한다.@target(hello.aop.member.annotation.ClassAop)@within(hello.aop.member.annotation.ClassAop)@target vs @Within
@target은 인스턴스의 모든 메서드를 조인 포인트로 적용한다.@within은 해당 타입 내에 있는 메서드만 조인 포인트로 적용한다.쉽게 이야기해서

@target은 부모 클래스의 메서드까지 어드바이스를 다 적용하고,@within은 자기 자신의 클래스에 정의된 메서드에만 어드바이스를 적용한다.AtTargetAtWithinTest
실행 결과
parentMethod()는Parent클래스에만 정의되어 있고,Child클래스에 정의되어 있지 않기 때문에@within에서 AOP 적용 대상이 되지 않는다.실행결과를 보면
child.parentMethod()를 호출 했을 때[@within]이 호출되지 않은 것을 확인할 수 있다.@annotation, @Args
@annotation
정의
@annotation: 메서드가 주어진 애노테이션을 가지고 있는 조인 포인트를 매칭설명
@annotation(hello.aop.member.annotation.MethodAop)다음과 같이 메서드(조인 포인트)에 애노테이션이 있으면 매칭한다.
AtAnnotationTest
실행 결과
@Args
정의
@args: 전달된 실제 인수의 런타임 타입이 주어진 타입의 애노테이션을 갖는 조인 포인트설명
전달된 인수의 런타임 타입에
@Check애노테이션이 있는 경우에 매칭한다.@args(test.Check)bean
정의
bean: 스프링 전용 포인트컷 지시자, 빈의 이름으로 지정한다.설명
bean(orderService) || bean(*Repository)*과 같은 패턴을 사용할 수 있다.OrderService,*Repository(OrderRepository)의 메서드에 AOP가 적용된다.실행 결과
매개변수 전달
다음은 포인트컷 표현식을 사용해서 어드바이스에 매개변수를 전달할 수 있다.
this, target, args,@target, @Within, @annotation, @Args
다음과 같이 사용한다.
arg로 맞추었다.args(arg,..)args(String,..)ParameterTest
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 vs target
단순히 타입 하나를 정하면 되는데,
this와target은 어떤 차이가 있을까?스프링에서 AOP를 적용하면 실제
target객체 대신에 프록시 객체가 스프링 빈으로 등록된다.this는 스프링 빈으로 등록되어 있는 프록시 객체를 대상으로 포인트컷을 매칭한다.target은 실제 target 객체를 대상으로 포인트컷을 매칭한다.프록시 생성 방식에 따른 차이
스프링은 프록시를 생성할 때 JDK 동적 프록시와 CGLIB를 선택할 수 있다. 둘의 프록시를 생성하는 방식이 다르기 때문에 차이가 발생한다.
먼저 JDK 동적 프록시를 적용했을 때
this,target을 알아보자.MemberService 인터페이스 지정
this(hello.aop.member.MemberService)this는 부모 타입을 허용하기 때문에 AOP가 적용된다.target(hello.aop.member.MemberService)target은 부모 타입을 허용하기 때문에 AOP가 적용된다.MemberServiceImpl 구체 클래스 지정
this(hello.aop.member.MemberServiceImpl): proxy 객체를 보고 판단한다.JDK 동적 프록시로 만들어진 proxy 객체는
MemberService인터페이스를 기반으로 구현된 새로운 클래스다.따라서
MemberServiceImpl를 전혀 알지 못하므로 AOP 적용 대상이 아니다.target(hello.aop.member.MemberServiceImpl): target 객체를 보고 판단한다.target 객체가
MemberServiceImpl타입이므로 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의 경우 구체 클래스를 지정하면 프록시 생성 전략에 따라서 다른 결과가 나올 수 있다는 점을 알아두자.ThisTargetTest
this,target은 실제 객체를 만들어야 테스트 할 수 있다. 테스트에서 스프링 컨테이너를 사용해서target,proxy객체를 모두 만들어서 테스트해보자.properties = {"spring.aop.proxy-target-class=false"}:application.properties에 설정하는 대신 해당 테스트에서만 설정을 임시로 적용한다.이렇게 하면 각 테스트마다 다른 설정을 손쉽게 적용할 수 있다.
spring.aop.proxy-target-class=false: 스프링이 AOP 프록시를 생성할 때 JDK 동적 프록시를 우선 생성한다.물론 인터페이스가 없다면 CGLIB를 사용한다.
spring.aop.proxy-target-class=true: 스프링이 AOP 프록시를 생성할 때 CGLIB 프록시를 생성한다.참고로 이 설정을 생략하면 스프링 부트에서 기본으로 CGLIB를 사용한다.
spring.aop.proxy-target-class=false
JDK 동적 프록시 사용
JDK 동적 프록시를 사용하면
this(hello.aop.member.MemberServiceImpl)로 지정한[this-impl]부분이 출력되지 않는 것을 확인할 수 있다.spring.aop.proxy-target-class=true, 또는 생략(스프링 부트 기본 옵션)
CGLIB 사용
참고
this,target지시자는 단독으로 사용되기 보다는 파라미터 바인딩에서 주로 사용된다.All reactions