스프링에선 @Bean 어노테이션을 붙일 경우, 해당 클래스를 스프링 빈으로 싱글톤으로 등록해준다.
하지만, Bean으로 수동 등록할 때, 내부의 의존관계를 직접 메서드 호출로 주입할 경우, 의존 관계 설정을 통해 생성되는 객체들은 싱글톤이 보장되지 않는다. 단순히 @Bean만으로 빈들을 설정할 경우에는 스프링 컨테이너를 대상으로 CGLIB가 동작하지 않기 때문이다.
또한, 인스턴스의 컨텍스트가 @Bean 코드를 거치지 않는다면 해당 Bean은 생성되지 않는다.
만약 별도 스프링 설정파일을 사용하며, 파일의 클래스에 @Configuration을 붙여주고, 해당 클래스의 메소드들에 @Bean 어노테이션을 붙여줄 경우, @Configuration이 붙은 클래스를 CGLIB를 통한 래퍼클래스로 생성하고, @Bean이 붙은 메서드들의 내부 의존관계 설정 로직에 대해서 추가적인 로직이 동작하며 의존관계에서 생성되는 객체들도 싱글톤을 보장해준다. 하지만 컨텍스트가 도달하지 않는 @Bean 클래스들은 생성되지 않는다는 단점이 있다.
@Configuration
@ComponentScan(
basePackages = "group.core.member",
basePackageClasses = FixDiscountPolicy.class,
excludeFilters = @ComponentScan.Filter(type = FilterType.ANNOTATION,
classes=Configuration.class)
)
public class AutoAppConfig {
}
위와 같이 @Configuration + @ComponentScan + @Autowired 방식을 사용한다면 Configuration을 통해 컨테이너가 CGLIB 래퍼클래스로 빈에 등록되며, DI 과정에서의 객체들도 싱글톤으로 컨테이너에 등록된다.
또한, 컨텍스트와 상관없이 모든 @Component 어노테이션이 붙은 클래스들을 싱글톤으로 생성해준다. 이 과정에서 @Autowired가 붙은 메소드들을 통해 DI를 해주며 추가적인 객체들이 싱글톤으로 생성되나, 만약 해당 객체가 스프링 빈으로 등록되지 않았다면 오류가 발생한다. (Autowired시, 스프링 빈들을 탐색하며 의존성이 주입되기 때문)
정확히는 @Configuration + @Bean을 사용한 수동 방식과, @ComponentScan + @Component를 사용한 자동 방식이다.
자동 방식은 보통 비지니스 로직이 구현된 코드에서 사용하는 것이 좋다. 비지니스 로직은 숫자도 매우 많고, 한번 개발해야 하면 컨트롤러, 서비스, 리포지토리 처럼 어느정도 유사한 패턴이 있다. 이런 경우 자동 기능을 적극 사용하는 것이 좋다. 보통 문제가 발생해도 어떤 곳에서 문제가 발생했는지 명확하게 파악하기 쉽다.
수동 방식은 기술 지원 로직에 사용하는 것이 좋다. 등록되는 빈이 수정될 경우, Application 전반에 영향을 미치기 때문이다. 비지니스 로직은 문제 발생시 어디가 문제인지 명확하게 드러나지만, 기술지원 로직은 파악하기 어려운 경우가 많다. 그에 따라 아래와 같이 기술 지원 로직은 별도 설정 파일을 통해 수동 빈 등록을 사용하여 명확히 드러내는 것이 좋다.
@Configuration
public class DiscountPolicyConfig {
@Bean
public DiscountPolicy rateDiscountPolicy() {
return new RateDiscountPolicy();
}
@Bean
public DiscountPolicy fixDiscountPolicy() {
return new FixDiscountPolicy();
}
}