[Spring] Spring Advisor와 Pointcut - 개념편

반응형

Spring AOP 컴포넌트를 이용해 Advice를 만들어 메서드의 전후처리를 프록시 객체를 통해 구현해봤습니다.

 

2022.05.16 - [Programming/Spring] - [Spring] Spring Advice로 커스텀 어드바이스 만들기

 

[Spring] Spring Advice로 커스텀 어드바이스 만들기

이번 포스트부터는 Spring AOP 컴포넌트를 이용하여 실제 Advice를 만들어보고, 위빙하는 시간을 가져보겠습니다. Advice Interface Advice에 대해 다시 복습해보자면 Advice는 특정 조인포인트 즉, 메서드가

blog.neonkid.xyz

Spring AOP의 ProxyFactory 클래스는 우리가 작성한 메서드에서 AOP 프록시 인스턴스를 얻고 구성하는 간단한 방법을 제공했는데요. 

 

이 원리를 보자면 addAdvice 메서드가 호출되었을 때 내부적으로 addAdvisor 메서드에 작업을 위임하여 DefaultPointcutAdvisor 인스턴스를 생성하고 모든 메서드를 가리키는 포인트컷으로 어드바이저를 구성합니다. 이런 방식으로 대상의 모든 메서드에 어드바이스가 적용되는 것입니다.

 

그런데, AOP를 로깅 목적으로 사용한다면 괜찮겠지만 때로는 특정 메서드에서 어드바이스가 작동하지 않도록 구현해야할 때가 있습니다.

 

 

 

메서드의 순차적 검사

간단하게는 모든 메서드를 순차적으로 확인하여 해당 메서드가 어드바이스 동작을 제외해야 할지를 구분하는 알고리즘을 구현해볼 수 있습니다. 하지만 이 접근 방식에는 몇 가지 단점이 있습니다.

 

  • 적용 가능한 메서드 목록을 어드바이스에 하드 코딩하는 경우, 어드바이스의 재사용성 감소
  • 어드바이스 내 어드바이스가 적용되는지 메서드를 검사할 때 O(n) [여기서 n은 메서드 갯수]의 시간이 발생하므로 성능 이슈 발생

 

1번째 단점은 단순히 프로그래밍적인 이슈인데, 만약 하드 코딩을 통해 적용가능한 메서드 목록을 넣는 경우 어드바이스 마다 이 메서드를 넣어줘야 하기 때문에 사실상 어드바이스를 쓰기 보다는 직접 구현해서 넣는 것을 선호할 수도 있겠다 라는 생각이 들엇습니다.

 

2번째 단점은 어드바이스가 메서드 호출 때마다 이 메서드에 내가 있어야 하는지 없어야 하는지를 일일이 메서드 갯수만큼 순회해야한다는 것입니다. 이 문제는 메서드가 늘어나는 만큼 성능 감소량이 커지기 때문에 심히 우려해야 할 부분입니다.

 

 

 

포인트컷(Pointcut)

앞서 본 단점을 개선한 것이 바로 포인트컷입니다. 포인트컷을 사용하면 어드바이스 내에 적용 대상 메서드인지를 검사하는 코드를 넣지 않아도 어드바이스를 적용할 메서드를 구성할 수 있습니다.

 

또한 포인트컷은 각 메서드마다 이 메서드가 어드바이스를 적용해야 하는지, 안해야 하는지를 한 번 검사를 수행한 뒤 이를 캐싱한 뒤 나중에 재사용하기 때문에 성능 이슈도 해소할 수 있습니다. 

 

대상 내에 있는 메서드에 어드바이스 적용을 제어할 때에는 무조건 포인트컷(Pointcut)을 쓰는 것이 바람직한가요?

 

가급적이면 그래야겠지만 무조건은 아닙니다. 예를 들어 우리가 이전 글에서 다뤘던 KeyGenerator처럼 어드바이스와 대상 클래스가 밀접하게 연관되어 있는 강결합한 케이스의 경우에는 하드 코딩이 좋습니다. 범용적으로 써야하는 어드바이스인 경우는 그 대상이 모호하여 하드 코딩하기에는 번거로움이 많이 생기겠지만 그 대상이 명확한 것은 반대로 하드 코딩이 좋을 것입니다.

 

이처럼 어드바이스와 대상 사이의 결합도를 대상 친밀도(Target affinity)라고 합니다. 일반적으로 어드바이스에 대한 친밀도가 거의 없거나 전혀 없다면 포인트컷을 사용하는 것이 좋습니다.

 

그럼 포인트컷 인터페이스가 어떻게 되어 있는지 알아보겠습니다. Spring에서 포인트컷은 다음과 같은 Pointcut 인터페이스를 구현해 생성합니다.

 

Spring AOP 패키지에 있는 Pointcut 인터페이스를 보면 ClassFilter와 MethodMacher의 인스턴스를 각각 변환하는 getter 메서드가 존재합니다. 

 

Pointcut 인터페이스에는 getClassFilter 메서드가 있는데, 이는 Pointcut을 특정 메서드에 적용할지 여부를 결정할 때 쓰는 메서드로 Spring에서 이 메서드를 통해 Pointcut 인터페이스가 메서드의 클래스에 적용되는지를 먼저 확인합니다.

 

ClassFilter 인터페이스에는 matches 메서드가 있습니다. 이 메서드는 검사할 클래스를 나타내는 Class 인스턴스를 전달하는데, matches가 true를 반환하면 포인트컷이 해당 클래스에 반영되는 것이고, 그렇지 않으면 false를 반환합니다.

 

ClassFilter 밑으로 MethodMatcher가 있습니다. Spring에서는 2가지 MethodMatcher를 지원하며 isRuntime 메서드에 따라 정적 MethodMatcher를 쓸지 동적 MethodMatcher를 쓸지를 결정합니다.

 

  • isRuntime() 메서드로 runtime인지 여부를 확인
  • 위 메서드 반환값에 따라 MethodMatcher의 종류를 결정

 

정적 포인트컷일 때 스프링은 대상의 모든 메서드에 대해 한 번씩 MethodMatcher의 matches 메서드를 호출하고 반환값을 캐싱하여 나중에 메서드를 호출하는 데 사용합니다. (각 메서드마다 한 번만 이 로직을 적용하며 나중에는 캐시된 값을 이용)

 

동적 포인트컷일 때 스프링은 메서드의 전반적인 적용 가능성을 결정하기 위해 메서드를 처음 호출했을 때 matches 메서드를 사용하여 정적 검사를 수행합니다. 하지만 만약에 정적 검사가 true를 반환하면 스프링은 matches 메서드를 사용해 메서드를 호출할 때마다 추가로 정적 검사를 더 수행하게 됩니다.

 

  • 메서드 첫 호출시 matches로 정적 검사 수행
  • 만약 정적 검사시 true를 반환한 경우 메서드 호출마다 정적 검사 수행
  • 정적 검사가 false로 나타난 경우 이 메서드는 포인트컷을 수행하지 않으므로 검사 중단

이런 방식으로 동적 MethodMatcher는 메서드 자체를 보는 것이 아닌 메서드의 특정 호출을 기반으로 포인트컷을 적용해야 할지를 보기 때문에 더 많은 비용이 발생합니다.

 

위와 같은 상황이 필요한 경우가 있다면 예를 들어 인수가 100보다 큰 Integer일 때만 포인트컷을 적용하고 싶은 경우가 있습니다. 이처럼 메서드의 모양, 형태로 구분하지 않고 메서드의 특정 행동으로 보려고 한다면 동적 MethodMatcher를 수행해야 합니다.

 

하지만 동적 MethodMatcher는 그만큼 비용이 크기 때문에 정적 MethodMatcher보다 성능이 좋지는 않습니다. 하지만 동적 포인트컷은 어드바이스를 적용할지 여부를 결정할 때 상당한 유연성을 제공하며 이 부분이 필요하지 않다면 일반적으론 정적 MethodMatcher를 사용하는 것이 좋습니다. 

 

동적 MethodMatcher를 써야할 때가 있다면 어드바이스가 상당한 오버헤드를 발생하는 로직일 때 동적 포인트컷을 이용해 불필요한 어드바이스의 호출을 피하도록 구현한다면 효율적으로 사용해 볼 수 있을 것입니다.

 

 

 

Spring의 Pointcut 구현체

포인트컷 인터페이스는 구현체가 아니기 때문에 우리가 이를 사용하기 위해서는 어떤식으로 사용할지를 구현해줘야 합니다. 하지만 스프링은 정적 포인트컷과 동적 포인트컷을 모두 사용할 수 있는 추상 클래스를 제공하기 때문에 우리가 직접 커스텀 포인트컷을 구현해야 할 필요는 없습니다.

 

Spring 4.x 버전에서는 8개의 포인트컷 인터페이스 구현체를 제공합니다. 2개의 추상 클래스는 정적 또는 동적 포인트컷을 생성할 때 사용할 수 있는 편의 클래스(convenience class)이고, 나머지 6개의 클래스는 각기 개별적인 기능을 사용할 수 있는 구상 클래스(concrete class)입니다.

 

다음은 2개의 편의 클래스 이름과 패키지명을 보겠습니다.

 

  • StaticMethodMatcherPointcut
    (org.springframework.aop.support)

  • DynamicMethodMatcherPointcut
    (org.springframework.aop.support)

정적 포인트컷을 생성하는 StaticMethodMatcherPointcut과 동적 포인트컷을 생성하는 DynamicMethodMatcherPointcut이 있습니다.

 

  • AnnotationMatchingPointcut
    (org.springframework.aop.support.annotation)

    클래스나 메서드에서 특정 Java annotation을 찾는 포인트컷 구현체

  • AspectJExpressionPointcut
    (org.springframework.aop.aspectj)

    AspectJ 위버를 사용해 AspectJ 구문으로 포인트컷 표현식을 평가하는 포인트컷 구현체

  • ComposablePointcut
    (org.springframework.aop.support)

    둘 이상의 포인트컷을 union()과 intersaction() 같은 작업을 통해 하나로 구성

  • ControlFlowPointcut
    (org.springframework.aop.support)

    다른 메서드의 제어 흐름에 포함된 모든 메서드에 대응되는 특수한 포인트컷.
    (다른 메서드의 호출 결과로부터 직간접적으로 호출되는 모든 메서드가 해당)

  • JdkRegexpMethodPointcut
    (org.springframework.aop.support)

    JDK 1.4 정규식으로 포인트컷을 정의하는 포인트컷

  • NameMatchMethodPointcut
    (org.springframework.aop.support)

    메서드 이름 목록에 메서드가 포함되는지 간단히 확인하는 포인트컷

 

 

DefaultPointcutAdvisor

Pointcut 구현체를 사용하려면 먼저 Advisor 인터페이스의 인스턴스나 좀 더 구체적으로 PointcutAdvisor 인터페이스의 인스턴스를 생성해야 합니다.

이전 글에서 다뤘다시피 Advisor는 어드바이스와 포인트컷의 결합에 어드바이스가 적용될 메서드와 어드바이스를 적용할 방식을 지정하는 스프링의 aspect입니다. 

 

Spring AOP에서 aspect는 Advisor 인터페이스를 구현한 클래스의 인스턴스로 아래의 두 가지 Advisor를 제공합니다.

 

  • PointcutAdvisor
  • IntroductionAdvisor

 

포인트컷을 사용하여 조인포인트에 적용할 어드바이스를 제어하는 모든 Advisor 구현체는 Pointcut Advisor 인터페이스를 구현합니다. 이 외에도 Spring은 Introduction을 특별한 종류의 어드바이스로 취급하므로 IntroductionAdvisor 인터페이스를 사용해 Introduction이 적용되는 클래스를 제어할 수 있습니다.

 

스프링에서는 여러 PointcutAdvisor 구현체를 제공하지만 이 글에서는 DefaultPointcutAdvisor만을 다룰 것입니다. DefaultPointcutAdvisor는 하나의 포인트컷을 하나의 어드바이스와 연결시키는 간단한 PointcutAdvisor입니다.

 

 

 

마치며...

어드바이스에 이어 포인트컷에 대해 알아봤습니다. Spring AOP가 가진 개념이 엄청 깊고 복잡하다는 것을 느꼈을텐데요.

다음 포스트에 계속 이어서 포인트컷 구현체를 어떻게 사용할 수 있는지를 다뤄보도록 하겠습니다.

 

 

 

참고: Spring AOP APIs
(https://docs.spring.io/spring-framework/docs/2.0.x/reference/aop-api.html)

반응형

Tistory Comments 0