[토비의 스프링] Week9(6.4~6.5)

7 분 소요

토비의 스프링 3.1 Chapter 6.4 ~ 6.5

Chapater 6.4 스프링의 프록시 팩토리 빈

ProxyFactoryBean

:스프링이 제공하는 프록시 오브젝트를 생성해주는 기술을 추상화한 팩토리 빈.

  • 프록시를 생성하는 작업만 담당 (부가기능은 별도의 Bean에 둔다)
  • 부가기능은 MethodInterceptor 인터페이스를 구현하여 만든다.

MethodInterceptor의 invoke() 메서드는 ProxyFactoryBean으로부터 target 객체에 대한 정보까지 함께 제공받아 target이 다른 여러 프록시에서 함께 사용할 수 있다.

...
@Test
public void simpleProxy(){
    // JDK 다이내믹 프록시 생성
    Hello proxiedHello = (Hello)Proxy.newProxyInstance(
      getClass().getClassLoader(),
      new Class[]{Hello.class},
      new UppercaseHandler(new HelloTarget()));
}

@Test
public void proxyFactoryBean(){
  ProxyFactoryBean pfBean = new ProxyFactoryBean();
  pfBean.setTarget(new HelloTarget()); // 타깃 설정
  pfBean.addAdvice(new UppercaseAdvice()); // 부가 기능을 담은 어드바이스를 추가

  Hello proxiedHello = (Hello) pfBean.getObject(); // FactoryBean이므로 getObject()로 생성된 프록시를 가져온다.

  ...
}

static class UpperAdvice implements MethodInterceptor{
  public Object invoke(MethodInvocation invocation) throws Throwable{
    // 메서드 실행 시 target obj를 저달할 필요 없음
    // MethodInvocation에 target obj 정보가 들어 있기 때문이다.
    // proceed()메서드를 실행하면 target obj의 메서드를 내부적으로 실행시켜 준다.
    String ret = (String)invocation.proceed(); 
    return ret.toUpperCase(); // 부가기능적용
  }
}

  • MethodInterceptor는 Advice 인터페이스를 상속하는 서브인터페이스.
  • 인터페이스 타입을 제공하지 않아도 ProxyFactoryBean이 인터페이스 자동검출 기능을 사용해 target이 구현하고 있는 인터페이스 정보를 알아낸다.

기존 JDK 다이내믹 프록시를 이용한 방식과의 차이점

1) 기존 JDK 다이내믹 프록시를 이용한 방식
InvocationHandler가 부가기능과 메서드 선정 알고리즘을 DI받아 target에 실행을 위임한다.
-> 부가기능을 가진 InvocationHandler가 target과 메서드 선정 알고리즘 코드에 의존함.
->target이 다르거나 메서드 선정 방식이 다르면 해당 InvocationHandler는 여러 프록시에서 공유가 불가능함.
-> 그래서 부가기능이 target obj마다 새로 만들어지는 문제가 있다.

2) 스프링의 ProxyFactoryBean
부가기능을 제공하는 부분과 메서드 선정 알고리즘 코드를 InvocationHandler로부터 분리한다.
부가기능을 제공하는 오브젝트를 어드바이스라고 하고, 메서드 선정 알고리즘을 담은 오브젝트는 포인트컷이라 한다.
-> 어드바이스와 포인트컷은 프록시에 DI로 주입되어 사용
-> 어드바이스는 target에 직접 의존하지 않도록 템플릿 구조로 설계되어 있음(target 정보 상태값을 가지고 있지 않다)
-> 어드바이스나 포인트컷을 싱글톤 빈으로 만들어 둘 수 있어 재사용이 가능하다.

작동 방식

1) 프록시는 클라이언트로부터 요청을 받으면 포인트컷에 부가기능을 부여할 메서드인지를 확인해달라고 요청한다.
2) 포인트컷으로부터 부가기능을 적용할 대상 메서드인지 확인받으면 MethodInterceptor 타입의 어드바이스를 호출한다.
3) 어드바이스에서 target 메서드의 호출이 필요하면 proceed()메서드를 호출한다.
4) proceed()메서드 수행하는 부분은 target obj에 위임되어 실행된다.

어드바이저

: 포인트컷과 어드바이스를 묶은 오브젝트.
ProxyFactoryBean에는 여러 개의 어드바이스와 포인트컷이 추가될 수 있기 때문에 포인트컷과 어드바이스를 따로 등록하면 묶어줄 어드바이스와 포인트컷이 무엇인지 알기 어렵다. 그래서 이 둘을 묶어주는 어드바이저 타입을 addAdvisor()메서드로 등록한다.

Chapter 6.5 스프링 AOP

DefaultAdvisorAutoProxyCreator: 빈 후처리기

  • BeanPostProcessor 인터페이스를 구현.
  • 어드바이저를 이용한 자동 프록시 생성기
  • ProxyFactoryBean을 등록하지 않아도 타깃 오브젝트에 자동으로 프록시가 적용되게 할 수 있음.
  • 적용 방법: 빈 후처리기를 빈으로 등록한다. (스프링은 빈 obj가 생길 때마다 후처리기로 보낸다)

빈 후처리기를 이용한 자동 프록시 생성 방법

1) 빈 obj를 만들 때마다 스프링은 후처리기에 빈을 보냄 2) 후처리기는 빈으로 등록된 모든 어드바이저 내의 포인트컷을 이용해 전달받은 빈이 프록시 적용 대상인지 확인
3) 프록시 적용 대상이면 내장된 프록시 생성기에게 현재 빈에 대한 프록시를 만들게 함
4) 만들어진 프록시에 어드바이저 연결 5) 빈 후처리기는 프록시가 생성되면 프록시 오브젝트를 스프링 컨테이너에게 돌려줌. (빈 obj가 아닌 프록시 obj)

포인트컷 선정 기능

1) NameMatchMethodPointcut: 메서드를 선별하는 기능만 가짐. (필터 기능 x)
2) getClassFilter() 메서드: 오버라이드해서 특정 이름 패턴을 가진 메서드만 선정하게 함.

Class Filter를 적용한 포인트컷

...
public class NameMatchClassMethodPointcut extends NameMatchMethodPointcut{
  // 기존 단순한 NameMatchClassMethodPointcut에 필터를 덮어씌운다.
  public void setMappedClassName(String mappedClassName){
    this.setClassFilter(new SimpleClassFilter(mappedClassName));
  }

  static class SimpleClassFilter implements ClassFilter{
    String mappedName;

    private SimpleClassFilter(String mappedName){
      this.mappedName = mappedName;
    }

    public boolean matches(Class<?> clazz) {
      // 아래는 와일드카드가 들어간 문자열 비교를 지원하는 유틸리티 메서드.
      return PatternMatchUtils.simpleMatch(mappedName, clazz.getSimpleName());
    }
  }
}

포인트컷 표현식

: 일종의 표현식 언어를 사용해 포인트컷을 작성할 수 있도록 하는 방법. 굳이 클래스 필터와 메소드 매처를 일일이 구현할 필요가 없어진다.

적용 방법

  • AspectJExpressionPointcut 클래스를 사용 (그래서 AspectJ 표현식이라고도 함)

문법

  • 포인트컷 표현식은 포인트컷 지시자를 이용해 작성한다.
  • 메서드의 풀 시그니처를 문자열로 비교하는 개념이다.
    execution([접근제한자 패턴] 타입패턴 [타입패턴.]이름패턴 (타입패턴 ”..”, …) [throws 예외 패턴])
  • 접근제한자 패턴
  • 타입 패턴: 리턴 값의 타입
  • 타입패턴.: 패키지와 클래스 이름에 대한 패턴. 사용할 때는 .으로 연결한다.
  • 이름패턴: 메서드 이름 패턴
  • (타입패턴 ”..”, …): 파라미터의 타입 패턴을 순서대로 넣을 수 있다. 와일드카드를 이용할 수도 있다.
  • 예외 패턴: 예외 이름 패턴

Ex: public int minus(int a, int b) throws RuntimeException {return 0;}

  • 접근제한자 패턴: public
  • 타입 패턴: int
  • 타입패턴.: packagename.classname
  • 이름패턴: minus
  • (타입패턴 ”..”, …): (int, int)
  • 예외 패턴: throws java.lang.RuntimeException

활용

execution(* minus(int, int))
리턴 타입은 상관없이 minus라는 메서드 이름, 두 개의 int 파라미터를 가진 모든 메소드를 선정
execution(* minus(…))
minus라는 메서드 이름을 가진 모든 메서드 선정
execution(* *(..))
모든 메서드 선정

AOP: 애스펙트 지향 프로그래밍

댓글남기기