Spring Core/03. AOP

03. pointcut 작성

  • -
반응형

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

 

Pointcut(포인트컷)

 

pointcut이란?

Pointcut은 target의 메서드에서 advice를 적용할 것인지 결정하는 메서드 선정 알고리즘으로 @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개 이상의 파라미터 대체

return_type이나 parameter에 객체형을 사용할 때는 String이나 Integer 등을 제외한 사용자 정의 클래스는 패키지 이름까지 명시해줘야 한다.

 

포인트컷 적용 예

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

Aspect를 만들고 Pointcut이 주어질 때 동작하는 메서드들을 추정해 보자.

package com.doding.aoptest.aspects;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;

import lombok.extern.slf4j.Slf4j;

@Component
@Aspect
@Slf4j
public class PointcutTestAspect {
  // @Before("execution(String *..SimpleBean.getName())")
  // @Before("execution(!String com..set*(String))")
  // @Before("execution(void com..*Bean.*(*, *))")
  // @Before("execution(* com.doding..*(int, ..))")
  public void pointcutTest(JoinPoint jp) {
    log.debug("pointcut 확인 메서드: {}", jp.getSignature());
  }
}

잘못 사용된 pointcut은 심각한 문제를 일으킬 수 있다. 다음의 코드는 반환 타입을 고려하지 않고 패키지도, 메서드 이름도 상관 없고 단지 첫 번째 파라미터가 int가 있으면 되는 경우이다.

@Before("execution(* *(int, ..))")

얼핏 보면 PointcutBean의 calc가 대상인 것처럼 보이지만 사실 Spring이 관리하는 빈은 우리가 만든것 이외에도 어마어마하게 많고 그중에 어떤 빈의 어떤 메서드에 AOP가 적용되는지 알수 없다. 따라서 적절한 패키지와 클래스를 이용한 제한은 반드시 필요하다.

 

단위테스트를 통해서 빈의 메서드들을 동작시켜서 예상대로 AOP가 동작하는지 점검해 보자.

@SpringBootTest
public class PointcutTest {

    @Autowired
    PointcutBean pBean;

    @Autowired
    SimpleBean sBean;

    @Test
    public void methodCallTest() throws Exception {
        sBean.setName("홍길동");
        sBean.setNow(Calendar.getInstance());
        sBean.getName();
        sBean.getNow();
        pBean.setOper("*");
        pBean.calc(100, 200);
    }
}

 

기타

 

execution 이외의 지정자

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

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

@Before("within(com.doding..service.*)")
public void serviceBeans() {}

@Before("@within(org.springframework.stereotype.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의 연결

&&, ||, !, and, or, not을 이용해 여러 개의 pointcut을 연결해서 사용할 수도 있다. 다음은 pointcutBean이라는 빈 이거나 com..SimpleBean 타입인 경우 적용되는 point cut을 선언한 예이다.

@Before("bean(pointcutBean) || within(com..SimpleBean)")
반응형

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

06. AOP 작성 주의 사항  (0) 2024.02.16
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

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

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