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를 사용한다. 따라서 AOP를 적용하려면 항상 프록시를 통해서 대상 객체(Target)을 호출해야 한다. 이렇게 해야 프록시에서 먼저 어드바이스를 호출하고, 이후에 대상 객체를 호출한다. 만약 프록시를 거치지 않고 대상 객체를 직접 호출하게 되면 AOP가 적용되지 않고, 어드바이스도 호출되지 않는다.
AOP를 적용하면 스프링은 대상 객체 대신에 프록시를 스프링 빈으로 등록한다. 따라서 스프링은 의존관계 주입시에 항상 프록시 객체를 주입한다. 프록시 객체가 주입되기 때문에 대상 객체를 직접 호출하는 문제는 일반적으로 발생하지 않는다. 하지만 대상 객체의 내부에서 메서드 호출이 발생하면 프록시를 거치지 않고 대상 객체를 직접 호출하는 문제가발생한다.
스프링 부트 2.6부터는 순환 참조를 기본적으로 금지하도록 정책이 변경되었다. 따라서 이번 예제를 스프링 부트2.6 이상의 버전에서 실행하면 다음과 같은 오류 메시지가 나오면서 정상 실행되지 않는다. Error creating bean with name 'callServiceV1': Requested bean is currently in creation: Is there an unresolvable circular reference?
이 문제를 해결하려면 application.properties 에 다음을 추가해야 한다. spring.main.allow-circular-references=true
앞으로 있을 다른 테스트에도 영향을 주기 때문에 스프링 부트 2.6 이상이라면 이 설정을 꼭 추가해야 한다.
프록시와 내부 호출 - 대안2 지연 조회
앞서 생성자 주입이 실패하는 이유는 자기 자신을 생성하면서 주입해야 하기 때문이다. 이 경우 수정자 주입을 사용하거나 지금부터 설명하는 지연 조회를 사용하면 된다.
스프링 빈을 지연해서 조회하면 되는데, ObjectProvider(Provider) , ApplicationContext 를 사용하면 된다.
내부 호출 자체가 사라지고, callService => internalService 를 호출하는 구조로 변경되었다. 덕분에 자연스럽게 AOP가 적용된다.
여기서 구조를 변경한다는 것은 이렇게 단순하게 분리하는 것 뿐만 아니라 다양한 방법들이 있을 수 있다.
클라이언트 => external()
클라이언트 => internal()
물론 이 경우 external() 에서 internal() 을 내부 호출하지 않도록 코드를 변경해야 한다. 그리고 클라이언트가 external() , internal() 을 모두 호출하도록 구조를 변경하면 된다. (물론 가능한 경우에 한해서)
참고
AOP는 주로 트랜잭션 적용이나 주요 컴포넌트의 로그 출력 기능에 사용된다. 쉽게 이야기해서 인터페이스에 메서드가 나올 정도의 규모에 AOP를 적용하는 것이 적당하다. 더 풀어서 이야기하면 AOP는 public 메서드에만 적용한다. private 메서드처럼 작은 단위에는 AOP를 적용하지 않는다.
AOP 적용을 위해 private 메서드를 외부 클래스로 변경하고 public 으로 변경하는 일은 거의 없다. 그러나 위 예제와 같이 public 메서드에서 public 메서드를 내부 호출하는 경우에는 문제가 발생한다.
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.
프록시와 내부 호출 - 문제
CallServiceV0
CallServiceV0.external()을 호출하면 내부에서 internal() 이라는 자기 자신의 메서드를 호출한다.CallLogAspect
CallServiceV0Test
@Import(CallLogAspect.class): 앞서 만든 간단한 Aspect 를 스프링 빈으로 등록한다. 이렇게 해서, CallServiceV0 에 AOP 프록시를 적용한다.@Import(CallLogAspect.class): 앞서 만든 간단한 Aspect 를 스프링 빈으로 등록한다. 이렇게 해서,CallServiceV0 에 AOP 프록시를 적용한다.
실행 결과 - external()

실행 결과를 보면 callServiceV0.external() 을 실행할 때는 프록시를 호출한다. 따라서 CallLogAspect 어드바이스가 호출된 것을 확인할 수 있다. 그리고 AOP Proxy는 target.external() 을 호출한다.
그런데 여기서 문제는 callServiceV0.external() 안에서 internal() 을 호출할 때 발생한다. 이때는 CallLogAspect 어드바이스가 호출되지 않는다.
자바 언어에서 메서드 앞에 별도의 참조가 없으면 this 라는 뜻으로 자기 자신의 인스턴스를 가리킨다.
결과적으로 자기 자신의 내부 메서드를 호출하는 this.internal() 이 되는데, 여기서 this 는 실제 대상 객체
(target)의 인스턴스를 뜻한다. 결과적으로 이러한 내부 호출은 프록시를 거치지 않는다. 따라서 어드바이스도 적용할수 없다.
실행 결과 - internal()


할 수 있다.
참고
프록시와 내부 호출 - 대안1 자기 자신 주입
CallServiceV1
CallServiceV1Test
실행 결과


주의
스프링 부트 2.6부터는 순환 참조를 기본적으로 금지하도록 정책이 변경되었다. 따라서 이번 예제를 스프링 부트2.6 이상의 버전에서 실행하면 다음과 같은 오류 메시지가 나오면서 정상 실행되지 않는다.
Error creating bean with name 'callServiceV1': Requested bean is currently in creation: Is there an unresolvable circular reference?이 문제를 해결하려면 application.properties 에 다음을 추가해야 한다.
spring.main.allow-circular-references=true앞으로 있을 다른 테스트에도 영향을 주기 때문에 스프링 부트 2.6 이상이라면 이 설정을 꼭 추가해야 한다.
프록시와 내부 호출 - 대안2 지연 조회
CallServiceV2
CallServiceV2Test
실행 결과

프록시와 내부 호출 - 대안3 구조 변경
CallServiceV3
InternalService
CallServiceV3Test
실행 결과


참고
All reactions