ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 스프링 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
Designed by Tistory.