You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
따라서 AOP를 적용하려면 항상 프록시를 통해서 대상 객체(Target)을 호출해야 한다.
이렇게 해야 프록시에서 먼저 어드바이스를 호출하고, 이후에 대상 객체를 호출한다.
만약 프록시를 거치지 않고 대상 객체를 직접 호출하게 되면 AOP가 적용되지 않고, 어드바이스도 호출되지 않는다.
AOP를 적용하면 스프링은 대상 객체 대신에 프록시를 스프링 빈으로 등록한다. 따라서 스프링은 의존관계 주입시에 항상 프록시 객체를 주입한다. 프록시 객체가 주입되기 때문에 대상 객체를 직접 호출하는 문제는 일반적으로 발생하지 않는다. 하지만 대상 객체의 내부에서 메서드 호출이 발생하면 프록시를 거치지 않고 대상 객체를 직접 호출하는 문제가발생한다. 실무에서 반드시 한번은 만나서 고생하는 문제이기 때문에 꼭 이해하고 넘어가자.
예제를 통해서 내부 호출이 발생할 때 어떤 문제가 발생하는지 알아보자. 먼저 내부 호출이 발생하는 예제를 만들어보자.
실행 결과를 보면 callServiceV0.external() 을 실행할 때는 프록시를 호출한다. 따라서 CallLogAspect 어드바이스가 호출된 것을 확인할 수 있다.
그리고 AOP Proxy는 target.external() 을 호출한다.
그런데 여기서 문제는 callServiceV0.external() 안에서 internal() 을 호출할 때 발생한다. 이때는CallLogAspect 어드바이스가 호출되지 않는다.
자바 언어에서 메서드 앞에 별도의 참조가 없으면 this 라는 뜻으로 자기 자신의 인스턴스를 가리킨다.
결과적으로 자기 자신의 내부 메서드를 호출하는 this.internal() 이 되는데, 여기서 this 는 실제 대상 객체(target)의 인스턴스를 뜻한다. 결과적으로 이러한 내부 호출은 프록시를 거치지 않는다. 따라서 어드바이스도 적용할수 없다.
reacted with thumbs up emoji reacted with thumbs down emoji reacted with laugh emoji reacted with hooray emoji reacted with confused emoji reacted with heart emoji reacted with rocket emoji reacted with eyes emoji
Uh oh!
There was an error while loading. Please reload this page.
섹션 14. 스프링 AOP - 실무 주의사항
1️⃣ 프록시와 내부 호출 - 문제
스프링은 프록시 방식의 AOP를 사용한다.
따라서 AOP를 적용하려면 항상 프록시를 통해서 대상 객체(Target)을 호출해야 한다.
이렇게 해야 프록시에서 먼저 어드바이스를 호출하고, 이후에 대상 객체를 호출한다.
만약 프록시를 거치지 않고 대상 객체를 직접 호출하게 되면 AOP가 적용되지 않고, 어드바이스도 호출되지 않는다.
AOP를 적용하면 스프링은 대상 객체 대신에 프록시를 스프링 빈으로 등록한다. 따라서 스프링은 의존관계 주입시에 항상 프록시 객체를 주입한다. 프록시 객체가 주입되기 때문에 대상 객체를 직접 호출하는 문제는 일반적으로 발생하지 않는다. 하지만 대상 객체의 내부에서 메서드 호출이 발생하면 프록시를 거치지 않고 대상 객체를 직접 호출하는 문제가발생한다. 실무에서 반드시 한번은 만나서 고생하는 문제이기 때문에 꼭 이해하고 넘어가자.
예제를 통해서 내부 호출이 발생할 때 어떤 문제가 발생하는지 알아보자. 먼저 내부 호출이 발생하는 예제를 만들어보자.
CallServiceV0.external()을 호출하면 내부에서internal()이라는 자기 자신의 메서드를 호출한다.this가 붙게 된다.그러니까 여기서는this.internal()이라고 이해하면 된다.CallServiceV0에 AOP를 적용하기 위해서 간단한Aspect를 하나 만들자.이제 앞서 만든
CallServiceV0을 실행할 수 있는 테스트 코드를 만들자.@Import(CallLogAspect.class): 앞서 만든 간단한Aspect를 스프링 빈으로 등록한다. 이렇게 해서CallServiceV0에 AOP 프록시를 적용한다.@SpringBootTest: 내부에 컴포넌트 스캔을 포함하고 있다.CallServiceV0에@Component가 붙어있으므로 스프링 빈 등록 대상이 된다.실행결과
callServiceV0.external()을 실행할 때는 프록시를 호출한다. 따라서CallLogAspect어드바이스가 호출된 것을 확인할 수 있다.target.external()을 호출한다.callServiceV0.external()안에서internal()을 호출할 때 발생한다. 이때는CallLogAspect어드바이스가 호출되지 않는다.2️⃣ 프록시와 내부 호출 - 대안1 자기 자신 주입
callServiceV1를 수정자를 통해서 주입 받는 것을 확인할 수 있다. 스프링에서 AOP가 적용된 대상을 의존관계 주입 받으면 주입 받은 대상은 실제 자신이 아니라 프록시 객체이다.external()을 호출하면callServiceV1.internal()를 호출하게 된다. 주입받은callServiceV1은 프록시이다. 따라서 프록시를 통해서 AOP를 적용할 수 있다.3️⃣ 프록시와 내부 호출 - 대안2 지연 조회
앞서 생성자 주입이 실패하는 이유는 자기 자신을 생성하면서 주입해야 하기 때문이다. 이 경우 수정자 주입을 사용하거나 지금부터 설명하는 지연 조회를 사용하면 된다.
스프링 빈을 지연해서 조회하면 되는데,
ObjectProvider(Provider),ApplicationContext를 사용하면 된다.CallServiceV2
ObjectProvider는 기본편에서 학습한 내용이다.ApplicationContext는 너무 많은 기능을 제공한다.ObjectProvider는 객체를 스프링 컨테이너에서 조회하는 것을 스프링 빈 생성 시점이 아니라 실제 객체를 사용하는 시점으로 지연할 수 있다.callServiceProvider.getObject()를 호출하는 시점에 스프링 컨테이너에서 빈을 조회한다.여기서는 자기 자신을 주입 받는 것이 아니기 때문에 순환 사이클이 발생하지 않는다.
4️⃣ 프록시와 내부 호출 - 대안3 구조 변경
앞선 방법들은 자기 자신을 주입하거나 또는
Provider를 사용해야 하는 것 처럼 조금 어색한 모습을 만들었다.가장 나은 대안은 내부 호출이 발생하지 않도록 구조를 변경하는 것이다. 실제 이 방법을 가장 권장한다
CallServiceV3
내부 호출 자체가 사라지고,
callServiceinternalService를 호출하는 구조로 변경되었다. 덕분에 자연스럽게 AOP가 적용된다.여기서 구조를 변경한다는 것은 이렇게 단순하게 분리하는 것 뿐만 아니라 다양한 방법들이 있을 수 있다.
클라이언트→external()클라이언트→internal()물론 이 경우
external()에서internal()을 내부 호출하지 않도록 코드를 변경해야 한다. 그리고 클라이언트가external(),internal()을 모두 호출하도록 구조를 변경하면 된다. (물론 가능한 경우에 한해서)All reactions