728x90
Spring의 빈(Bean)과 프록시
- Spring 컨테이너의 빈: Spring 컨테이너는 애플리케이션 시작 시 @Service, @Repository, @Component 등으로 선언된 클래스의 인스턴스를 생성하고 관리합니다. 이 인스턴스들을 "빈"이라고 부릅니다.
- @Async와 AOP 프록시 생성:
- 어떤 서비스 클래스(예: MyServiceA)의 특정 메소드(예: asyncMethod())에 @Async 어노테이션이 붙어 있고, @EnableAsync가 활성화되어 있다면, Spring 컨테이너는 MyServiceA의 실제 인스턴스(myServiceA_real)를 생성하는 것 외에, MyServiceA의 프록시 객체(myServiceA_proxy)를 생성합니다.
- 이 프록시 객체는 myServiceA_real을 래핑(wrapping)하고 있으며, myServiceA_real과 동일한 인터페이스를 구현하거나 상속받습니다.
- Spring 컨테이너는 다른 빈들이 MyServiceA 타입의 빈을 주입받을 때, 실제 myServiceA_real 대신 myServiceA_proxy를 주입해 줍니다.
다른 서비스 코드에서 호출 시 프록시의 역할
이제 MyServiceB라는 다른 서비스 클래스가 MyServiceA를 주입받아 사용하는 상황을 가정해 봅시다.
@Service
public class MyServiceA {
@Async
public void asyncMethod() {
// 비동기적으로 실행될 로직
System.out.println("MyServiceA.asyncMethod() - Current Thread: " + Thread.currentThread().getName());
}
public void syncMethodCallingAsyncInternally() {
System.out.println("MyServiceA.syncMethodCallingAsyncInternally() - Calling asyncMethod() internally");
asyncMethod(); // <-- 동일 클래스 내부 호출 (프록시 우회)
}
}
@Service
public class MyServiceB {
private final MyServiceA myServiceA; // Spring이 myServiceA_proxy를 주입해 줍니다.
public MyServiceB(MyServiceA myServiceA) {
this.myServiceA = myServiceA;
}
public void callAsyncMethodFromOtherService() {
System.out.println("MyServiceB.callAsyncMethodFromOtherService() - Calling MyServiceA.asyncMethod()");
myServiceA.asyncMethod(); // <-- 다른 서비스 코드에서 호출 (프록시를 통해 호출)
}
}
MyServiceB의 callAsyncMethodFromOtherService() 메소드에서 myServiceA.asyncMethod()를 호출할 때 일어나는 일은 다음과 같습니다:
- MyServiceB에 주입된 myServiceA 인스턴스는 실제 MyServiceA 객체가 아니라 Spring이 생성한 MyServiceA 프록시 객체(myServiceA_proxy)입니다.
- myServiceA_proxy.asyncMethod()가 호출되면, 이 호출은 프록시 객체를 통해 전달됩니다.
- 프록시는 asyncMethod() 호출을 가로챕니다(인터셉션). Spring AOP는 이 호출을 가로채서 @Async 어노테이션이 붙어 있음을 인지합니다.
- 프록시는 비동기 로직을 수행합니다. 즉, 새로운 스레드 풀에서 asyncMethod()를 실행하도록 작업을 제출합니다.
- 결과적으로 MyServiceA.asyncMethod() 내부의 코드는 호출을 시작한 스레드(MyServiceB가 속한 스레드)와 다른 스레드에서 실행됩니다.
요약
- 동일 클래스 내부 호출 (MyServiceA.syncMethodCallingAsyncInternally() -> asyncMethod()): this.asyncMethod()와 같이 호출하면, 이는 Spring 프록시를 거치지 않고 실제 MyServiceA 객체의 메소드를 직접 호출하는 것이기 때문에 @Async가 무시되고 동기적으로 실행됩니다. Spring 프레임워크 입장에서는 마치 @Async 어노테이션이 없는 일반 메소드 호출과 동일하게 취급합니다.
- 다른 서비스 코드에서의 호출 (MyServiceB -> MyServiceA.asyncMethod()): MyServiceB에 주입된 myServiceA는 실제 MyServiceA 객체가 아니라 Spring이 특별히 만든 프록시 객체입니다. myServiceA.asyncMethod() 호출은 이 프록시 객체를 통해 이루어지며, 프록시가 @Async의 비동기 로직을 가로채서 처리하므로 정상적으로 비동기 호출이 발생합니다.
따라서 Spring AOP의 프록시 메커니즘은 @Async, @Transactional, @Cacheable 등과 같은 어노테이션의 핵심 기능을 제공하며, 외부(다른 빈)에서 호출할 때만 그 기능을 발휘합니다.
'Engineering > Spring' 카테고리의 다른 글
Spring @Async 사용 예제 (1) | 2025.06.09 |
---|---|
POST request RestTemplate with MultiValue (0) | 2025.01.07 |
[SpringBoot] ALLOW_COMMENTS 오류 (1) | 2021.04.09 |
jar 파일 외부에서 프로퍼티 이용하는 방법(spring) (1) | 2015.10.12 |
spring boot 사용 예제 (0) | 2014.07.09 |