본문 바로가기
Spring

[Spring] Spring AOP - 관심사 분리(부가기능과 핵심기능)

by 덩라 2024. 2. 26.

아래 포스팅을 먼저 보시면 이해에 도움이 됩니다.

https://byunsw4.tistory.com/49

 

[Design Pattern] 프록시 패턴 & 데코레이터 패턴

0. 추가된 요구사항 이미 개발된 API 에 추가되는 기능이 발생했다고 생각해보겠습니다. 아래 Controller 는 쇼핑몰에 입점한 판매자가 해당 브랜드의 쿠폰을 등록하는 API 입니다. @RequiredArgsConstructor

byunsw4.tistory.com


0. AOP

AOP 란, "관점 지향 프로그래밍(Aspect Oriented Programming)" 의 약자로 부가기능과 핵심기능을 분리해서 개발하는 방법으로,

핵심기능을 구현한 비지니스 로직 내에서 부가기능을 같이 관리하는 경우, 코드의 가독성이 떨어지는 문제나 유지보수/코드 재사용성에 미치는 안 좋은 영향을 최소화 하기 위해 고안된 개발 방법입니다.

 

대표적으로 AspectJ 라는 프로젝트를 통해 이를 쉽게 구현할 수 있고, Spring 또한 해당 프로젝트를 차용하여 @Aspect 라는 어노테이션을 통해 Spring AOP 기능을 지원합니다.

@Slf4j
@Component
@Aspect
public class TimeAspect {

    @Around("execution(* hello.proxy.blog..*(..))")
    public Object timeLogging(ProceedingJoinPoint joinPoint) throws Throwable {
        long start = System.currentTimeMillis();
        log.info("aop logging start - by proxy");

        Object result = joinPoint.proceed();

        long end = System.currentTimeMillis();
        log.info("aop logging end - by proxy => {}ms", end - start);

        return result;
    }
}

위는 @Aspect 를 사용하여 구현한 간단한 AOP 예제 입니다.

본 포스팅에서 천천히 살펴보도록 하겠습니다.

 

1) Pointcut(포인트컷)

포인트컷은 쉽게 말하면 부가기능을 "어디에 적용할 것인가" 를 처리하기 위한 필터링 로직입니다.

 

예를 들어, "어떠한 요청을 처리하는데 걸리는 시간을 측정하는 부가기능"이 있다고 가정한다면, 이 부가기능은 Controller 영역에서만 처리되면 되고, Service 나 Domain 영역까지 필요한 부가기능은 아닐 것 입니다.

 

이렇게, "어디에는 적용하고 어디에는 적용하지 않고" 를 결정하기 위해 필요한 것이 바로 포인트컷 입니다.

앞서 본 예제에서는 아래 부분에 해당하는 내용이 바로 포인트컷 입니다.

"execution(* hello.proxy.blog..*(..))"

 

2) Advice(어드바이스)

어드바이스실행하고자 하는 부가기능, 즉 부가기능 로직입니다.

앞서 설명한 포인트컷에 의해 부가기능을 실행할 대상이 결정되고, 그 대상에서 어드바이스가 실행한다고 할 수 있겠습니다.

 

어드바이스는 부가기능 로직과 더불어, 해당 부가기능이 실행되는 위치에 대한 내용도 담고있습니다.

// @Around 는 부가기능을 포인트컷에 해당하는 메서드의 앞/뒤에서 실행시킨다는 의미
@Around("execution(* hello.proxy.blog..*(..))") // "execution(~~~)" 은 포인트컷
public Object timeLogging(ProceedingJoinPoint joinPoint) throws Throwable {
    // == 부가기능 시작 == //
    long start = System.currentTimeMillis();
    log.info("aop logging start - by proxy");

    // target(joinPoint) 로직 호출
    Object result = joinPoint.proceed();

    long end = System.currentTimeMillis();
    log.info("aop logging end - by proxy => {}ms", end - start);

    return result;
    // == 부가기능 끝 == //
}

 

3) Advisor(어드바이저)

어드바이저하나의 포인트 컷과 하나의 어드바이스를 가지는 형태를 의미합니다.

앞서 본 예제가 바로 어드바이저의 대표적인 예라고 할 수 있습니다.

  1. 포인트컷 : exection(* hello.proxy.blog..*(..))
  2. 어드바이스 : @Around 를 통한 부가기능 실행위치 정보 + 시간을 측정하는 메서드 로직

즉, @Aspect 를 통해 AOP 를 적용하면, 수행하고자 하는 부가기능을 하나의 어드바이저 형태로 개발하게 되는 것입니다.

 

1. 어드바이스의 종류

어드바이스는 실제로 수행하고자 하는 로직과 함께, 그 로직이 핵심기능의 어느 부분에서 실행되는지를 포함해야 합니다.

총 5가지의 위치에서 부가기능을 실행할 수 있습니다.

  1. @Around : 핵심기능이 실행되기 전과 실행되고 난 후에 부가기능이 동작. 실행범위가 가장 넓고, 대표적으로 사용되는 범위.
  2. @Before : 핵심기능이 실행되기 전에 동작.
  3. @After : 핵심기능이 실행되고 난 후에 동작. 무조건 실행됨. try~catch~finally 에서 finally 와 같음.
  4. @AfterReturning : 핵심기능이 실행된 후, 정상적으로 return 될 때 동작.
  5. @AfterThrowing : 핵심기능이 실행됐는데, 핵심기능에서 throw 가 발생했을 때 동작. try~catch~finally 에서 catch 와 같음.

아래는 각각의 어드바이스가 실행되는 위치에 따른 예제입니다.

// @Around -> ProceedingJoinPoint proceed() 를 무조건 해줘야 함. -> 안하면 대.장.애.
@Around("hello.aop.order.aop.Pointcuts.allOrderAndService()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
    log.info("[Around] {} start", joinPoint.getSignature());
    Object result = joinPoint.proceed(); // @Around 사용 시, 이 문장이 없으면 핵심기능 실행 안 됨.
    log.info("[Around] {} end", joinPoint.getSignature());
    return result;
}
    
// @Before / @AfterReturning / @AfterThrowing / @After -> 굳이 joinPoint 를 실행하지 않아도, 알아서 target 이 실행됨.
@Before("hello.aop.order.aop.Pointcuts.allOrderAndService()")
public void before(JoinPoint joinPoint) {
    log.info("[before] {}", joinPoint.getSignature());
}

// 메서드가 Return 된 후에 동작함.
// exception 이 발생하면 동작하지 않음
@AfterReturning("hello.aop.order.aop.Pointcuts.allOrderAndService()")
public void afterReturning(JoinPoint joinPoint) {
    log.info("[after returning] {}", joinPoint.getSignature());
}

// 메서드에서 exception 이 발생하면, excepion 처리 후에 동작함
// 정상종료되는 경우, 실행하지 않음
// try-catch-finally 에서 catch 에 해당
@AfterThrowing("hello.aop.order.aop.Pointcuts.allOrderAndService()")
public void afterThrowing() {
    log.info("[after throwing] - joinPoint 없어도 알아서 실행됩니다.");
}

// 예외 / 정상종료와 무관하게 무조건 실행됨
// try-catch-finally 에서 finally 느낌
@After("hello.aop.order.aop.Pointcuts.allOrderAndService()")
public void after() {
    log.info("[after] - 이 로그는 무조건 찍힙니다.");
}

핵심기능이 성공적을 수행되어 @AfterReturning 이 처리된 결과

 

핵심기능에서 exception 이 발생해 @AfterThrowing 이 처리된 결과

 

어드바이스에서 가장 많이 사용되는 범위는 @Around 입니다.
@Before 와 @After 를 모두 처리할 수 있고, 부가기능 로직에 따라 @AfterThrowing 까지 다 처리할 수 있기 때문입니다.
하지만, @Around 를 사용하는 경우, 반드시 해줘야 할 것이 있습니다.
바로 "핵심기능 로직 호출" 입니다.

위 예제 코드를 보면, 다른 범위는 다 파라미터로 JoinPoint 를 받는 반면, @Around 만 ProceedingJoinPoint 를 파라미터로 받는 것을 볼 수 있습니다.
ProceedingJoinPoint.java 를 확인해보면, JoinPoint 를 상속받고 있다는 것을 확인할 수 있고, ProceedingJoinPoint 에만 "proceed()" 라는 메서드가 선언되어 있음을 알 수 있습니다.

@Around 의 경우, 핵심기능의 전/후로 부가기능 로직을 수행하는데 이는 반대로 말하면, 부가기능이 수행되다가 어느 타이밍에 핵심기능이 호출되어야 하는지를 개발자가 지정해야 한다는 이야기 입니다.
다른 어드바이스들은 부가기능의 동작 타이밍이 정해져있기 때문에, 핵심기능 호출이 필요가 없지만, @Around 의 경우는 그렇지 않은 것이죠.

따라서, @Around 를 사용시에는 반드시! ProceedingJoinPoint.proceed() 를 명시해줘야 핵심로직이 실행됩니다.

 

 

2. 포인트컷 표현식

구현된 부가기능을 어디에 적용할지를 선택하기 위해 포인트컷에 특정 표현식을 사용하게 됩니다.

대표적인 표현식은 아래 종류가 있고, 하나씩 예제를 살펴보겠습니다.

 

1) execution

사실, 지금 설명하는 이 execution 표현식만 사용하더라도, 왠만한 상황에서는 대처가 가능합니다.

따라서 어드바이스에서 알아본 @Around 처럼 포인트컷 표현식에도 execution이 가장 많이 사용됩니다.

execution은 특정 규칙의 문자열을 분석해서 어드바이스를 적용하는 방식으로, 기본적인 포멧은 아래와 같습니다.

execution(접근제어자? 리턴타입 선언타입?메서드이름(파라미터) 예외?)

뒤에 ? 가 붙은건 있어도 되고 없어도 된다는 뜻입니다.

즉, 최소한으로 표현식을 작성하면 아래와 같은 형식으로도 사용할 수 있습니다.

execution(리턴타입 메서드이름(파라미터))

 

사실 이런 포멧으로만 보는거는 별로 와닿지가 않는 것 같습니다. 그래서 여러 케이스의 예시를 통해 알아보도록 하겠습니다.

 

다양한 포인트 컷을 확인하기 위해 아래 예제 코드를 기준으로 포인트컷을 적용해보겠습니다.

@Service
public class CouponService {

    public Long create(CouponCreateDto couponCreateDto) {
        return 10L;
    }

    public String find(String name) {
        return name;
    }

    public void delete(Long Id) throws IOException {

    }
}

 

public class CouponServiceAOPTest {
    // Spring AOP 에서 제공하는 포인트컷 객체
    // 포인트컷이 의도한대로 매칭되는지를 확인하기 위해 실제 Aspect 객체를 만들어도 되지만,
    // 테스트 코드로 간편하게 검증하기 위해 해당 객체를 사용
    // pointcut.setExpression(param) : param 표현식을 포인트컷에 적용
    // pointcut.matches(method, Object) : pointcut 이 Object.method()에 적용되는 대상인지 검증
    AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();

    // pointcut 테스트를 위한 메서드 정보 -> 자바 리플렉션 참고
    Method find; // CouponService.find() 에 대한 정보
    Method delete; // CouponService.delete() 에 대한 정보

    @BeforeEach
    void setup() throws NoSuchMethodException {
        find = CouponService.class.getMethod("find", String.class); // Parameter Type 이 String 인 find 메서드
        delete = CouponService.class.getMethod("delete", Long.class); // Parameter Type 이 String 인 delete 메서드
    }

}
여기에선 포인트컷 표현식이 제대로 적용됐는지를 확인해보기 위해 Spring AOP 에서 제공하는 AspectJExpressionPointcut 을 사용합니다. AspectExpressionPointcut 클래스의 setExpression() 메서드를 통해 포인트컷을 지정하고, matches() 메서드를 통해 포인트 컷이 제대로 매칭됐는지 검증합니다.

 

 

 

a. 모든 내용을 포함시킨 포인트컷 - 접근제어자 / 선언타입 / 예외 패턴 모두 포함

@Test
@DisplayName("모든 내용을 포함시킨 포인트컷 - 접근제어자 / 선언타입(패키지경로) / 예외 패턴 모두 포함")
void all_matches_pointcut_include_all_pattern() {
    pointcut.setExpression("execution(public void hello.proxy.blog.CouponService.delete(Long) throws java.io.IOException)");
    assertThat(pointcut.matches(delete, CouponService.class)).isTrue();
}

 

 

b. 생략 가능한 내용을 제외한 포인트컷 - 리턴타입 / 메서드 / 파라미터 만 포함

@Test
@DisplayName("생략 가능한 내용을 제외한 포인트컷 - 리턴타입 / 메서드명 / 파라미터만 포함")
void minimum_matches_pointcut() {
    pointcut.setExpression("execution(void delete(Long))");
    assertThat(pointcut.matches(delete, CouponService.class)).isTrue(); // 매칭 O
    assertThat(pointcut.matches(find, CouponService.class)).isFalse(); // 메서드 명과 파라미터 타입이 달라서 매칭 안 됨
}

 

 

c. 가장 많이 생략한 포인트컷 - 리턴타입 / 메서드 / 파라미터만 쓰되, 아무거나 와도 매칭됨. 사실상 글로벌 설정

@Test
@DisplayName("가장 많이 생략한 포인트컷 - 리턴타입 / 메서드명 / 파라미터 가 아무거나 와도 매칭되는 포인트 컷")
void any_matches_pointcut() {
    // 접근제어자 : 없음(생략됨)
    // 리턴타임 : * (아무거나 다 가능)
    // 선언타입 : 없음(생략됨)
    // 메서드명 : * (아무거나 다 가능)
    // 파라미터 : .. (아무거나 다 가능)
    // 예외 : 없음(생략됨)
    pointcut.setExpression("execution(* *(..))");
    assertThat(pointcut.matches(delete, CouponService.class)).isTrue();
    assertThat(pointcut.matches(find, CouponService.class)).isTrue();
}

 

 

d. 메서드 이름에 패턴을 매칭한 포인트컷

@Test
@DisplayName("메서드 이름에 패턴을 적용한 포인트 컷")
void method_name_pattern_matches_pointcut() {
    pointcut.setExpression("execution(* dele*(..))"); // 메서드 명이 "dele"로 시작하는 메서드
    assertThat(pointcut.matches(delete, CouponService.class)).isTrue();
    assertThat(pointcut.matches(find, CouponService.class)).isFalse();
}

 

 

e. 선언타입(패키지 + 클래스명)이 정확하게 매칭된 포인트컷

@Test
@DisplayName("선언타입(패키지 + 클래스)를 정확하게 매칭한 포인트 컷")
void package_class_matches_pointcut() {
    // hello.proxy.blog 패키지 하위의 CouponService 의 모든 메서드
    pointcut.setExpression("execution(* hello.proxy.blog.CouponService.*(..))");
    assertThat(pointcut.matches(delete, CouponService.class)).isTrue();
    assertThat(pointcut.matches(find, CouponService.class)).isTrue();
}

 

 

f. 특정 패키지의 하위의 모든 클래스의 모든 메서드를 매칭한 포인트컷

@Test
@DisplayName("특정 패키지 하위를 매칭한 포인트 컷")
void package_path_matches_pointcut() {
    // hello.proxy.blog 패키지 하위의 모든 클래스 의 모든 메서드
    pointcut.setExpression("execution(* hello.proxy.blog.*.*(..))");
    assertThat(pointcut.matches(delete, CouponService.class)).isTrue();
    assertThat(pointcut.matches(find, CouponService.class)).isTrue();
}

 

 

g. 파라미터를 정확하게 매칭한 포인트컷

@Test
@DisplayName("특정 파라미터를 정확하게 매칭한 포인트컷")
void exact_parameter_matches_pointcut() {
    // 파라미터로 String 타입 1개를 가지는 모든 메서드
    pointcut.setExpression("execution(* *(String))");
    assertThat(pointcut.matches(delete, CouponService.class)).isFalse();
    assertThat(pointcut.matches(find, CouponService.class)).isTrue();
}

 

 

h. 파라미터가 없는 메서드를 매칭한 포인트컷

@Test
@DisplayName("파라미터가 없는 메서드를 매칭하는 포인트컷")
void no_parameter_matches_pointcut() {
    pointcut.setExpression("execution(* *())");
    assertThat(pointcut.matches(delete, CouponService.class)).isFalse();
    assertThat(pointcut.matches(find, CouponService.class)).isFalse();
}

 

 

i. 첫 번째 파라미터가 일치하는 메서드를 매칭한 포인트컷

@Test
@DisplayName("앞에 파라미터는 일치하고, 뒤에 파라미터는 유동적인 메서드를 매칭하는 포인트컷")
void first_parameter_exact_after_all_matches_pointcut() {
    pointcut.setExpression("execution(* *(String, ..))"); // 첫 번째 파라미터는 String 이어야 하고, 그 이후론 있든 없든 상관없음
    assertThat(pointcut.matches(delete, CouponService.class)).isFalse();
    assertThat(pointcut.matches(find, CouponService.class)).isTrue();
}

 

위 예시들을 통해, 표현식을 조합하여 여러 가지의 execution 표현식을 만들어 포인트컷을 만들 수 있습니다.

 

2) within

within특정 타입에 대해 포인트컷을 지정한다는 의미로, 메서드를 기준으로는 지정할 수 없고 특정 타입에 대해서만 지정한다는 차이가 있습니다. 앞에서 배운 execution 표현식이 타입과 메서드 모두에서 필터링이 가능했다면, within타입에 대한 필터링만 처리되고, within 에 의해 필터링된 타입의 모든 메서드에서 부가기능을 처리한다고 생각하면 됩니다.

@Test
@DisplayName("within - CouponService 타입에 대해서 부가기능을 적용한다.")
void within_match_pointcut() {
    pointcut.setExpression("within(hello.proxy.blog.CouponService)");
    assertThat(pointcut.matches(delete, CouponService.class)).isTrue();
    assertThat(pointcut.matches(find, CouponService.class)).isTrue();
}

 

주의할 점은, 정확히 타입을 매칭하기 때문에, 상속관계에 있는 부모타입 지정은 불가능합니다.

@Test
@DisplayName("within - CouponService 타입에 대해서 부가기능을 적용한다.")
void within_parent_type_match_fail() {
    pointcut.setExpression("within(hello.proxy.blog.CouponServiceInterface)");
    assertThat(pointcut.matches(delete, CouponService.class)).isFalse();
    assertThat(pointcut.matches(find, CouponService.class)).isFalse();
}

 

 

3) args

args 는 파라미터의 타입에 따라 적용하는 표현식입니다. 파라미터의 인자로 가능한 타입을 지정하면 포인트컷이 적용되는 방식입니다.

@Test
@DisplayName("args - 메서드의 파라미터가 일치하면 적용하는 포인트컷")
void args_matches_pointcut() {
    pointcut.setExpression("args(String)");
    assertThat(pointcut.matches(delete, CouponService.class)).isFalse();
    assertThat(pointcut.matches(find, CouponService.class)).isTrue();
}

@Test
@DisplayName("args - args 는 파라미터가 품을 수 있는 모든 타입에 대해 적용된다. > 파라미터의 부모타입을 지정해도 포인트컷 적용")
void args_parent_type_matches_pointcut() {
    pointcut.setExpression("args(Object)");
    assertThat(pointcut.matches(delete, CouponService.class)).isTrue();
    assertThat(pointcut.matches(find, CouponService.class)).isTrue();
}

 

 

args 는 메서드의 파라미터가 품을 수 있는 타입을 포함하여 포인트컷을 적용할 수 있습니다. 즉, 메서드에 명시된 타입이 아니더라도, 부모타입으로 표현식을 지정해도 포인트컷이 매치됩니다. 

앞에서 본 execution의 경우는 메서드에 실제로 명시된 파라미터의 타입만을 매치한다면, args 는 런타임 중에 해당 파라미터로 들어올 수 있는 부모타입까지도 포인트컷으로 지정할 수 있다는 차이가 있습니다.

@Test
@DisplayName("args vs execution - args 는 파라미터 타입의 부모도 가능 / execution 은 코드에 명시된 타입만 가능")
void args_versus_execution_pointcut() {
    // == args 표현식
    AspectJExpressionPointcut argsPointcut = new AspectJExpressionPointcut();
    argsPointcut.setExpression("args(Object)");

    // delete(Long), find(String) 에서 Long 과 String 모두 Object 타입의 상속을 받은 타입이므로
    // args(Object) 로 해도 매치됨
    assertThat(argsPointcut.matches(delete, CouponService.class)).isTrue();
    assertThat(argsPointcut.matches(find, CouponService.class)).isTrue();

    // == execution 표현식
    AspectJExpressionPointcut executionPointcut = new AspectJExpressionPointcut();
    executionPointcut.setExpression("execution(* *(Object))");

    // delete(Long), find(String) 에서 메서드 파라미터 타입이 Long 과 String 으로 명시됐기 때문에
    // execution(* *(Object)) 로 하는 경우 매치 실패
    // 위 매치가 성립하려면, 메서드가 delete(Object) 혹은 find(Object) 형태로 정의되어야 함
    assertThat(executionPointcut.matches(delete, CouponService.class)).isFalse();
    assertThat(executionPointcut.matches(find, CouponService.class)).isFalse();
}

 

 

4) @target & @within

@target 과 @within특정 annotation 을 가지는 타입의 메서드에 부가기능을 적용하기 위한 포인트컷이라는 점에서 동일합니다.

그러나 두 표현식의 차이는 "부모타입까지 적용되는지 적용되지않는지" 입니다.

 

해당 표현식을 테스트해보기 위해 아래와 같은 annotation 을 생성하고, 이를 CouponService 에 추가해보겠습니다.

@Retention(RetentionPolicy.RUNTIME)
public @interface AopAnnotation {
}

public interface CouponServiceInterface {
    void superProcess();
}

@AopAnnotation
@Service
public class CouponService implements CouponServiceInterface{

    // == 중략 == //

    @Override
    public void superProcess() {

    }
}

 

생성한 @ClassAop 라는 annotation 을 통한 표현식을 @target 과 @within 표현식을 적용해보면 아래와 같습니다.

@Test
@DisplayName("@target : 특정 annotation 을 가진 타입에 적용되는 포인트컷 - 부모 타입까지 전파됨")
void annotation_target_within_matches_pointcut() throws NoSuchMethodException {
    pointcut.setExpression("@target(hello.proxy.blog.annotation.AopAnnotation)");

    // CouponService 구현체에 대해 매치됨
    assertThat(pointcut.matches(delete, CouponService.class)).isTrue();
    assertThat(pointcut.matches(find, CouponService.class)).isTrue();

    // 부모 타입인 CouponServiceInterface 에 대해서도 매치됨
    Method superProcess = CouponServiceInterface.class.getMethod("superProcess");
    assertThat(pointcut.matches(superProcess, CouponServiceInterface.class)).isTrue();
}

@target 은 annotation 이 붙어있는 클래스와 함께 해당 클래스의 부모 타입에도 어드바이스가 전파됨을 알 수 있습니다.

 

@Test
@DisplayName("@within : 특정 annotation 을 가진 타입에 적용되는 포인트컷 - 부모 타입으로 전파 안됨")
void annotation_within_matches_pointcut() throws NoSuchMethodException {
    pointcut.setExpression("@within(hello.proxy.blog.annotation.AopAnnotation)");

    // CouponService 구현체에 대해 매치됨
    assertThat(pointcut.matches(delete, CouponService.class)).isTrue();
    assertThat(pointcut.matches(find, CouponService.class)).isTrue();

    // 부모 타입인 CouponServiceInterface 에 대해서는 매치 안 됨
    Method superProcess = CouponServiceInterface.class.getMethod("superProcess");
    assertThat(pointcut.matches(superProcess, CouponServiceInterface.class)).isFalse();
}

@within 은 @target 과 다르게, 부모 타입에는 어드바이스가 전파되지 않음을 알 수 있습니다.

 

5) @annotation

@annotation 표현식특정 annotation을 가지는 메서드에 대해 어드바이스를 적용합니다.

사용법은 앞서 봤던 @target, @within 과 동일하지만, 메서드에 대해 어드바이스 적용 여부를 결정한다는 점에서 차이가 있습니다.

@AopAnnotation
@Service
public class CouponService implements CouponServiceInterface{

    // == 중략 == //
    
    public String find(String name) {
        return name;
    }

    @AopAnnotation
    public void delete(Long id) throws IOException {

    }
}

 

@Test
@DisplayName("@annotation : 특정 annotation 을 가진 메서드에 대해 매칭")
void annotation_matches_pointcut() {
    pointcut.setExpression("@annotation(hello.proxy.blog.annotation.AopAnnotation)");

    assertThat(pointcut.matches(find, CouponService.class)).isFalse();
    assertThat(pointcut.matches(delete, CouponService.class)).isTrue();
}

 

 

6) bean

bean 표현식스프링 빈의 등록된 이름에 대해 포인트컷을 매치하는 표현식입니다.

스프링 빈의 이름에 따라 어드바이스의 적용여부가 결정되는 만큼, 스프링에서만 사용 가능한 표현식입니다.

@Test
@DisplayName("bean : 스프링 빈의 이름으로 매칭. 스프링에서만 사용 가능한 표현식")
void bean_matches_pointcut() {
    pointcut.setExpression("bean(CouponService)");

    assertThat(pointcut.matches(delete, CouponService.class)).isTrue();
    assertThat(pointcut.matches(find, CouponService.class)).isTrue();
}

 

스프링 빈 이름에 * 와 같은 표현식을 넣어, 특정 이름이 포함된 빈에 대해 어드바이스를 적용할 수 도 있습니다.

@Test
@DisplayName("bean : 이름에 패턴을 넣어 매치시킬 수 있음")
void bean_pattern_matches_pointcut() {
    pointcut.setExpression("bean(*Service)");

    assertThat(pointcut.matches(delete, CouponService.class)).isTrue();
    assertThat(pointcut.matches(find, CouponService.class)).isTrue();
}

 

 

3. 포인트컷 조합하기

위에서 알아본 여러 가지 형태의 포인트컷을 통해 2가지 이상의 포인트컷을 조합하여 어드바이스 적용여부를 결정할 수 있습니다.

아래는 앞에서 알아본 어드바이스와 포인트컷을 통해 만든 Aspect 입니다.

@Slf4j
@Component
@Aspect
public class ComplexAspect {

    @Pointcut("execution(* hello.proxy.blog.coupon..*(..))")
    private void allCoupon() {}

    @Pointcut("execution(* *..*Service.*(..))")
    private void allService(){}

    @Around("allCoupon() && allService()")
    public Object allServiceAboutCoupon(ProceedingJoinPoint joinPoint) throws Throwable {

        // == advice logic == //
        
        return joinPoint.proceed();
    }
}

 

위 처럼 @Pointcut() 이라는 annotation 을 통해 어드바이스를 적용할 대상을 앞에서 배운 여러 가지의 표현식을 통해 복합적인 조건을 모두 충족하는 어드바이저를 생성할 수 있습니다.

 

위 Aspect 를 예로 들면, 아래 2가지를 모두 만족하는 대상에 대해 어드바이스를 적용한다는 뜻 입니다. 

  1. hello.proxy.blog.coupon 패키지 하위에 존재하는 모든 클래스의 모든 메서드
  2. 클래스 명이 Service 로 끝나는 클래스의 모든 메서드

 


참고 및 출처.

https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-%ED%95%B5%EC%8B%AC-%EC%9B%90%EB%A6%AC-%EA%B3%A0%EA%B8%89%ED%8E%B8

 

스프링 핵심 원리 - 고급편 강의 - 인프런

스프링의 핵심 원리와 고급 기술들을 깊이있게 학습하고, 스프링을 자신있게 사용할 수 있습니다., 핵심 디자인 패턴, 쓰레드 로컬, 스프링 AOP스프링의 3가지 핵심 고급 개념 이해하기 📢 수강

www.inflearn.com

 

댓글