FactoryBean을 구현한 클래스에서 @Transactional을 적용한 메서드를 가진 Service 클래스를 주입받을 때 생성자 주입과 필드 주입 방식에 있어 발생하는 차이점에 대해 정리하고자 합니다.
일반적인 빈 생성의 경우에는 생성자 주입과 필드 주입 2가지 경우 모두 AOP 프록시 객체가 잘 전달이 되는데 위 상황에서는 다르게 동작하는 것을 발견했습니다.
0. Given:
/** Foo.java */
@RequiredArgsConstructor
public class Foo {
private final FooService fooService;
public void bar() {
fooService.bar();
}
}
/** FooService.java */
@Service
public class FooService {
@Transactional
public void bar() {
System.out.println(TransactionAspectSupport.currentTransactionStatus());
}
}
1. Working case
/** FooBean.java */
@Component
public class FooBean implements FactoryBean<Foo> {
@Autowired
private FooService fooService;
@Override
public Foo getObject() throws Exception {
return new Foo(fooService);
}
@Override
public Class<Foo> getObjectType() {
return Foo.class;
}
}
- 필드 주입으로 Foo 객체를 생성하는 경우 트랜잭션이 잘 걸림
- FooBean 클래스에 대해 빈이 먼저 등록되고 의존성 객체는 빈 생성 후 주입됩니다.
- Spring Container에서 “FooService” 빈을 찾아서 프록시 객체 반환합니다
2. Not working
/** FooBean.java */
@Component
@RequiredArgsConstructor
public class FooBean implements FactoryBean<Foo> {
private final FooService fooService;
@Override
public Foo getObject() throws Exception {
return new Foo(fooService);
}
@Override
public Class<Foo> getObjectType() {
return Foo.class;
}
}
- 생성자 주입으로 Foo 객체를 초기화하는 경우 트랜잭션이 안 걸림
- Proxy 객체가 생성되지 않아 트랜잭션 AOP를 적용시킬 수 없음
- FooBean은 생성자 주입으로 빈 생성과 동시에 의존성을 주입받습니다. 주입받는 FooService 객체가 프록시 객체가 아닌 일반 객체로 받고 있음
(Error 발생)
org.springframework.transaction.NoTransactionException: No transaction aspect-managed TransactionStatus in scope
생성자 주입 시 발생할 수 있는 문제
- 프록시 객체가 주입되지 않음: FooBean이 생성될 때, FooService가 스프링 컨테이너로부터 주입되는데, 이 시점에 FooService가 아직 프록시 객체로 변환되지 않았을 수 있습니다. 이 경우, AOP를 통해 트랜잭션 처리가 필요한 시점에서 프록시 객체가 아닌 실제 객체가 주입되면 @Transactional이 제대로 적용되지 않습니다.
원인 분석
생성자 주입을 하는 경우 스프링 컨테이너에 등록된 실제 객체 FooService를 반환하여 FooBean 생성자에 전달하고 있음
(필드 주입을 하는 경우에는 빈 객체를 먼저 생성한 후에 의존성을 주입하는 방식이다. 스프링 컨테이너가 초기화된 후 추후에 의존성을 주입한다.)
@Component
public class FooBean implements FactoryBean<Foo> {
@Autowired
private FooService fooService;
}
위 형태로 빈 주입을 하는 경우, FactoryBean을 구현한 FooBean에 대해서 빈 등록이 됩니다. 그런 다음, getObject()를 메서드를 통해 FooBean 클래스에서 Foo 빈을 생성합니다. (FooBean은 Foo Bean을 생성하는 Factory 역할)
FactoryBean 이란
- If a bean implements this interface, it is used as a factory for an object to expose, not directly as a bean instance that will be exposed itself.
- NB: A bean that implements this interface cannot be used as a normal bean. A FactoryBean is defined in a bean style, but the object exposed for bean references (getObject()) is always the object that it creates
- Interface to be implemented by objects used within a BeanFactory which are themselves factories for individual objects.
빈 주입의 차이
- 필드 주입의 경우 Bean이 완전히 생성된 후에 의존성이 주입된다. 프록시가 적용된 상태에서 주입(FooService)이 이루어진다.
- 생성자 주입 대신 필드 주입을 사용하면, 스프링 컨텍스트가 완전히 초기화된 후 프록시 객체가 주입되므로 트랜잭션과 같은 AOP 기능이 정상적으로 동작합니다.
FactoryBean의 AOP 적용 문제
- FactoryBean 자체는 프록시가 적용되지 않음: 스프링이 @Transactional을 사용하는 빈에 대해 트랜잭션 프록시를 적용할 때, 그 빈이 스프링 컨테이너에서 직접 관리되는 경우에만 프록시가 적용됩니다. 그런데 FactoryBean은 객체 생성의 책임을 갖고 있는 빈이기 때문에, 이로부터 생성된 객체(Foo)는 스프링의 컨테이너가 직접 관리하는 빈이 아니며, AOP 프록시가 자동으로 적용되지 않을 수 있습니다.
- FooService의 주입 문제: FactoryBean의 getObject() 메서드를 호출해 반환되는 Foo 객체는 FooService를 생성자 주입받지만, 이때 FooService가 트랜잭션 프록시로 감싸진 객체가 아닌, 실제 객체가 주입될 가능성이 있습니다. 이는 FactoryBean의 인스턴스가 생성되고 FooService가 주입되는 시점에 AOP 프록시가 아직 적용되지 않았기 때문입니다.
왜 생성자 주입이 문제가 되는가?
생성자 주입은 빈이 생성되는 시점에 의존성을 주입하는데, 이 시점에서는 아직 스프링이 AOP 프록시를 적용하기 이전일 수 있습니다.
FactoryBean을 통해 빈이 생성되면, 그 객체는 스프링이 관리하는 컨텍스트 밖에서 생성되었기 때문에 프록시 적용의 타이밍이 맞지 않게 됩니다.
따라서, 생성자 주입 시점에 스프링 컨테이너는 아직 프록시를 적용하지 않고 실제 객체를 주입하게 됩니다.
필드 주입이 정상적으로 작동하는 이유
필드 주입을 사용하는 경우, 빈이 생성된 후에 스프링이 프록시 객체를 주입합니다. 이 방식은 빈의 생성이 완료된 후에 의존성을 주입하기 때문에, 스프링이 관리하는 프록시 객체가 주입될 수 있습니다.
'Spring Framework > Spring' 카테고리의 다른 글
Spring StdSerializer, @JsonSerializer 커스텀 직렬화 처리 방법 (1) | 2024.10.16 |
---|---|
Spring Scope 어노테이션으로 빈 라이프사이클 이해하기 - prototype, request, session (4) | 2024.10.13 |
Spring AOP 개념 정리 및 Aspect 적용 방법 - annotation 활용 (1) | 2024.10.10 |
[Spring] 스프링 FactoryBean 이해하기 - Custom Bean 생성방법 (2) | 2024.10.09 |
[Spring] Spring Batch Tasklet 작업 단위 이해하기 - StepContribution, ChunkContext (0) | 2024.10.07 |