maeng0830
뇌 채우기 운동
maeng0830
전체 방문자
오늘
어제
  • maeng0830-note (85)
    • 자바 (3)
    • 스프링 (39)
      • Core (21)
      • DB (16)
      • Security (2)
      • Test (0)
    • 자료구조 & 알고리즘 (19)
      • 자료구조 (12)
      • 알고리즘 (7)
    • 다른 개발 도구들 (4)
      • Git&Github (1)
      • Redis (3)
    • 프로젝트 (9)
      • Album (9)
    • CS (10)
      • 운영체제 (5)
      • 데이터베이스 (5)

블로그 메뉴

  • 홈
  • 태그
  • 방명록

공지사항

인기 글

태그

  • 트랜잭션
  • 자료구조
  • JPQL
  • JPA
  • spring security

최근 댓글

최근 글

티스토리

hELLO · Designed By 정상우.
maeng0830

뇌 채우기 운동

스프링/Core

스프링이 지원하는 동적 프록시 기술

2023. 10. 17. 00:17

스프링은 동적 프록시를 보다 편리하게 사용하기 위한 기술들을 제공하고 있다.

이번 글에서는 스프링이 지원하는 동적 프록시 기술을 알아볼텐데, 동적 프록시에 대한 기본 개념은 이전 글을 참고하자.

 

JDK 동적 프록시 또는 CGLIB를 통해 동적 프록시를 생성하고 사용할 때 몇 가지 불편한 점이 존재한다.

 

우선 동적 프록시를 생성하기 위한 기반이 인터페이스인지, 클래스인지에 따라 사용할 수 있는 동적 프록시 기술이 다르다는 것이다.

JDK 동적 프록시의 경우, 인터페이스 기반으로 동적 프록시를 생성할 수 있으며, CGLIB는 클래스를 기반으로 동적 프록시를 생성할 수 있다.

동적 프록시의 기반이 무엇이냐에 따라 동적 프록시 기술을 다르게 사용하는 것은 꽤나 번거로운 일이다.

 

또 다른 문제는 앞서 말한 문제와 연결된다.

JDK 동적 프록시는 InvocationHandler의 구현체로 동적 프록시의 로직을 구현한다. 그러나 CGLIB는 MethodInterceptor의 구현체를 통해 동적 프록시의 로직을 구현한다.

사용할 동적 프록시의 로직이 동일함에도, 사용하는 동적 프록시 기술에 따라 각각 구현하는 것은 비효율적이다.

 

스프링은 이러한 불편함을 해소하기 위해 프록시 팩토리(Proxy Factory)와 어드바이저(Advisor)라는 기술을 제공한다.

 

프록시 팩토리(Proxy Factory)

프록시 팩토리(Proxy Factory)는 동적 프록시 기술을 보다 편리하게 사용하기 위해 스프링이 제공하는 기술이다.

 

예제 코드를 보면서 프록시 팩토리가 어떤 기술이고, 어떻게 사용되는지 알아보자.

 

어드바이스(Advice)

어드바이스는 동적 프록시의 로직이 작성되는 클래스이다.

org.aopalliance.intercept.MethodInterceptor의 구현체로 어드바이스를 생성할 수 있다.

 

invoke()에는 동적 프록시의 로직이 작성된다.

invoke()는 파라미터로 MethodInvocation invocation을 가지는데, invocation 내부에는 다음 메서드를 호출하는 방법, 현재 프록시 인스턴스, 프록시가 호출할 대상의 정보, 호출할 메소드의 정보 등이 포함되어 있다.

 

따라서 어드바이스는 프록시가 호출할 대상을 필드로 가질 필요가 없으며,

invocation.proceed()를 통해 호출할 대상의 메소드를 실행할 때도 별다른 파라미터가 필요하지 않다. 

invocation 내부에 필요한 정보가 이미 포함되어 있기 때문이다.

// Advice에는 프록시가 제공할 로직이 작성된다.
// org.aopalliance.intercept.MethodInterceptor의 구현체로 Advice 생성할 수 있다.
@Slf4j
public class TimeAdvice implements MethodInterceptor {

	@Override
	public Object invoke(MethodInvocation invocation) throws Throwable {
		log.info("TimeProxy 실행");
		long startTime = System.currentTimeMillis();

		// 프록시가 호출할 target의 정보, 호출할 메소드의 정보 등은 이미 invocation에 포함되어 있다.
		Object result = invocation.proceed(); // target 인스턴스의 메서드를 실행

		long endTime = System.currentTimeMillis();

		long resultTime = endTime - startTime;
		log.info("TimeProxy 종료  resultTime={}", resultTime);

		return result;
	}
}

 

사용 예시

interfaceProxy(), concreteProxy() 내부에는 각각 인터페이스가 있는 구현 클래스, 인터페이스가 없는 구체 클래스에 대한 동적 프록시가 프록시 팩토리로 어떻게 생성되고 사용되는지 작성되어 있다.

 

interfaceProxy() 내부의 target은 ServiceInterface의 구현 클래스인 ServiceImpl의 참조 변수이다.

그리고 concreteProxy() 내부의 target은 인터페이스가 없는 구체 클래스인 ConcreteService의 참조 변수이다.

 

프록시 팩토리는 new ProxyFactory()를 통해 생성되는데, 이 때 프록시가 호출할 대상을 파라미터로 받는다.

만약 호출할 대상이 인터페이스가 있는 구현 클래스라면 JDK 동적 프록시를 통해 인터페이스 기반 프록시를 생성한다.

반대로 호출할 대상이 인터페이스가 없는 구체 클래스라면 CGLIB를 통해 클래스 기반 프록시를 생성한다.

 

따라서 interfaceProxy()의 경우, JDK 동적 프록시를 통해 인터페이스 기반 프록시가 생성되고,

concreteProxy()의 경우 CGLIB를 통해 클래스 기반 프록시가 생성된다.

 

ProxyFactory.addAdvice()를 통해 프록시 팩토리로 생성될 동적 프록시가 실행할 로직을 추가한다.

이 때 파라미터로 사용되는 것이 어드바이스이다.

 

ProxyFactory.getProxy()를 통해 동적 프록시를 생성한다.

 

생성된 동적 프록시의 참조 변수인 proxy를 통해 사용할 메소드를 호출하면,

프록시 팩토리에 주입된 어드바이스의 invoke()가 호출되어 프록시의 로직이 실행된다.

 

어드바이스의 invoke() 내부에서 invocation.proceed()를 통해 프록시가 호출할 대상의 메소드를 호출할 때 아무런 파라미터가 필요하지 않는 이유는 프록시 팩토리가 생성될 때 이미 생성자를 통해 호출할 대상의 정보를 주입 받고, proxy를 통해 메소드를 호출할 때 해당 메소드의 정보가 invocation에 포함되기 때문이다.

 

proxyTargetClass()의 내부에는 인터페이스가 있는 구현 클래스를 프록시의 호출 대상으로 지정한 경우에도,

CGLIB를 통해 클래스에 기반한 동적 프록시를 생성하는 방법이 작성되어 있다.

@Slf4j
public class ProxyFactoryTest {

	@DisplayName("인터페이스가 있으면, JDK 동적 프록시를 통해 인터페이스 기반 프록시를 생성한다.")
	@Test
	void interfaceProxy() {
		// 인터페이스가 있는 구현 클래스
		ServiceImpl target = new ServiceImpl();

		// 프록시 팩토리 생성
		// 프록시팩토리에는 프록시가 호출할 target, target이 구현한 인터페이스 정보 등이 포함된다.
		// target이 인터페이스가 있는 구현 클래스라면 JDK 동적 프록시를 통해 인터페이스 기반 프록시를 생성한다.
		// target이 인터페이스가 없는 구체 클래스라면 CGLIB를 통해 클래스 기반 프록시를 생성한다.
		ProxyFactory proxyFactory = new ProxyFactory(target);

		// 프록시팩토리로 생성될 동적 프록시가 실행할 로직(=Advice)를 추가
		proxyFactory.addAdvice(new TimeAdvice());

		// 동적 프록시 생성
		ServiceInterface proxy = (ServiceInterface) proxyFactory.getProxy();

		log.info("targetClass={}", target.getClass());
		// targetClass=class hello.proxy.common.service.ServiceImpl
		log.info("proxyClass={}", proxy.getClass());
		// proxyClass=class com.sun.proxy.$Proxy13

		proxy.save();
		// TimeAdvice - TimeProxy 실행
		// ServiceImpl - save 호출
		// TimeAdvice - TimeProxy 종료  resultTime=0

		// 프록시팩토리로 생성된 동적 프록시인지 확인한다.
		assertThat(AopUtils.isAopProxy(proxy)).isTrue();
		// 프록시팩토리로 생성된 동적 프록시이고, JDK 동적 프록시인지 확인한다.
		assertThat(AopUtils.isJdkDynamicProxy(proxy)).isTrue();
		// 프록시팩토리로 생성된 동적 프록시기오, CGLIB 동적 프록시인지 확인한다.
		assertThat(AopUtils.isCglibProxy(proxy)).isFalse();
	}

	@DisplayName("인터페이스가 없으면, CGLIB를 통해 클래스 기반 프록시를 생성한다.")
	@Test
	void concreteProxy() {
		// 인터페이스가 있는 구현 클래스
		ConcreteService target = new ConcreteService();

		// 프록시 팩토리 생성
		// 프록시팩토리에는 프록시가 호출할 target, target이 구현한 인터페이스 정보 등이 포함된다.
		// target이 인터페이스가 있는 구현 클래스라면 JDK 동적 프록시를 통해 인터페이스 기반 프록시를 생성한다.
		// target이 인터페이스가 없는 구체 클래스라면 CGLIB를 통해 클래스 기반 프록시를 생성한다.
		ProxyFactory proxyFactory = new ProxyFactory(target);

		// 프록시팩토리로 생성될 동적 프록시가 실행할 로직(=Advice)를 추가
		proxyFactory.addAdvice(new TimeAdvice());

		// 동적 프록시 생성
		ConcreteService proxy = (ConcreteService) proxyFactory.getProxy();

		log.info("targetClass={}", target.getClass());
		// targetClass=class hello.proxy.common.service.ConcreteService
		log.info("proxyClass={}", proxy.getClass());
		// proxyClass=class hello.proxy.common.service.ConcreteService$$EnhancerBySpringCGLIB$$9f45c72b

		proxy.call();
		// TimeAdvice - TimeProxy 실행
		// ConcreteService - ConcreteService 호출
		// TimeAdvice - TimeProxy 종료  resultTime=7

		// 프록시팩토리로 생성된 동적 프록시인지 확인한다.
		assertThat(AopUtils.isAopProxy(proxy)).isTrue();
		// 프록시팩토리로 생성된 동적 프록시이고, JDK 동적 프록시인지 확인한다.
		assertThat(AopUtils.isJdkDynamicProxy(proxy)).isFalse();
		// 프록시팩토리로 생성된 동적 프록시기오, CGLIB 동적 프록시인지 확인한다.
		assertThat(AopUtils.isCglibProxy(proxy)).isTrue();
	}

	@DisplayName("ProxyTargetClass 옵션을 사용하면, 인터페이스가 있어도 CGLIB를 통해 클래스 기반 프록시를 생성한다.")
	@Test
	void proxyTargetClass() {
		// 인터페이스가 있는 구현 클래스
		ServiceImpl target = new ServiceImpl();

		// 프록시 팩토리 생성
		// 프록시팩토리에는 프록시가 호출할 target, target이 구현한 인터페이스 정보 등이 포함된다.
		// target이 인터페이스가 있는 구현 클래스라면 JDK 동적 프록시를 통해 인터페이스 기반 프록시를 생성한다.
		// target이 인터페이스가 없는 구체 클래스라면 CGLIB를 통해 클래스 기반 프록시를 생성한다.
		ProxyFactory proxyFactory = new ProxyFactory(target);

		// 인터페이스가 있어도, target 클래스를 상속하여 동적 프록시를 생성한다. 즉 CGLIB 동적 프록시를 생성한다.
		proxyFactory.setProxyTargetClass(true);

		// 프록시팩토리로 생성될 동적 프록시가 실행할 로직(=Advice)를 추가
		proxyFactory.addAdvice(new TimeAdvice());

		// 동적 프록시 생성
		ServiceInterface proxy = (ServiceInterface) proxyFactory.getProxy();

		log.info("targetClass={}", target.getClass());
		// targetClass=class hello.proxy.common.service.ServiceImpl
		log.info("proxyClass={}", proxy.getClass());
		// proxyClass=class hello.proxy.common.service.ServiceImpl$$EnhancerBySpringCGLIB$$4d673546

		proxy.save();
		// TimeAdvice - TimeProxy 실행
		// ServiceImpl - save 호출
		// TimeAdvice - TimeProxy 종료  resultTime=0

		// 프록시팩토리로 생성된 동적 프록시인지 확인한다.
		assertThat(AopUtils.isAopProxy(proxy)).isTrue();
		// 프록시팩토리로 생성된 동적 프록시이고, JDK 동적 프록시인지 확인한다.
		assertThat(AopUtils.isJdkDynamicProxy(proxy)).isFalse();
		// 프록시팩토리로 생성된 동적 프록시이고, CGLIB 동적 프록시인지 확인한다.
		assertThat(AopUtils.isCglibProxy(proxy)).isTrue();
	}
}

 

어드바이저(Advisor)

어드바이저를 이해하기 위해서는 포인트컷과 어드바이스에 대해서도 함께 이해해야한다.

  • 포인트컷(Pointcut)은 어디에 프록시 로직을 적용할지, 어디에 적용하지 않을지를 판단하기 위한 필터링 로직이다. 주로 클래스와 메서드 이름으로 필터링한다.
  • 어드바이스(Advice)는 프록시 로직이다.
  • 어드바이저(Advisor)는 하나의 포인트컷과 하나의 어드바이스를 갖고 있는 것을 의미한다.

어드바이저의 구성 요소를 포인트컷과 어드바이스로 구분한 것은 역할과 책임을 명확하게 분리하기 위해서이다.

포인트컷은 프록시 로직 적용 여부를 판단하는 필터링 기능만 담당한다.

그리고 어드바이스는 프록시 로직 자체만 담당한다.

 

이제 예제 코드를 통해 포인트컷, 어드바이스, 그리고 어드바이저가 프록시 팩토리에 어떻게 사용되는지 알아보자.

 

예시 코드에는 위에서 사용했던 어드바이스인 TimeAdvice, ServiceInterface의 구현 클래스인 ServiceImpl이 사용된다.

 

어드바이저-직접 구현한 포인트컷

우선 포인트컷을 직접 구현해서 사용하는 예제 코드를 살펴보자.

 

직접 구현한 포인트컷

포인트컷은 Pointcut의 구현체를 통해 직접 구현할 수 있다.

MyPointcut은 Pointcut을 구현한 포인트컷이다.

 

MyPointcut를 살펴보자.

포인트컷은 크게 ClassFilter와 MethodMatcher로 구성된다. 

ClassFilter는 프록시 로직을 적용할 클래스가 맞는지를 판단하며, MethodMatcher는 프록시 로직을 적용할 메소드가 맞는지를 판단한다. 둘 다 true를 반환해야 프록시 로직이 적용된다.

 

다음으로 MyMethodMatcher를 살펴보자.

MyMethodMatcher는 메소드에 대한 필터링을 위한 MethodMatcher의 구현체이다.

matches() 내부에 메소드에 대한 필터링 로직이 작성된다.

파라미터인 Method method에는 호출할 메소드의 정보가 포함되어있다.

method에서 추출한 메소드명이 "save"인 경우 true를 반환하도록 로직을 작성했다.

 

다시 MyPointcut을 살펴보자.

getClassFilter의 반환 값을 ClassFilter.TRUE로 지정하여 기본적으로 모든 클래스에 대해 true를 반환하도록 지정했다.

그리고 getMethodMatcher의 반환값을 new MyMethodMathcer()로 지정하여, MyMethodMatcher에 작성된 로직에 따라 메소드를 필터링 하도록 설정하였다.

// 직접 구현한 포인트컷
class MyPointcut implements Pointcut {

    // 클래스에 대한 필터링
    @Override
    public ClassFilter getClassFilter() {
        return ClassFilter.TRUE;
    }

    // 메소드에 대한 필터링
    // 직접 구현한 MethodMatcher의 구현체를 반환한다.
    @Override
    public MethodMatcher getMethodMatcher() {
        return new MyMethodMatcher();
    }
}


// 메소드 필터링을 위한 MethodMatcher의 구현체
class MyMethodMatcher implements MethodMatcher {

    private String matchName = "save";

    // true를 반환할 경우, 어드바이스(프록시가 제공하는 기능)가 적용된다.
    @Override
    public boolean matches(Method method, Class<?> targetClass) {
        boolean result = method.getName().equals(matchName);
        log.info("포인트컷 호출 method={} targetClass={}", method.getName(), targetClass);
        log.info("포인트컷 결과 result={}", result);
        return result;
    }

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

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

 

사용 예시

advisorTest2() 내부에는 직접 구현한 포인트컷이 프록시 팩토리와 함께 어떻게 사용되는지를 보여준다.

 

어드바이저의 대표적인 타입은 DefaultPointcutAdvisor이다.

new DefaultPointcutAdvisor(new MyPointcut(), new TimeAdvice())는 포인트컷인 MyPointcut과 어드바이스인 TimeAdvice로 구성되는 어드바이저를 생성하는 코드이다.

 

그리고 ProxyFactory.addAdvisor()의 파라미터로 생성한 어드바이저를 사용하여 프록시 팩토리의 어드바이저를 지정한다.

 

프록시 팩토리로 생성된 proxy를 통해 메소드를 호출하는 코드를 살펴보자.

proxy.save()의 경우 프록시가 호출할 대상의 메소드가 "save"이다. ClassFilter, MethodMatcher 모두 true를 반환하게 된다.

그러므로 프록시 로직이 적용된다. 즉 TimeAdvice.invoke()이 호출되는 것이다.

 

반면에 proxy.find()의 경우 프록시가 호출할 대상의 메소드가 "find"이다. MethodMatcher가 false를 반환하게 된다.

따라서 프록시 로직이 적용되지 않고, 프록시가 호출할 대상의 find()가 바로 호출된다.

@DisplayName("직접 만든 포인트컷 적용")
@Test
void advisorTest2() {
    ServiceInterface target = new ServiceImpl();
    ProxyFactory proxyFactory = new ProxyFactory(target);

    // 어드바이저 생성
    // 어드바이저는 하나의 포인트컷 + 하나의 어드바이스
    // 직접 구현한 포인트컷을 적용한다.
    DefaultPointcutAdvisor advisor = new DefaultPointcutAdvisor(new MyPointcut(),
            new TimeAdvice());

    // 프록시팩토리에 적용할 어드바이저를 지정한다.
    // 어드바이저는 포인트컷과 어드바이스를 모두 갖고 있기 때문에, 어디에 어떤 부가기능을 적용해야할지 알 수 있다.
    // 프록시팩토리를 생성할 때 어드바이저는 필수이다.
    // 프록시팩토리에 어드바이스만 지정한다면, 내부적으로 Pointcut.TRUE의 어드바이저가 생성된다.
    proxyFactory.addAdvisor(advisor);

    ServiceInterface proxy = (ServiceInterface) proxyFactory.getProxy();

    proxy.save();
    // 포인트컷 호출 method=save targetClass=class hello.proxy.common.service.ServiceImpl
    // 포인트컷 결과 result=true
    // TimeAdvice - TimeProxy 실행
    // ServiceImpl - save 호출
    // TimeAdvice - TimeProxy 종료  resultTime=0
    proxy.find();
    // 포인트컷 호출 method=find targetClass=class hello.proxy.common.service.ServiceImpl
    // 포인트컷 결과 result=false
    // ServiceImpl - find 호출
}

 

어드바이저-스프링이 제공하는 포인트컷

advisorTest3()에는 스프링이 제공하는 포인트컷을 사용한 예시 코드가 작성되어 있다.

 

직접 구현한 포인트컷을 사용한 advisorTest2()와의 차이점은 스프링이 제공하는 포인트컷 중 하나인 NameMatchMethodPointcut을 사용한 점이다.

 

스프링이 제공하는 포인트컷은 아래와 같이 다양하다.

  • NameMatchMethodPointcut은 메서드 이름을 기반으로 매칭한다. PatternMatchUtils를 사용한다.
  • JdkRegexpMehtodPointcut은 JDK 정규 표현식을 기반으로 포인트컷을 매칭한다.
  • TruePointcut은 항상 참을 반환한다.
  • AnnotationMatchingPointcut은 애노테이션을 기반으로 매칭한다.
  • AspectJExpressionPointcut은 aspectJ 표현식으로 매칭한다.

이처럼 다양한 포인트컷이 제공되지만, 사실 중요한 것은 AspectJExpressionPointcut 이다.

AspectJExpressionPointcut에 대해서는 추후에 AOP에 대한 게시글을 올릴 때 자세히 알아보고자 한다.

@DisplayName("스프링이 제공하는 포인트컷 적용")
@Test
void advisorTest3() {
    ServiceInterface target = new ServiceImpl();
    ProxyFactory proxyFactory = new ProxyFactory(target);

    // 스프링이 제공하는 포인트컷
    // 메소드명이 "save"일 때만 어드바이스 실행
    NameMatchMethodPointcut pointcut = new NameMatchMethodPointcut();
    pointcut.setMappedNames("save");

    // 어드바이저 생성
    // 어드바이저는 하나의 포인트컷 + 하나의 어드바이스
    // 스프링이 제공하는 포인트컷 적용
    DefaultPointcutAdvisor advisor = new DefaultPointcutAdvisor(pointcut, new TimeAdvice());

    // 프록시팩토리에 적용할 어드바이저를 지정한다.
    // 어드바이저는 포인트컷과 어드바이스를 모두 갖고 있기 때문에, 어디에 어떤 부가기능을 적용해야할지 알 수 있다.
    // 프록시팩토리를 생성할 때 어드바이저는 필수이다.
    // 프록시팩토리에 어드바이스만 지정한다면, 내부적으로 Pointcut.TRUE의 어드바이저가 생성된다.
    proxyFactory.addAdvisor(advisor);

    ServiceInterface proxy = (ServiceInterface) proxyFactory.getProxy();

    proxy.save();
    // TimeAdvice - TimeProxy 실행
    // ServiceImpl - save 호출
    // TimeAdvice - TimeProxy 종료  resultTime=0
    proxy.find();
    // ServiceImpl - find 호출
}

 

어드바이저-다수의 어드바이저

위의 예시 코드들에서는 하나의 프록시가 하나의 어드바이저만 사용했다.

만약 하나의 프록시가 다수의 어드바이저를 사용하려면 어떻게 해야할까?

즉 하나의 프록시가 호출할 대상에게 다수의 프록시 로직을 제공하려면 어떻게 해야할까?

 

스프링은 하나의 프록시에 다수의 어드바이저를 적용할 수 있도록 구현해두었다.

 

예시 코드를 살펴보자.

 

어드바이스인 Advice1과 Advice2를 사용하여 어드바이저인 advisor1과 advisor2를 생성하였다.

 

ServiceImpl 인스턴스를 호출할 대상으로 갖는 프록시 팩토리 인스턴스 proxyFactory를 생성하였다.

 

그리고 proxyFactory.addAdvisor(advisor2), proxyFactory.addAdvisor(advisor2)를 통해 proxyFactory에 어드바이저를 2개 지정하였다.

이처럼 프록시 팩토리에 다수의 어드바이저를 지정하면, 프록시 팩토리로 생성되는 동적 프록시는 호출할 대상에게 다수의 프록시 로직을 제공할 수 있다.

 

proxy.save()를 호출할 때 어떤 과정이 진행되는지 알아보자.

  1. 첫 번째로 추가한 어드바이저의 어드바이스인 advice2.invoke()가 호출된다.
  2. advice2.invoke() 내부의 log.info("advice2 호출")이 실행된다.
  3. advice2.invoke() 내부의 invocation.proceed()는 두 번째로 추가한 어드바이저의 어드바이스인 advice1.invoke()를 호출한다.
  4. advice1.invoke() 내부의 log.info("advice1 호출")이 실행된다.
  5. advice1.invoke() 내부의 invocation.proceed()는 proxy의 호출 대상인 target의 save()를 호출한다.
  6. target.save()가 실행되고 종료된다.
  7. advice1.invoke() 내부의 나머지 로직이 수행되고 종료된다.
  8. advice2.invoke() 내부의 나머지 로직이 수행되고 종료된다.

즉 한 프록시에 다수의 어드바이저가 설정되어 있다면,

마지막 어드바이저를 제외한 어드바이저들은 invocation.proceed()를 통해 다음 어드바이저의 invoke()를 호출한다.

그리고 마지막 어드바이저는 프록시가 호출할 대상의 메소드를 호출한다.

public class MultiAdvisorTest {

	...
    
	@DisplayName("하나의 프록시를 통해 다수의 어드바이저 적용")
	@Test
	void multiAdvisorTest2() {
		// client -> proxy -> advisor2 -> advisor1 -> target

		// 어드바이저 생성
		DefaultPointcutAdvisor advisor1 = new DefaultPointcutAdvisor(Pointcut.TRUE, new Advice1());
		DefaultPointcutAdvisor advisor2 = new DefaultPointcutAdvisor(Pointcut.TRUE, new Advice2());

		// 프록시 생성
		ServiceInterface target = new ServiceImpl();
		ProxyFactory proxyFactory = new ProxyFactory(target);
		proxyFactory.addAdvisor(advisor2);
		proxyFactory.addAdvisor(advisor1);
		ServiceInterface proxy = (ServiceInterface) proxyFactory.getProxy();

		// 실행
		proxy.save();
		// Advice2 - advice2 호출
		// Advice1 - advice1 호출
		// ServiceImpl - save 호출
		// Advice1 - advice1 종료
		// Advice2 - advice2 종료
	}

	@Slf4j
	static class Advice1 implements MethodInterceptor {

		@Override
		public Object invoke(MethodInvocation invocation) throws Throwable {
			log.info("advice1 호출");
			invocation.proceed();
			log.info("advice1 종료");
			return null;
		}
	}

	@Slf4j
	static class Advice2 implements MethodInterceptor {

		@Override
		public Object invoke(MethodInvocation invocation) throws Throwable {
			log.info("advice2 호출");
			invocation.proceed();
			log.info("advice2 종료");
			return null;
		}
	}
}

 

    '스프링/Core' 카테고리의 다른 글
    • @Aspect
    • 동적 프록시를 위한 빈 후처리기
    • 동적 프록시 기술
    • 디자인 패턴-프록시, 데코레이터 패턴
    maeng0830
    maeng0830

    티스토리툴바