항해99

스프링5 프로그래밍 입문 - ch7. AOP 프로그래밍 - (3) 프록시 생성 방식

자몽포도 2023. 3. 25. 18:58

초보 웹 개발자를 위한 스프링 5 프로그래밍 입문을 더 잘 이해하기 위해 정리하기 위한 포스팅입니다.

 

목차

1. 프록시 생성 방식

2. execution 명시자 표현식

3. Advice 적용 순서 @Order


1. 프록시 생성 방식

이전 포스팅(스프링5 프로그래밍 입문 - ch7. AOP 프로그래밍 - (2) AOP)에서 만들어 뒀던 코드를 조금 변경해보자. 타입을 인터페이스에서 구현체로 변경해보자.

그리고 실행해보자.

 

다음과 같은 에러가 발생한다.

에러를 해석해보자. calculator 라는 빈의 타입은 RecCalculator 를 기대했다. 하지만 실제 타입은 Proxy18이었다.

여기서 기대한 대상은 개발자다. 개발자가 타입을 RecCalculator로 기대했으니 말이다. 그런데 실제 타입은 Proxy18이었다는 것은 무슨 의미인고 하냐면 아래와 같은 계층 구조를 가진다는 의미이다.

 

이미 Proxy18 객체로 타입이 지정됐기 떄문에 RecCalculator 가 될 수 없다. 스프링은 AOP를 위한 프록시 객체를 생성할 때 실제 생성할 빈 객체가 인터페이스를 상속하면 인터페이스를 이용해서 프록시를 생성한다.

 

만약 빈 객체가 인터페이스를 상속할 때 인터페이스가 아닌 클래스를 이용해서 프록시를 생성하고 싶은 아래와 같이 설정하면 된다.

인터페이스 말고 클래스를 이용해서 프록시를 생성하는 방법


2. execution 명시자 표현식

execution 명시자는 Advice를 적용할 메서드를 지정할 때 사용한다. 기본 형식은 다음과 같다.

execution(수식어패턴? 리턴타입패턴 클래스이름패턴? 메서드이름패턴(파라미터패턴))

 

 

물음표는 생략이 가능함을 의미한다. 각 패턴은 '*'을 이용해서 모든 값을 표현할 수 있다. 또한, '..'(점 두 개)을 이용하여 0개 이상이라는 의미를 표현할 수 있다. 아래는 명시자 예시이다.

설명
execution(public void set*(..)) 리턴 타입 void, 메서드이름이 set으로 시작하고, 파라미터가 0개 이상인 메서드 호출, 파라미터 부분에 '..'을 사용하여 파라미터가 0개 이상인 것을 표현했다.
execution(* chap07.*.*()) chap07 패키지의 타입에 속한 파라미터가 없는 모든 메서드 호출
execution(* chap07..*.*(..)) chap07 패키지 및 하위 패키지에 있는, 파라미터가 0개 이상인 메서드 호출
execution(Long chap07.Calculator.factorial(..)) 리턴 타입이 Long인 Calculator 클래스의 파라미터는 0개 이상 factorial 메서드 호출
execution(* read*(integer, ..)) 메서드 이름이 read로 시작하고, 첫 번째 파라미터 타입이 integer이며, 한 개 이상의 파라미터를 갖는 메서드 호출

3. Advice 적용 순서 @Order

아래와 같이 한 Pointcut에 여러 Advice를 적용할 수도 있다. 

 

CacheAspect의 구현 코드는 아래와 같다. 참고로 CacheAspect는 우리가 흔히 아는 캐시 기능을 적용한 Aspect이다.

 

스프링 프레임워크 5.3.25 버전 기준으로는 스프링 빈이 등록된 순서로 Aspect가 작동한다. 하지만 프로젝트가 커진다면 스프링에서 어떤 순서로 빈을 등록할지 개발자가 예측하기 힘들다. Aspect 적용 순서는 @Order을 통해 정할 수 있다.

 

 

Proceed() 를 호출하지 않는다면?

추가로 위 소스 코드에서 if 분기점에 있는 빨간색 박스 부분을 살펴보자. 여기서는 joinPoint에 proceed() 를 호출하지 않는다. 이럴 경우 프록시 객체에서 다음 프록시로 넘어가지 않는다. CacheAspect에서 로직이 종료된다. 

이제 확인해보자. 실제 Calculator 구현체에서 로직을 실행한다면 아래 출력문이 실행되어야 한다.

 

아래는 실행한 로직과 결과이다.

 

4번 실행했지만 2번만 실제 비즈니스 로직에 접근하는 것을 알 수 있다. 이로써 proceed() 가 호출되어야 다음 프록시 혹은 비즈니스 로직으로 넘어간다는 것을 확인할 수 있다.