스프링에서 빈을 등록할 때 크게 2가지 방법이 있다.
1) 클래스마다 @Controller
, @Service
, @Repository
와 같은 어노테이션 붙여주기
2) @Configuration
클래스 내부 메서드에 @Bean
어노테이션 붙여주기
2번째 방법으로 빈을 등록 시 어떻게 스프링 컨테이너가 싱글톤으로 빈을 관리하는지에 대해서 알아보려고 한다.
아래와 같은 @Configuration
클래스를 통해 빈 등록을 할때, stationRepository()
는 stationService를 만들 때 한 번, lineService를 만들 때 한 번, 총 2번이 호출 된다.
@Configuration
public class AppConfig {
@Bean
public StationService stationService() {
return new StationServiceImpl(stationRepository());
}
@Bean
public StationRepository stationRepository() {
return new JdbcStationRepository();
}
@Bean
public LineService lineService() {
return new LineServiceImpl(stationRepository(), lineRepository());
}
// ...
}
그럼 stationService에서 쓰이는 stationRepository()
와 lineService에서 쓰이는 stationRepository()
는 new로 생성되기 때문에 서로 다른 인스턴스라고 생각할 수 있다.
하지만 주소값을 찍어보면 서로 같은 인스턴스라는 것을 알 수 있다.
이것이 어떻게 가능할까?
스프링 컨테이너는 기본적으로 빈을 싱글톤으로 관리해준다. 하지만 위의 구조에서는 new 연산이 2번 불린다. 이를 싱글톤으로 관리해주기 위해서 스프링은 @Configuration
어노테이션이 붙어 있다면, 바이트코드를 조작해서 싱글톤으로 빈을 관리한다.
@Configuration
어노테이션이 붙어 있는 클래스 자체도 빈으로 등록된다. 이를 찍어보면 클래스 명 뒤에EnhancerBySpringCGLIB
가 붙게된다.(@Configuration
대신 @Component
등의 다른 어노테이션을 붙여주면 클래스명만 찍히는 걸 확인할 수 있다.)
// @Configuration
class wooteco.subway.AppConfig$$EnhancerBySpringCGLIB$$58ae7df8
// @Component
class wooteco.subway.AppConfig
즉, @Configuration
어노테이션이 붙은 클래스는 스프링에 의해 빈으로 등록될 때 우리가 만든 클래스가 등록되는 것이 아니라 스프링에 의해 조작된 클래스가 빈으로 등록된다.
@Configuration
어노테이션이 붙은 클래스는 CBLIB을 이용하여 우리가 만든 클래스를 상속받아 임의의 클래스를 만들어주고, 그 클래스가 빈으로 등록되는 것이다. 이 조작된 빈이 싱글톤을 보장해준다.
조작된 클래스는 @Bean
어노테이션이 붙어있는 메서드가 실행될 때, 스프링 컨테이너에 해당 빈이 존재하면 그 빈을 찾아서 반환해주고, 없다면 새로 생성해서 빈으로 등록해주고 반환해준다.
내부 메서드에 @Bean
이 붙어 있다면, @Configuration
이 없더라도 빈으로 등록은 되지만, 싱글톤은 보장되지 않는다.
CGLIB : 바이트 코드 조작 라이브러리. 런타임에 동적으로 자바 클래스의 프록시를 생성해주는 기능을 제공.
참고로 @Bean
이 있다고 다 빈으로 등록되는 건 아니다. ApplicationContext에 직접 설정파일을 줄 땐, 클래스 레벨에 @Component(@Configuration, @Controller, @Service, @Repository)
가 없어도 @Bean
이 있으면 빈으로 등록해주지만(설정 파일 자체를 넘겨주기 때문), 컴포넌트 스캔으로 빈 등록할 땐, @Component
가 붙어있지 않으면 아에 클래스를 인식하지 못하기 때문에 클래스 내부 메서드에 @Bean
이 있어도 빈으로 등록되지 않는다.