[TIL] [Spring AOP] [실무 주의사항] 프록시와 내부 호출 문제

SlowAnd·2024년 1월 6일
0

Spring AOP

목록 보기
4/5
post-thumbnail

프록시 방식의 스프링 AOP

스프링은 프록시 방식의 AOP를 사용한다.

따라서 AOP를 적용하려면 항상 Proxy를 통해서 대상 객체(Target)을 호출해야 한다.

이렇게 해야 프록시에서 먼저 어드바이스를 호출하고, 이후에 대상 객체를 호출한다.

Advice 호출하는데, 왜 항상 Proxy를 통해서 호출되는걸까?

AOP를 적용하면 스프링은 대상 객체 대신에 Proxy를 스프링 빈으로 등록한다

따라서 스프링은 의존관계 주입시에 항상 프록시 객체를 주입한다.
프록시 객체가 주입되기 때문에 객체를 직접 호출하는 문제는 일반적으로 발생하지 않는다.

[문제] 프록시X , 실제 객체 호출

하지만 대상 객체의 내부에서 메서드 호출이 발생하면 프록시를 거치지 않고 대상 객체를 직접 호출하는 문제가 발생한다.

예시 상황

테스트 코드 결과

internal() 메서드를 안에 가지고 있는 external() 메서드를 호출한 결과

external() 실행 과정

실행 결과를 보면 callServiceV0.external() 을 실행할 때는 프록시를 호출한다. 따라서 CallLogAspect 어드바이스가 호출된 것을 확인할 수 있다.

그리고 AOP Proxy는 target.external() 을 호출한다.
그런데 여기서 문제는 callServiceV0.external() 안에서 internal() 을 호출할 때 발생한다.

이때는 CallLogAspect 어드바이스가 호출되지 않는다.
자바 언어에서 메서드 앞에 별도의 참조가 없으면 this 라는 뜻으로 자기 자신의 인스턴스를 가리킨다.

결과적으로 자기 자신의 내부 메서드를 호출하는 this.internal() 이 되는데, 여기서 this 는 실제 대상 객체 (target)의 인스턴스를 뜻한다.
결과적으로 이러한 내부 호출은 프록시를 거치지 않는다. 따라서 어드바이스도 적용할 수 없다.

internal() 실행 과정


외부에서 호출하는 경우 프록시를 거치기 때문에 internal()CallLogAspect 어드바이스가 적용된 것을 확인 할 수 있다.

대안? AspectJ 하지만 비추천

AspectJ? 하지만 비추천

실제 코드에 AOP를 직접 적용하는 AspectJ를 사용하면 이런 문제가 발생하지 않는다.
프록시를 통하는 것이 아 니라 해당 코드에 직접 AOP 적용 코드가 붙어 있기 때문에 내부 호출과 무관하게 AOP를 적용할 수 있다.
하지만 로드 타임 위빙 등을 사용해야 하는데, 설정이 복잡하고 JVM 옵션을 주어야 하는 부담이 있다.
그리고 지금부터 설명할 프록시 방식의 AOP에서 내부 호출에 대응할 수 있는 대안들도 있다.
이런 이유로 AspectJ를 직접 사용하는 방법은 실무에서는 거의 사용하지 않는다.


[대안1] - 지연 조회

잠깐
내부 호출을 해결하는 가장 간단한 방법은 자기 자신을 의존관계 주입 받는 것이다. 이때 생성자 주입, 수정자 주입이 방법이 있는데, 생성자 주입은 순환참조 문제를 일으킨다. 따라서 수정자 주입 방법을 사용하는데
스프링 2.6부터 이 방법을 금지한다.


따라서 스프링 빈을 지연해서 조회하자.
1.@Lazy 사용
2.ObjectProvider(Provider) , ApplicationContext 를 사용

1.@Lazy 사용

2. ObjectProvider(Provider) , ApplicationContext 를 사용
DL - ObjectPoriver 설명 참조
ApplicationContext 는 너무 많은 기능을 제공한다.
ObjectProvider 는 객체를 스프링 컨테이너에서 조회하는 것을 스프링 빈 생성 시점이 아니라 실제 객체를 사용하 는 시점으로 지연할 수 있다.
callServiceProvider.getObject() 를 호출하는 시점에 스프링 컨테이너에서 빈을 조회한다.
여기서는 자기 자신을 주입 받는 것이 아니기 때문에 순환 사이클이 발생하지 않는다.

결과


[대안2][권장 방법] - 구조 변경(분리)

가장 나은 대안은 내부 호출이 발생하지 않도록 구조를 변경하는 것이다. 실제 이 방법을 가장 권장한다.

external 클래스, internal 클래스를 따로 만들고,
extenral 클래스에 interanal 객체 주입을 받으면 된다.
업로드중..
내부호출자체가사라지고, callService internalService 를호출하는구조로변경되었다.덕분에자연스 럽게 AOP가 적용된다.


참고

참고
AOP는 주로 트랜잭션 적용이나 주요 컴포넌트의 로그 출력 기능에 사용된다.
쉽게 이야기해서 인터페이스에 메서드가 나올 정도의 규모에 AOP를 적용하는 것이 적당하다.

더 풀어서 이야기하면 AOP는 public 메서드에 만 적용한다.
private 메서드처럼 작은 단위에는 AOP를 적용하지 않는다.
AOP 적용을 위해 private 메서드를 외부 클래스로 변경하고 public 으로 변경하는 일은 거의 없다.
그러나 위 예제와 같이 public 메서드에서 public 메서드를 내부 호출하는 경우에는 문제가 발생한다.
AOP가 잘 적용되지 않으면 내부 호출을 의심해보자.


참조
스프링 핵심 원리 - 고급편

0개의 댓글