오봉이와 함께하는 개발 블로그

스프링 핵심 원리 - 고급편 > 직접 만든 포인트컷 본문

BE/Spring

스프링 핵심 원리 - 고급편 > 직접 만든 포인트컷

오봉봉이 2024. 10. 1. 23:27
728x90

스프링 핵심 원리 - 고급편 > 직접 만든 포인트컷

이전 글에는 save(), find() 모두 어드바이스 로직이 적용되었다.
이번에는 save()메서드에만 어드바이스 로직이 적용되도록 해보자. 이전에 했듯, 어드바이스에 로직을 추가하여 메서드 이름을 보고 분기를 통해 수행할 수도 있지만, 이런 기능을 특화하여 제공하는 것이 포인트컷이다.

해당 요구사항을 만족하도록 포인트컷을 직접 구현해보자.

 public interface Pointcut {
     ClassFilter getClassFilter();
     MethodMatcher getMethodMatcher();
}
 public interface ClassFilter {
     boolean matches(Class<?> clazz);
}
 public interface MethodMatcher {
     boolean matches(Method method, Class<?> targetClass);
     //..
}

포인트컷은 크게 ClassFilterMethodMatcher둘로 이루어진다.
이름 그대로 하나는 클래스가 맞는지, 하나는 메서드가 맞는지 확인할 때 사용한다. 둘다 true 로 반환해야 어드바이스를 적용할 수 있다.
일반적으로 스프링이 이미 만들어둔 구현체를 사용하지만 개념 학습 차원에서 간단히 직접 구현해보자.

@Slf4j
public class AdvisorTest {

  @Test
  void advisorTest2() {
    ServiceImpl target = new ServiceImpl();
    ProxyFactory proxyFactory = new ProxyFactory(target);
    DefaultPointcutAdvisor advisor = new DefaultPointcutAdvisor(new MyPointcut(), new TimeAdvice());
    proxyFactory.addAdvisor(advisor);
    ServiceInterface proxy = (ServiceInterface) proxyFactory.getProxy();

    proxy.save();
    proxy.find();
  }

  static class MyPointcut implements Pointcut {

    @Override
    public ClassFilter getClassFilter() {
      return ClassFilter.TRUE;
    }

    @Override
    public MethodMatcher getMethodMatcher() {
      return new MyMethodMatcher();
    }
  }

  static class MyMethodMatcher implements MethodMatcher {
    private static final String MATCH_NAME = "save";

    @Override
    public boolean matches(Method method, Class<?> targetClass) {
      boolean result = method.getName().equals(MATCH_NAME);
      log.info("Call Pointcut Method = {}, targetClass = {}", method.getName(), targetClass);
      log.info("Pointcut Result = {}", result);
      return result;
    }

    @Override
    public boolean isRuntime() {
      return false;
    }

    @Override
    public boolean matches(Method method, Class<?> targetClass, Object... args) {
      throw new UnsupportedOperationException();
    }
  }
}

MyPointcut

  • 직접 구현한 포인트컷이다.
  • Pointcut 인터페이스를 구현한다.
  • 현재 메서드 기준으로 로직을 적용하면 된다.
  • 클래스 필터는 항상 true 를 반환하도록 했고, 메서드 비교 기능은 MyMethodMatcher를 사용한다.

MyMethodMatcher

  • 직접 구현한 MethodMatcher이다.
  • MethodMatcher 인터페이스를 구현한다.
  • matches()
    • 이 메서드에 method, targetClass정보가 넘어온다.
    • 이 정보로 어드바이스를 적용할지 적용하지 않을지 판단할 수 있다.
    • 여기서는 메서드 이름이 "save"인 경우에 true를 반환하도록 판단 로직을 적용했다.
  • isRuntime() , matches(... args)
    • isRuntime()의 반환 값이 true이면 matches(... args) 메서드가 대신 호출된다.
    • 동적으로 넘어오는 매개변수를 판단 로직으로 사용할 수 있다.
    • isRuntime()false인 경우 클래스의 정적 정보만 사용하기 때문에 스프링이 내부에서 캐싱을 통해 성능 향상이 가능하지만, isRuntime()true인 경우 매개변수가 동적으로 변경된다고 가정하기 때문에 캐싱을 하지 않는다.
      • 매개변수가 동적으로 변경되기 때문에 캐싱을 할 필요가 없는 것이고, 캐싱을 하더라도 어떻게 보면 미미한 성능 향상이거나, 메모리를 더 많이 사용하여 오히려 성능저하가 발생할 수 있다.
    • 크게 중요한 부분은 아니니 참고만 하고 넘어가자

실행 결과

Call Pointcut Method = save, targetClass = class hello.proxy.common.service.ServiceImpl
Pointcut Result = true
Time Proxy Execute
call save
Time Proxy Shutdown resultTime = 0
Call Pointcut Method = find, targetClass = class hello.proxy.common.service.ServiceImpl
Pointcut Result = false
call find

실행 결과를 보면 기대한 것과 같이 save()를 호출할 때는 어드바이스가 적용되지만, find()를 호출할 때는 어드바이스가 적용되지 않는다.

그림으로 정리

image

  1. 클라이언트가 프록시의 save()를 호출
  2. 포인트컷에다가 Service 클래스의 save() 메서드에 어드바이스를 적용해도 될지 물어본다.
  3. MATCH_NAME에 따라 true를 반환한다.
  4. 어드바이스를 호출하여 부가 기능을 적용한다.
  5. 이후 실제 save()를 호출한다.

image

  1. 클라이언트가 프록시의 find()를 호출
  2. 포인트컷에다가 Service 클래스의 find() 메서드에 어드바이스를 적용해도 될지 물어본다.
  3. MATCH_NAME에 따라 false를 반환한다.
  4. 어드바이스를 호출하지도, 부가 기능을 적용하지도 않는다.
  5. 이후 실제 find()를 호출한다.

 

출처: 김영한 지식공유자의 스프링 핵심 원리 고급편

728x90
Comments