Spring에서 수동으로 빈을 등록할 때에는 @Configuration 클래스 안에서 @Bean을 사용해야 한다. 이번에는 왜 @Configuraiton 클래스 안에서 @Bean을 사용해야 하는지 살펴보도록 하자.
@Configuration 안에 @Bean을 사용해야 하는 이유, proxyBeanMethods
@Bean 어노테이션을 이용한 수동 빈 등록
스프링에서는 일반적으로 컴포넌트 스캔을 사용해 자동으로 빈을 등록하는 방법을 이용한다. 하지만 @Bean 어노테이션을 사용해 수동으로 빈을 등록해야 하는 경우도 있다. 대표적으로 다음과 같은 경우에 @Bean으로 직접 빈을 등록해준다.
- 개발자가 직접 제어가 불가능한 라이브러리를 활용할 때
- 애플리케이션 전범위적으로 사용되는 클래스를 등록할 때
- 다형성을 활용하여 여러 구현체를 등록해주어야 할 때
@Bean을 이용한 수동 빈 메소드는 스프링 빈 안에만 구현해되어 있다면 모두 동작한다. 하지만 스프링은 @Bean은 반드시 @Configuration 어노테이션을 활용하도록 강조하는데, 그 이유는 @Configuration에 특별한 부가 기능이 적용되기 때문이다.
@Configuration에 적용되는 프록시 패턴
@Configuration 어노테이션 안에는 @Component 어노테이션이 붙어있어서 @Configuration이 붙어있는 클래스 역시 컴포넌트 스캔 대상이 되어 빈으로 자동 등록된다.
그럼에도 불구하고 스프링이 @Configuration을 따로 만든 이유는 CGLIB으로 프록시 패턴을 적용해 수동으로 등록하는 스프링 빈이 반드시 싱글톤으로 생성됨을 보장하기 위해서이다.
예를 들어 다음과 같이 스프링 빈으로 등록하고자 하는 클래스가 있다고 하자.
public class MyResource {
}
위의 클래스를 @Component를 이용해 자동으로 빈 등록을 한다면 스프링이 해당 클래스의 객체의 생성을 제어하게 되고(제어의 역전, IoC) 1개의 객체만 생성되도록 컨트롤할 수 있다.
하지만 위의 클래스를 @Bean을 사용해 직접 빈으로 등록해준다고 하자. 그러면 우리는 다음과 같이 해당 빈 등록 메소드를 여러 번 호출할 수 있게 된다.
@Configuration
public class MyBeanConfiguration {
@Bean
public MyResource myResource() {
return new MyResource();
}
@Bean
public MyFirstBean myFirstBean() {
return new MyFirstBean(myResource());
}
@Bean
public MySecondBean mySecondBean() {
return new MySecondBean(myResource());
}
}
만약 위와 같이 MyResource 빈을 생성하는 메소드를 여러 번 호출하여 다른 빈에 DI를 여러 번 하였다면 여러 개의 MyResource 빈이 생성이 될 것이다.
하지만 스프링은 이러한 문제를 방지하고자 @Configuration이 있는 클래스를 객체로 생성할 때 CGLIB 라이브러리를 사용해 프록시 패턴을 적용한다. 그래서 @Bean이 있는 메소드를 여러 번 호출하여도 항상 동일한 객체를 반환하여 싱글톤을 보장한다. 이를 이해하기 쉬운 코드로 나타내면 다음과 같다.
@Configuration
public class MyBeanConfigurationProxy extends MyBeanConfiguration {
private Object source;
@Override
public MyResource myResource() {
if (myResource == null) {
source = super.myResource();
}
return source;
}
@Override
public MyFirstBean myFirstBean() {
return super.myFirstBean();
}
@Override
public MySecondBean mySecondBean() {
return super.mySecondBean();
}
}
CGLIB 은 상속을 사용해서 프록시를 구현하므로 다음과 같이 프록시가 구현된다고 이해할 수 있다. 물론 실제로는 이렇게 생성되지 않고 내부 클래스를 사용하는 등의 차이가 있으므로 이해를 돕기 위한 코드로만 생각하면 된다.
싱글톤 여부를 제어하기 위한 proxyBeanMethods
대부분의 경우에는 @Bean에 의한 수동 빈 등록을 할 때 싱글톤으로 생성되기를 원한다. 하지만 @Bean 메소드를 호출할 때 의도적으로 매번 다른 객체가 생성되기를 원할 수 있다. 그럴 때에는 @Configuration 어노테이션이 갖고 있는 proxyBeanMethods를 false로 주면 된다. 예를 들어 아까 봤던 코드에 다음과 같이 proxyBeanMethods를 false로 주었다고 하자.
@Configuration(proxyBeanMethods = false)
public class MyBeanConfiguration {
@Bean
public MyResource myResource() {
return new MyResource();
}
@Bean
public MyFirstBean myFirstBean() {
return new MyFirstBean(myResource());
}
@Bean
public MySecondBean mySecondBean() {
return new MySecondBean(myResource());
}
}
그러면 위의 설정 클래스에 대해서는 프록시가 적용되지 않으며 모든 @Bean 메소드 호출마다 새로운 객체를 생성해준다.
하지만 거의 모든 상황에서 새로운 객체의 생성을 필요로 하는 경우는 거의 없다. 하지만 @Configuration이 아니라면 빈이 싱글톤임을 보장받을 수 없으므로 반드시 @Configuration 안에 @Bean을 사용해주도록 하자.
관련 포스팅