-
스프링 AOP 실무 주의사항Spring&SpringBoot 2025. 10. 21. 16:25
프록시 내부 호출 문제
스프링은 프록시 방식의 AOP를 사용한다. 따라서 AOP를 적용하려면 항상 프록시를 통해서 대상 객체(Target)을 호출해야 한다
이렇게 해야 프록시에서 먼저 어드바이스를 호출하고, 이후에 대상 객체를 호출한다
만약 프록시를 거치지 않고 대상 객체를 직접 호출하게 되면 AOP가 적용되지 않고, 어드바이스도 호출되지 않는다
@Slf4j @Component public class CallServiceV0 { public void external() { log.info("call external"); internal(); // 내부 메서드 호출 this.internal() } public void internal() { log.info("call internal"); } }@Slf4j @Aspect public class CallLogAspect { @Before("execution(* hello.aop.internalcall..*.*(..))") public void doLog(JoinPoint joinPoint) { log.info("aop={}", joinPoint.getSignature()); } }@SpringBootTest @Slf4j @Import(CallLogAspect.class) public class CallServiceV0Test { @Autowired CallServiceV0 callServiceV0; @Test void external() { log.info("target={}", callServiceV0.getClass()); callServiceV0.external(); } @Test void internal() { callServiceV0.internal(); } }

- callServiceV0.external 내부의 internal()을 호출할 때 CallLogAspect 어드바이스가 호출되지 않는다
- 내부 호출은 프록시를 거치지 않고 어드바이스 또한 적용할 수 없다
- AspectJ를 사용하면 이런 문제가 발생하지 않는다
- AspectJ를 직접 사용하는 방법은 실무에서는 거의 사용하지 않는다
대안1. 자기 자신 주입
@Slf4j @Component public class CallServiceV1 { private CallServiceV1 callServiceV1; @Autowired public void setCallServiceV1(CallServiceV1 callServiceV1) { this.callServiceV1 = callServiceV1; } public void external() { log.info("call external"); callServiceV1.internal(); //외부 메서드 호출 } public void internal() { log.info("call internal"); } }@Import(CallLogAspect.class) @SpringBootTest(properties = "spring.main.allow-circular-references=true") public class CallServiceV1Test { @Autowired CallServiceV1 callServiceV1; @Test void external() { callServiceV1.external(); } }

대안2. 지연 조회
@Slf4j @Component public class CallServiceV2 { // private final ApplicationContext applicationContext; private final ObjectProvider<CallServiceV2> callServiceProvider; public CallServiceV2(ObjectProvider<CallServiceV2> callServiceProvider) { this.callServiceProvider = callServiceProvider; } public void external() { log.info("call external"); // CallServiceV2 callServiceV2 = applicationContext.getBean(CallServiceV2.class); CallServiceV2 callServiceV2 =callServiceProvider.getObject(); callServiceV2.internal(); //외부 메서드 호출 } public void internal() { log.info("call internal"); } }@Import(CallLogAspect.class) @SpringBootTest public class CallServiceV2Test { @Autowired CallServiceV2 callServiceV2; @Test void external() { callServiceV2.external(); } }
- 스프링빈 지연 조회 : applicationContext 또는 ObjectProvider 사
대안3. 구조 변경
@Slf4j @Component public class InternalService { public void internal() { log.info("call internal"); } }@Slf4j @Component @RequiredArgsConstructor public class CallServiceV3 { private final InternalService internalService; public void external() { log.info("call external"); internalService.internal(); //외부 메서드 호출 } }@Import(CallLogAspect.class) @SpringBootTest public class CallServiceV3Test { @Autowired CallServiceV3 callServiceV3; @Test void external() { callServiceV3.external(); } }

- 인터페이스에 메서드가 나올 정도의 규모에 AOP를 적용하는 것이 적당하다
728x90'Spring&SpringBoot' 카테고리의 다른 글
로그출력AOP & 재시도AOP (0) 2025.10.21 템플릿 메서드 패턴 & 전략 패턴 & 템플릿 콜백 패턴 (0) 2025.10.13 로그추적기 - 동시성 문제 (0) 2025.10.02 스프링 퀵 스타트 04 (0) 2025.09.22 스프링 퀵 스타트 03 (0) 2025.09.18