우아한테크코스 미션을 하는 중에 무심코 사용하던 스프링 빈 등록에 대해 궁금하게 되었다.
'스프링 컨테이너가 등록된 빈을 관리해준다.' ?
무지성으로 사용하던 @Component(@Controller, @Servicem @Repository) 그리고 @Autowired.....
근데 @Configuration 이랑 @Bean은 뭐지???
한 번 파해쳐 보자!
우선 컴포넌트에 대해 살펴 보자.
@Component 에노테이션은 해당 클래스를 스캔 대상으로 표시한다.
스캔 대상으로 지정한다는 게 무슨 말이지??
-> 빈들을 관리해주는 스프링 컨테이너는 설정 클래스에서 정보를 읽어와 빈 객체를 생성하고 각 빈을 의존 주입하는 작업을 수행한다.
여기서 빈을 생성할 때, 생성해야 할 빈들을 먼저 스캔하게 된다.
스캔할 때, @Component 애노테이션이 붙은 Class는 스캔의 대상이 되고 빈으로 등록된다.
우리가 자주 사용하는 @Controller, @Service, @Repository 애노테이션은 모두 @Component 애노테이션을 포함하고 있다.
하지만 @Component 애노테이션은 '스캔 대상이야.'라고 만 알려줄 뿐 애노테이선을 달아 놓았다고 해서 자동으로 빈으로 등록되는 것은 아니다.
@Component 애노테이션을 붙인 클래스를 스캔해서 스프링 빈으로 등록하려면 설정 클래스에 @ComponentScan 애노테이션을 적용해야 한다.
그럼 이제 @ComponentScan에 대해 알아보자.
@ComponentScan 에노테이션은 @Component 애노테이션이 붙은 클래스를 스캔하여 빈을 생성하도록 도와준다.
예시를 들어서 알아보자.
우선 @Component로 스캔 대상이 될 class를 만들어 보자.
package wooteco.subway.repository;
@Component
public class StationRepository {
private final JdbcTemplate jdbcTemplate;
public StationRepository(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
public Station save(Station station) {
...
return new Station(id, station.getName());
}
이제 @ComponentScan을 이용하여 @Component 애노테이션이 붙은 class를 스캔할 수 있도록 해보자.
@Configuration
@PropertySource("classpath:application.properties")
@ComponentScan("ooteco.subway.repository")
public class Configuration {
private final Environment env;
public PropertySourceConfig(Environment env) {
this.env = env;
}
@Bean
public JwtTokenKeyProvider jwtTokenKeyProvider() {
return new JwtTokenKeyProvider(env.getProperty("security-jwt-token-secret-key"));
}
...
}
마지막으로 아래 코드를 이용해 스프링 컨테이너를 생성하여 확인해 보면 Bean으로 해당 class(StationRepository)가 등록 되었다는 것을 확인 할 수 있다.
class ComponentTest {
@Test
void key() {
ApplicationContext context = new AnnotationConfigApplicationContext(Configuration.class);
StationRepository stationRepository = context.getBean(StationRepository.class);
assertThat(stationRepository).isNotNull();
}
}
근데 위에서 사용한 @Configuration은 뭐고 @Bean은 뭘까? 두 가지에 대해서도 알아 보도록 하자.
'스프링 컨테이너는 설정 클래스에서 정보를 읽어와 빈 객체를 생성하고 각 빈을 의존 주입하는 작업을 수행한다.'
앞에서 언급한 내용인데 여기서 '설정 파일'임을 암시해 주는 것이 @Configuration 애너테이션이다. 하지만' @Configuration은 설정 파일임을 알려주는 것이야.' 라고 만 알고 있으면 안된다.
@Configuration 에노테이션은 설정 파일을 만들고 빈을 등록하기 위한 애노테이션인 동시에 빈이 등록될 때 singleton이 되도록 보장해준다.
예시를 들어 알아보자.
@Configuration
public class Configuration {
private final Environment env;
@Bean
public StatinoRepository stationRepository() {
return new StationRepository();
}
@Bean
public StatinoService stationService() {
return new StationService(stationRepository());
}
}
위와 같이 @Configuration이 적용된 class가 있다.
이를 다음과 같이 테스트 헤보면
@Test
void Test() {
ApplicationContext context = new AnnotationConfigApplicationContext(PropertySourceConfig.class);
PropertySourceConfig config = context.getBean(PropertySourceConfig.class);
System.out.println(config);
}
StationRepository, StationService 두 개가 출력된다. 하지만 @Configuration을 빼면 StationRepository, StationService, StationRepository 세 개가 출력된다.
여기서 @Bean만 사용해도 스프링 빈으로 등록은 되지만 싱글톤은 보장해주지 않는다는 것을 알 수 있다.
@Bean 애노테이션에 대해 알아보기 전에 이쯤에서 궁금증이 생겼을 수도 있는데....
우리가 스프링을 사용하면서 @Component의 일부인 @Controller, @Service, @Repository는 어떤 설정 파일(@Configuration이 붙은)에서 스캔이 이루어져서 빈으로 등록되고 있는 걸까?
우리가 스프링을 사용할 때
@SpringBootApplication
public class SubwayApplication {
public static void main(String[] args) {
SpringApplication.run(SubwayApplication.class, args);
}
}
위와 같이 @SpringBootApplication 애노테이션이 붙은 class를 실행해서 사용하고 있다는 것은 모두 알고 있을 것이다.
그럼 @SpringBootApplication을 한 번 살펴보자.
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
...
}
해당 애노태이션의 내부인데 @ComponentScan
이 달려 있는 것이 확인가능하다. @Configuration
은 @SpringBootConfiguration을 타고 들어가면 확인가능하다!
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
@Indexed
public @interface SpringBootConfiguration {
...
}
이제 @Bean에 대해 알아보자.
빈을 스프링 컨테이너에 등록하기 위한 애노테이션이다.
그럼 스프링 빈은 무엇일까?
: 스프링 컨테이너(Ioc 컨테이너 또는 DI 컨테이너)에 의해 관리되는 객체를 빈이라고 부른다.
이제 껏 스프링 컨테이너에 빈을 등록하는 방법에 대해 알아봤는데
그럼 스캔의 대상으로 지정해 주는 @Component와 @Bean의 차이점은 무엇일까?
스프링에 빈을 등록하는 방법에 대해 학습해 보았다. 이를 통해 무지성으로 사용하던 애노테이션들(@Component, @Controller, @Service, @Repository...)에 대해 보다 더 깊이 있게 알게 되었다. 이들이 비록 간단한 애노테이션들이지만 사용할 때, '내가 알고 있다.' 라는 마인드로 프로그래밍을 할 수 있게 된 것 같다.