Spring Core/03. AOP

03. pointcut 작성

  • -

이번 포스트에서는 advice를 어떤 target의 메서드 즉 join point에 적용 할 것인지 결정하기 위한 pointcut 작성에 대해 살펴보자.

 

Pointcut(포인트컷)

 

pointcut이란?

Pointcut은 advice를 적용할 target의 메서드를 결정하는 메서드 선정 알고리즘으로 @Before 등 애너테이션의 value 값이다. pointcut을 작성 할 때는 execution, within, bean 등 지정자와 함께 사용한다.

  • execution: 메서드의 signature 기반으로 한 정교한 포인트컷 작성법으로 가장 많이 사용된다.
    • @Before("execution(String *..SimpleBean.getName())")
  • within: 빈 클래스 기반으로 포인트컷을 작성한다. 작성 방식은 execution의 class 부분과 동일하다.
    • @Before("within(com..SimpleBeanImpl)")
  • bean: 빈의 이름 기반으로 포인트컷을 작성한다.
    • @Before("bean(pointcutTestBean)")

여기서는 execution 기반으로 포인트컷 작성법을 살펴보자.

 

execution을 이용한 매서드 패턴 지정

execution에 작성하는 메서드의 패턴은 아래 그림처럼 4개 부분으로 나뉜다.

포인트컷 작성 방법

다음은 각 부분에 대한 작성 방법과 사용되는 기호에 대한 설명이다.

구분 설명
return_type 특징 메서드의 리턴 타입을 나타내며 필수 입력사항
기호 * 타입에 무관
! 타입에 대한 부정 ex) !String, !void
package + class 특징 패키지를 포함하는 클래스이름으로 메서드가 선언된 클래스 또는 상위 타입
선택사항으로 생략 가능하며 생략하지 않을 경우 메서드 이름과 .로 연결
기호 .. 0개 이상의 하위 패키지 대체. 맨 처음에는 등장할 수 없음
* 0개 이상의 문자열 대체
method_name 특징 메서드의 이름을 나타내며 필수 입력사항
기호 * 0개 이상의 문자열을 대체
parameter 특징 메서드의 파라미터를 이름 없이 타입만 입력하며 선택사항
기호 * 파라미터의 타입에 무관하게 1개의 파라미터 대체
.. 파라미터의 타입에 무관하게 0개 이상의 파라미터 대체
Spring의 AOP에서 pointcut을 작성할 때는 접근 제한자로 public만 사용 가능하다. interface 기반의 메섣드들은 어차피 public일 것이고 CGLib를 통해서 만들어지는 proxy도 public만 지원한다. 따라서 pointcut에서 접근 제한자는 의미가 없다.

return_type이나 parameter에 객체형을 사용할 때는 패키지 이름까지 명시해주는 것이 좋다. pointcut에 클래스 이름만 썼을 때는 중복되는 클래스를 구분하지 못하기 때문이다. 예를 들어 List라고 썼을 때 java.util.List인지 java.awt.List인지 구분할 수 없다. 물론 클래스 패스에서 유일한 이름이라면 상관 없지만 명확하게 하기 위해서는 패키지 이름까지 포함하는 것이 유리하다.

execution(public java.util.List<com.example.MyClass> *(..))


여러 개의 pointcut을 연결할 때는 &&, ||, !, and, or, not 등을 사용할 수 있다.

@Before("execution(* com..*(..)) || execution(* org..*(..))")

 

포인트컷 적용 예

빈을 추가하고 다양한 포인트컷이 주어질 때 어떤 메서드가 연결되는지 확인해 보자.

PointcutBean을 추가해보자.

단위테스트를 통해서 빈의 메서드들을 호출해보자.

@SpringBootTest 
@ActiveProfiles("dev") 
@Slf4j
public class PointcutTest {
    @Autowired
    CalculatorBean cbean;
    @Autowired
    PointcutBean pbean;

    @Test
    public void methodCall(){
        cbean.add(1, 2, 3);
        cbean.divide(1, 2);
        pbean.setName("hong");
        pbean.getName() ;
        pbean.print(List.of("hong", "jang"));
    }
}


advice를 추가하고 pointcut을 다양하게 변경해보면서 어떤 메서드들이 연동되는지 고민해보자.

@Before("execution(String com.doding.aop.beans.*.get*())")
// @Before("execution(!void com.doding..*.*(..))")
// @Before("execution(* com.doding..*(*))")
// @Before("execution(* com.doding..*(String))")
// @Before("execution(* com.doding..*(java.util.List))")

public void pointcutTest(JoinPoint jp){
    log.trace("pointcut 확인: {}", jp.getSignature());
}

 

기타

 

execution 이외의 지정자

pontcut을 작성하면서 지시자로 execution 이 외에도 within이나 bean, @annotation등을 사용할 수도 있다.

within실제 타겟 빈의 타입을 기준으로 해당 클래스, 인터페이스 내의 모든 메서드를 pointcut으로 지정할 수 있다. 유사하게 @within빈에 적용된 에너테이션을 기준으로 pointcut을 지정할 때 사용된다.  타입을 지정하는 세부적인 작성 방식은 execution의 class 부분과 동일하다.  

@Before("within(com.doding.service.*)")           // com.doding.service 소속 빈들의 모든 메서드
public void serviceBeans() {}

@Before("@within(org.springframework.stereotype.Service)")  // @Service 타입 빈들의 모든 메서드
public void serviceBeans() {}


bean 은 스프링의 빈 이름을 이용하여 포인트컷을 지정할 때 사용된다. 따라서 빈의 구체적인 타입이나 클래스의 구조와 무관하게 빈의 이름만으로 pointcut을 지정한다.

@Before("bean(*Service)")
public void serviceBeans() {}


@annotation 지정하는 특정 애너테이션이 선언된 메서드에 대해서 advice를 지정할 때 사용할 수 있다.

@Before("@annotation(org.springframework.transaction.annotation.Transactional)")
public void transactionalMethods() {}

 

pointcut 재사용

만약 하나의 pointcut이 여러개의 advice에 적용될 수 있다면 pointcut을 저장해서 재사용할 수 있다. 이것을 named pointcut이라고 하고 이를 위해 @Pointcut 애너테이션이 사용된다. @Pointcut이 선언된 메서드의 이름은 pointcut id가 되고 advice를 선언할 때 이 pointcut id를 이용하면 된다. 사용시 주의 사항은 마치 메서드를 호출하는 형태로 사용해야 하며 다른 클래스에 선언된 pointcut id를 사용해야 하는 경우 패키지를 포함한 전체 경로명을 써줘야 한다.

package com.doding.some.aop;

public class PointCuts {

    @Pointcut("execution(* com.doding.service.*.*(..))")
    public void serviceLayerExecution() {
        // 내용은 필요 없음
    }
}
package com.doding.other.aop;

@Aspect
@Component
public class LoggingAspect {

  @Before("com.doding.some.aop.Pointcuts.serviceLayerExecution()")
  public void logBeforeServiceLayerExecution(JoinPoint joinPoint) {
    // 타겟 동작 전에 실행 할 일 정리
  }
  
  @After("com.doding.some.aop.Pointcuts.serviceLayerExecution()")
  public void logAfterServiceLayerExecution(JoinPoint joinPoint) {
    // 타겟 동작 후에 실행 할 일 정리
  }
}

 

pointcut 작성 시 주의사항

일반적으로 Pointcut 작성 시는 다음의 사항에 주의해야 한다.

  • 정확한 범위 지정: 너무 넓은 범위의 Pointcut은 시스템 성능에 영향을 줄 수 있으며 전혀 예상치 못한 결과를 얻을 수 있다. 다음 표현식의 문제는 무엇일까?
    @Before("execution(* *(int, ..))")

    Spring이 관리하는 빈은 우리가 만든것 이외에도 어마어마하게 많고 그중에 어떤 빈의 어떤 메서드에 AOP가 적용되는지 알수 없다. 따라서 와일드카드를 남발하지 말고 적절한 패키지와 클래스를 이용한 제한은 반드시 필요하다.
  • 중복 방지: 여러 pointcut이 같은 메서드에 매칭될 경우 예상치 못한 동작이 발생할 수 있다. 따라서 pointcut을 설계할 때는 중복되지 않게 해야 하고 어쩔 수 없이 중복되는 경우는 @Order를 이용해서 순서를 명시해주자.
  • 동적 프록시의 이해: Spring의 AOP는 상속을 기반으로 동적 프록시를 생성한다. 따라서 final 클래스/메서드나 public 이외의 메서드는 AOP를 적용할 수 없다.
  • 마지막으로 AOP가 적용되면 쉽게 코드의 동작을 파악하기가 어렵다. 해당 메서드만 분석해서는 답이 없기 때문이다. 따라서 작성자의 정신이 가장 말짱할 때 반드시 충분한 테스트를 하고 문서화를 해두어야 나중에 유지보수가 용이하게 된다.
@Test
@DisplayName("CommonAspect의 argumentLogging에 의해 호출 시 로그 출력")
public void addTest(){
    int result = calculatorBean.add(1,2, 3);
    Assertions.assertEquals(6, result);
}

 

https://inf.run/uTES7

 

참~쉬운 스프링 Part 1. 스프링 프레임워크 이해 및 DI,AOP 강의 | 모두의 코딩:두딩 - 인프런

모두의 코딩:두딩 | 스프링 프레임워크의 핵심을 파고드는 강의로, DI와 AOP를 비롯한 스프링의 중심 개념을 마스터하게 됩니다. 환경 설정부터 단위테스트, 로깅, 메이븐 사용법까지, 스프링 프

www.inflearn.com

 

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

05. Spring 내부의 AOP들  (0) 2021.10.29
04. advice의 타입  (0) 2020.06.20
02. Aspect 작성과 동작  (0) 2020.06.18
01. AOP 기본 컨셉  (0) 2020.06.18
Contents

포스팅 주소를 복사했습니다

이 글이 도움이 되었다면 공감 부탁드립니다.