728x90
스프링은 프록시 방식의 AOP를 사용한다
따라서 AOP를 적용하려면 항상 프록시를 통해 대상 객체(Target)을 호출해야 한다.
이렇게 해야 프록시에서 먼저 어드바이스를 호출하고, 이후에 대상 겍체를 호출한다.
하지만, 대상 객체의 내부에서 메서드 호출이 발생하면 프록시를 거치지 않고 대상 객체를
직접 호출하는 문제가 발생한다. 실무에서 반드시 한번은 만나서 고생하는 문제
프록시와 내부호출 : 내부호출이 발생하는 예제
package hello.aop.internalcall;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
@Slf4j
@Component
public class CallServiceV0 {
public void external() {
log.info("call external");
internal(); //내부 메서드 호출(this.internal())}
public void internal() {
log.info("call internal");}
}
package hello.aop.internalcall.aop;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
@Slf4j
@Aspect
public class CallLogAspect {
@Before("execution(* hello.aop.internalcall..*.*(..))")
public void doLog(JoinPoint joinPoint) {
log.info("aop={}", joinPoint.getSignature());
}
}
package hello.aop.internalcall;
import hello.aop.internalcall.aop.CallLogAspect;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.annotation.Import;
@Import(CallLogAspect.class)
@SpringBootTest
class CallServiceV0Test {
@Autowired
CallServiceV0 callServiceV0;
@Test
void external() {
callServiceV0.external();
}
@Test
void internal() {
callServiceV0.internal();
}
}
실행 결과를 보면 callServiceV0.external() 을 실행할 때는 프록시를 호출한다.
따라서 CallLogAspect 어드바이스가 호출된 것을 확인할 수 있다.
그리고 AOP Proxy는 target.external() 을 호출한다.
그런데 여기서 문제는 callServiceV0.external() 안에서 internal() 을 호출할 때 발생한다.
이때는 CallLogAspect 어드바이스가 호출되지 않는다
프록시 방식의 AOP 한계
스프링은 프록시 방식의 AOP를 사용한다. 프록시 방식의 AOP는 메서드 내부 호출에 프록시를 적용할 수 없다
참고
실제 코드에 AOP를 직접 적용하는 AspectJ를 사용하면 이런 문제가 발생하지 않는다. 프록시를 통하는 것이 아니라 해당 코드에 직접 AOP 적용 코드가 붙어 있기 때문에 내부 호출과 무관하게 AOP를 적용할 수 있다.
AspectJ 를 사용하면, 에스팩트 라는 모듈화된 코드 블룩을 작성하여 , 해당 에스펙트가 실행될 지점을 지정하고, 어드바이스를 적용할 수 있다. 이 때 ,AspectJ 컴파일러는 에스팩트를 기존 클래스의 바이트 코드에 추가하여 새로운 클래스파일을 생성한다. 이렇게 생성된 클래스 파일은 ,기존 클래스와 같은 이름을 가지지만 AspectJ가 적용된 메서드가 포함된 프록시 클래스이다.
따라서, AspectJ를 사용하면 프록시 객체를 직접 만들어주는 것이 아니라, 애스팩트를 사용하여 프록시 기능을 구현할수 있다.
내부 호출이 발생하지 않도록 구조를 변경해야 한다.!!
package hello.aop.internalcall;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
/**
* 구조를 변경(분리)
*/
@Slf4j
@Component
@RequiredArgsConstructor
public class CallServiceV3 {
private final InternalService internalService;
public void external() {
log.info("call external");
internalService.internal(); //외부 메서드 호출
}
}
내부 호출을 InternalService 라는 별도의 클래스로 분리했다.
package hello.aop.internalcall;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
@Slf4j
@Component
public class InternalService {
public void internal() {
log.info("call internal");
}
}
package hello.aop.internalcall;
import hello.aop.internalcall.aop.CallLogAspect;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.annotation.Import;
@Import(CallLogAspect.class)
@SpringBootTest
class CallServiceV3Test {
@Autowired
CallServiceV3 callServiceV3;
@Test
void external() {
callServiceV3.external();
}
}
결론
AOP는 주로 트랜잭션 적용이나 주요 컴포넌트의 로그 출력 기능에 사용된다. 쉽게 이야기해서 인터페이스에 메서드가 나올 정도의 규모에 AOP를 적용하는 것이 적당하다. 더 풀어서 이야기하면 AOP는 public 메서드에만 적용한다. private 메서드처럼 작은 단위에는 AOP를 적용하지 않는다. AOP 적용을 위해 private 메서드를 외부 클래스로 변경하고 public 으로 변경하는 일은 거의 없다. 그러나 위 예제와 같이 public 메서드에서 public 메서드를 내부 호출하는 경우에는 문제가 발생한다. 실무에서 꼭 한번은 만나는 문제이기에 이번 강의에서 다루었다. AOP가 잘 적용되지 않으면 내부 호출을 의심해보자
728x90
'spring' 카테고리의 다른 글
엑셀파일 업로드 및 DB insert 방법 (0) | 2023.05.02 |
---|---|
[spring] 스프링 AOP적용 (0) | 2023.04.27 |
[spring] 스프링 AOP개념이해 (0) | 2023.04.24 |
로그 추적기 ( 템플릿 콜백 패턴) (0) | 2023.04.17 |
로그추적기 사용(feat.템플릿메서드 패턴) (0) | 2023.04.17 |