오늘의하루

[Spring] Proxy 내부 호출 문제 해결 및 순환 참조 문제 해결 본문

Spring

[Spring] Proxy 내부 호출 문제 해결 및 순환 참조 문제 해결

오늘의하루_master 2024. 8. 28. 17:35

Proxy 내부 호출

@Component
@Slf4j
public class A {
    public void external() {
        log.info("external");
        internal();
    }
    public void internal() {
        log.info("internal");
    }
}

---

@Aspect
@Slf4j
public class AllAspect {
    @Before("execution(* *(..))")
    public void doA(JoinPoint joinPoint) {
        log.info("aop = {}", joinPoint.getSignature());
    }
}

위 상황에서 external()을 호출하면 doA 메서드에서 작성한 로그는 external()에서만 출력됩니다.

이는 Spring이 Proxy 방식을 사용하여 AOP를 적용하기 때문입니다. Proxy 객체는 external() 호출을 처리한 후 실제 객체의 internal() 메서드를 호출하기 때문에 internal() 메서드에서는 AOP가 적용되지 않습니다.

 

이를 해결하기 위해서는 external()에서 internal()을 호출할 때 this가 아닌 Proxy 객체를 사용해야 합니다.

Proxy 객체를 사용한 해결 방법

@Component
public class A {
    private A a;
    
    @Autowired
    public void setB(A a) {
        this.a = a;
    }
    
    public void external() {
        log.info("external");
        a.internal();
    }
    
    public void internal() {
        log.info("internal");
    }
}

위 코드에서는 external()에서 internal()을 호출할 때 Proxy 객체를 사용하도록 설정했습니다.

Setter 주입을 사용한 이유는 아직 초기화되지 않은 객체를 생성자 주입으로 처리할 수 없기 때문입니다.

Setter 주입은 생성자 주입보다 늦게 초기화되므로 이러한 순환 참조 문제를 해결할 수 있습니다.

 

하지만 SpringBoot 2.6 이상에서는 기본적으로 이러한 순환 참조가 금지되어 있습니다.

만약 위와 같은 방식으로 진행하려면 application.properties에 다음 설정을 추가해야 합니다:

spring.main.allow-circular-references=true

설정 없이 해결하는 방법

Spring에서는 @Lazy와 ObjectProvider를 사용하여 순환 참조 문제를 해결할 수 있습니다.

@Lazy

JPA를 공부하면서 Lazy는 지연 로딩에서 사용한다는 것을 듣고 해당 어노테이션을 보니 이해가 잘 되었습니다.

@Lazy는 의존성 주입을 지연시켜 생성자 주입보다 이후에 주입이 이루어지도록 합니다.

이로 인해 순환 참조 문제를 해결할 수 있습니다.

@Component
public class A {
    privat A a;
    
    @Autowired
    public A (@Lazy A a) {
        this.a = a;
    }
    
    ... method ...
}

ObjectProvider

ObjectProvider는 스프링에서 제공해주는 빈을 컨테이너에 요청하면 찾아주는 기능을 제공합니다.

@Component
public class A {
    private final ObjectProvider<A> aProvider;
    
    @Autowired
    public A (ObjectProvider<A> aProvider) {
        this.aProvider = aProvider;
    } 
    
    public void external() {
        log.info("external");
        A a = aProvider.getObject();
        a.internal();
    }
}

AOP를 통해 빈 컨테이너에 저장된 객체는 Proxy이기 때문에 getObject()를 통해 찾은 객체 또한 Proxy이기 때문에 내부 객체를 호출하지만 어드바이스가 적용되는것을 확인할 수 있습니다.

Comments