애스펙트J는 포인트컷을 편리하게 표현하기 위한 특별한 표현식을 제공한다.
포인트컷 표현식은 execution같은 포인트컷 지시자로 시작한다. 줄여서 PCD라 한다.
종류
execution: 메서드 실행 조인 포인트를 매칭한다. 스프링 AOP에서 가장 많이 사용하고, 기능도 복잡하다.within: 특정 타입 내의 조인 포인트를 매칭한다.args: 인자가 주어진 타입의 인스턴스의 조인 포인트this: 스프링 빈 객체(스프링 AOP 프록시)를 대상으로 하는 조인 포인트target: Target 객체(스프링 AOP 프록시가 가르키는 실제 대상)를 대상으로 하는 조인 포인트@target: 실행 겍체의 클래스에 주어진 타입의 어노테이션이 있는 조인 포인트@annotation: 메서드가 주어진 어노테이션을 가지고 있는 조인 포인트를 매칭@args: 전달된 실제 인수의 런타임 타입이 주어진 타입의 어노테이션을 갖는 조인 포인트bean: 스프링 전용 포인트컷 지시자, 빈의 이름으로 포인트컷을 지정한다.execution을 가장 많이 사용하고, 나머지는 자주 사용하지 않는다.





execution 문법
execution(접근제어자? 반환타입 선언타입?메서드이름(파라미터) 예외?)
*와 같은 패턴을 지정할 수 있다.
매칭 조건
접근제어자?: public
반환타입: String
선언타입?: hello.aop.member.MemberServiceImpl
메서드이름: hello
파라미터: (String)
예외?: 생략
메서드와 포인트컷 표현식의 모든 내용이 정확하게 일치한다. 따라서 true를 반환한다.

매칭 조건
접근제어자?: 생략
반환타입: *
선언타입?: 생략
메서드이름: *
파라미터: (...)
예외?: 없음
*는 아무 값이 들어와도 된다는 뜻.
파라미터의 ..는 파라미터의 타입과 파라미터 수가 상관없다는 뜻.
0개도 되고 여러개도 되고
@Test
void nameMatch() {
pointcut.setExpression("execution(* hello(..))");
assertThat(pointcut.matches(helloMethod, MemberServiceImpl.class)).isTrue();
}
@Test
void nameMatchStar1() {
pointcut.setExpression("execution(* hel*(..))");
assertThat(pointcut.matches(helloMethod, MemberServiceImpl.class)).isTrue();
}
@Test
void nameMatchStar2() {
pointcut.setExpression("execution(* *el*(..))");
assertThat(pointcut.matches(helloMethod, MemberServiceImpl.class)).isTrue();
}
@Test
void nameMatchFalse() {
pointcut.setExpression("execution(* nono(..))");
assertThat(pointcut.matches(helloMethod, MemberServiceImpl.class)).isFalse();
}
@Test
void packageExactMatch1() {
pointcut.setExpression("execution(* hello.aop.member.MemberServiceImpl.hello(..))");
assertThat(pointcut.matches(helloMethod, MemberServiceImpl.class)).isTrue();
}
@Test
void packageExactMatch2() {
pointcut.setExpression("execution(* hello.aop.member.*.*(..))");
assertThat(pointcut.matches(helloMethod, MemberServiceImpl.class)).isTrue();
}
@Test
void packageExactFalse() {
pointcut.setExpression("execution(* hello.aop.*.*(..))");
assertThat(pointcut.matches(helloMethod, MemberServiceImpl.class)).isFalse();
}
@Test
void packageMatchSubPackage1() {
pointcut.setExpression("execution(* hello.aop.member..*.*(..))");
assertThat(pointcut.matches(helloMethod, MemberServiceImpl.class)).isTrue();
}
@Test
void packageMatchSubPackage2() {
pointcut.setExpression("execution(* hello.aop..*.*(..))");
assertThat(pointcut.matches(helloMethod, MemberServiceImpl.class)).isTrue();
}
타입 매칭 - 부모 타입 허용

부모는 자식을 품을 수 있기 때문에 가능
타입 매칭 - 부모가 가지고 있는 메서드만 가능

MemberServiceImpl 클래스만 소장하고 있는 internal 메서드를 확인하려는 경우 없다고 뜸. 부모는 hello라는 메서드만 가지고 있기 때문.
파라미터
//String 타입의 파라미터 허용
@Test
void argsMatch() {
pointcut.setExpression("execution(* *(String))");
assertThat(pointcut.matches(helloMethod, MemberServiceImpl.class)).isTrue();
}
//파라미터 X
@Test
void argsMatchNoArgs() {
pointcut.setExpression("execution(* *())");
assertThat(pointcut.matches(helloMethod, MemberServiceImpl.class)).isFalse();
}
//정확히 하나의 파라미터 허용, 모든 타입
@Test
void argsMatchStar() {
pointcut.setExpression("execution(* *(*))");
assertThat(pointcut.matches(helloMethod, MemberServiceImpl.class)).isTrue();
}
//(), (Xxx), (Xxx, Xxx)
@Test
void argsMatchAll() {
pointcut.setExpression("execution(* *(..))");
assertThat(pointcut.matches(helloMethod, MemberServiceImpl.class)).isTrue();
}
//처음이 무조건 String
//(String), (String, Xxx), (String, Xxx, Xxx)
@Test
void argsMatchComplex() {
pointcut.setExpression("execution(* *(String, ..))"); // String, * 는 파라미터가 2개
assertThat(pointcut.matches(helloMethod, MemberServiceImpl.class)).isTrue();
}
execution 파라미터 매칭 규칙
(String): 정확하게 String 타입 파라미터(): 파라미터 X(*): 정확히 하나의 파라미터, 대신 모든 타입(*, *): 정확히 두 개의 파라미터, 모든 타입(..): 숫자와 무관하게 모든 파라미터. 없어도 됨.(String, ..): (String), (String, Xxx), (String, Xxx, Xxx) ...(String, *): 파라미터 2개만 가능. 처음은 String특정 타입을 지정할 수 있고, 매칭되는 메서드를 전부 가져옴.
@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과 유사하다. 그래도 execution보다 기능이 적다.
하지만 차이점이 있는데,

execution과 다르게, 부모를 가져와 자식 비교를 할 수 없기 때문에 인터페이스 선정을 할 수 없다.
근데 잘 안씀. 이유는 execution이 더 좋아서.
강의중에 파일을 통채로 복사할 정도로 잘 안쓰는듯.
args: 인자가 주어진 타입의 인스턴스인 조인 포인트로 매칭
execution과 args의 차이점
execution은 파라미터 타입이 정확히 매칭되어야 한다.args는 부모 타입을 허용한다.
args vs execution

args의 경우에는 런타임에 전달된 인수로 보기 때문에 부모타입도 허용이 가능하다.
execution의 경우 메서드 시그니처로 판단하기 때문에 타입이 동일해야 한다.
args의 경우 단독으로는 잘 안쓴다고 함.
정의
@target: 실행 객체의 클래스에 주어진 타입의 어노테이션이 있는 조인 포인트
@within: 주어진 어노테이션이 있는 타입 내 조인 포인트
@target(hello.aop.member.annotation.ClassAop)
@within(hello.aop.member.annotation.ClassAop)
@target vs @within
@target은 인스턴스의 모든 메서드를 조인 포인트로 적용한다.
@within은 해당 타입 내에 있는 메서드만 조인 포인트로 적용한다.

package hello.aop.pointcut;
import hello.aop.member.annotation.ClassAop;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Import;
@Slf4j
@Import({AtTargetAtWithinTest.Config.class})
@SpringBootTest
public class AtTargetAtWithinTest {
@Autowired
Child child;
@Test
void success() {
log.info("child Proxy={}", child.getClass());
child.childMethod(); //부모, 자식 모두 있는 메서드
child.parentMethod(); //부모 클래스만 있는 메서드
}
static class Config {
@Bean
public Parent parent() {
return new Parent();
}
@Bean
public Child child() {
return new Child();
}
@Bean
public AtTargetAtWithinAspect atTargetAtWithinAspect() {
return new AtTargetAtWithinAspect();
}
}
static class Parent {
public void parentMethod(){} //부모에만 있는 메서드
}
@ClassAop
static class Child extends Parent {
public void childMethod(){}
}
@Slf4j
@Aspect
static class AtTargetAtWithinAspect {
//@target: 인스턴스 기준으로 모든 메서드의 조인 포인트를 선정, 부모 타입의 메서드도 적용
@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();
}
//@within: 선택된 클래스 내부에 있는 메서드만 조인 포인트로 선정, 부모 타입의 메서드는 적용되지 않음
@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();
}
}
}

@target : 내꺼와 연결된 부모. 동적
@within : 내꺼만. 정적
주의
args, @args, @target 는 단독으로 사용하면 안된다.
실행 시점에 일어나는 포인트컷 적용도 결국에는 프록시가 있어야 할 수 있다.
프록시 없이 사용하면, 모든 스프링 빈에 AOP를 적용하려고 한다.
그럼 오류가 발생한다.
그러므로 execution 등과 같이 범위를 정해주는 것과 함께 사용해줘야한다.
@annotation: 메서드가 주어진 어노테이션을 가지고 있는 조인 포인트를 매칭
@annotation(hello.aop.member.annotation.methodAop)

이렇게 사용한다.
쉽게 말해 @MethodAop 어노테이션이 걸린 곳을 프록시로 만들어 주는 것이다.
좀 유용하게 사용한다.
@args의 경우, 파라미터로 들어온 인수 클래스 내부에 어노테이션이 있을때 프록시를 생성해준다고 한다. 이건 잘 안쓴다고 함.
@args(test.Check)
이런식으로 사용하면 @Check가 들어온 파라미터 객체의 내부에 있을 경우, 프록시 생성.
bean: 스프링 전용 포인트컷 지시자, 빈의 이름으로 지정한다.
bean(orderService) || bean(*Respository)
*와 같은 패턴을 사용할 수 있다.

다음 포인트컷 표현식을 사용해서 어드바이스에서 매개변수를 전달할 수 있다.
this target, args, @target, @within, @annotation, @args
@Before("allMember() && args(arg, ..)")
public void logArgs3(String arg) {
log.info("[logArgs3] arg={}", arg);
}
포인트컷의 이름과 매개변수의 이름을 맞춰야 한다. arg
추가로 타입의 메서드에 지정한 타입으로 제한된다. String으로 받기 때문에 String으로 제한됨.
package hello.aop.pointcut;
import hello.aop.member.MemberService;
import hello.aop.member.annotation.ClassAop;
import hello.aop.member.annotation.MethodAop;
import lombok.extern.slf4j.Slf4j;
import org.aopalliance.intercept.Joinpoint;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.annotation.Import;
@Slf4j
@Import(ParameterTest.ParameterAspect.class)
@SpringBootTest
public class ParameterTest {
@Autowired
MemberService memberService;
@Test
void success() {
log.info("memberService Proxy={}", memberService.getClass());
memberService.hello("helloA");
}
@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]{}, obj={}", joinpoint.getSignature(), annotation.getClass());
}
@Before("allMember() && @within(annotation)")
public void atWithin(JoinPoint joinpoint, ClassAop annotation) {
log.info("[@within]{}, obj={}", joinpoint.getSignature(), annotation.getClass());
}
@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: 메서드의 어노테이션을 받음. 해당 어노테이션의 값도 받을 수 있음

this: 스프링 AOP 프록시를 호출
target: 스프링 AOP 프록시가 실제로 호출하는 대상을 호출
this(hello.aop.member.MemberService)
target(hello.aop.member.MemberService)
* 패턴 사용할 수 없다.
부모 타입 허용
this vs target
어려운 내용인데, 또 그렇게 중요하지는 않음.
또 많이 사용하지도 않음.
JDK 동적 프록시 : 인터페이스가 필수이고, 인터페이스를 구현한 프록시 객체를 생성
CGLIB : 인터페이스가 있어도 구체 클래스를 상속받아서 프록시 객체를 생성

이때는 문제가 없다.
this : proxy 객체를 보고 판단한다.
target : target 객체를 보고 판단한다.
MemberServiceImpl 구체 클래스 지정
this : 프록시는MemberService 인터페이스를 기반으로 구현된 새로운 클래스이다. 그러므로 MemberServiceImpl을 전혀 모르기 때문에 AOP 적용 대상이 아니다.
target : MemberServiceImpl이므로 AOP 적용 대상이다.

this : proxy 객체를 보고 판단한다.
target : target 객체를 보고 판단한다.
MemberServiceImpl 구체 클래스 지정
this : 프록시는MemberServiceImpl를 상속받아 구현된 새로운 클래스이다. 그러므로 포인트컷의 대상이 된다.
target : MemberServiceImpl이므로 AOP 적용 대상이다.
정리
this의 경우 구체 클래스를 지정하면 다른 결과가 나올 수 있다.
package hello.aop.pointcut;
import hello.aop.member.MemberService;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.annotation.Import;
/**
* application.properties
* spring.aop.proxy-target-class=true CGLIB
* spring.aop.proxy-target-class=false JDK 동적 프록시
*/
@Slf4j
@Import(ThisTargetTest.ThisTargetAspect.class)
@SpringBootTest(properties = "spring.aop.proxy-target-class=true")
public class ThisTargetTest {
@Autowired
MemberService memberService;
@Test
void success() {
log.info("memberService Proxy={}", memberService.getClass());
memberService.hello("helloA");
}
@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();
}
//부모 타입 허용
@Around("this(hello.aop.member.MemberServiceImpl)")
public Object doThis(ProceedingJoinPoint joinPoint) throws Throwable {
log.info("[this-impl] {}", joinPoint.getSignature());
return joinPoint.proceed();
}
//부모 타입 허용
@Around("target(hello.aop.member.MemberServiceImpl)")
public Object doTarget(ProceedingJoinPoint joinPoint) throws Throwable {
log.info("[target-impl] {}", joinPoint.getSignature());
return joinPoint.proceed();
}
}
}
CGLIB 일때

JDK 일때
