스프링 빈등록에 대해

yboy·2022년 5월 23일
1

Learning Log 

목록 보기
7/41
post-thumbnail

학습동기

우아한테크코스 미션을 하는 중에 무심코 사용하던 스프링 빈 등록에 대해 궁금하게 되었다.
'스프링 컨테이너가 등록된 빈을 관리해준다.' ?
무지성으로 사용하던 @Component(@Controller, @Servicem @Repository) 그리고 @Autowired.....
근데 @Configuration 이랑 @Bean은 뭐지???
한 번 파해쳐 보자!

학습내용

@Component

우선 컴포넌트에 대해 살펴 보자.

@Component 에노테이션은 해당 클래스를 스캔 대상으로 표시한다.

스캔 대상으로 지정한다는 게 무슨 말이지??
-> 빈들을 관리해주는 스프링 컨테이너는 설정 클래스에서 정보를 읽어와 빈 객체를 생성하고 각 빈을 의존 주입하는 작업을 수행한다.
여기서 빈을 생성할 때, 생성해야 할 빈들을 먼저 스캔하게 된다.

스캔할 때, @Component 애노테이션이 붙은 Class는 스캔의 대상이 되고 빈으로 등록된다.

우리가 자주 사용하는 @Controller, @Service, @Repository 애노테이션은 모두 @Component 애노테이션을 포함하고 있다.

하지만 @Component 애노테이션은 '스캔 대상이야.'라고 만 알려줄 뿐 애노테이선을 달아 놓았다고 해서 자동으로 빈으로 등록되는 것은 아니다.

@Component 애노테이션을 붙인 클래스를 스캔해서 스프링 빈으로 등록하려면 설정 클래스에 @ComponentScan 애노테이션을 적용해야 한다.

@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은 설정 파일임을 알려주는 것이야.' 라고 만 알고 있으면 안된다.

@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

이제 @Bean에 대해 알아보자.

빈을 스프링 컨테이너에 등록하기 위한 애노테이션이다.

그럼 스프링 빈은 무엇일까?
: 스프링 컨테이너(Ioc 컨테이너 또는 DI 컨테이너)에 의해 관리되는 객체를 빈이라고 부른다.

@Component vs @Bean

이제 껏 스프링 컨테이너에 빈을 등록하는 방법에 대해 알아봤는데
그럼 스캔의 대상으로 지정해 주는 @Component와 @Bean의 차이점은 무엇일까?

@Bean with @Configuration

  • 수동으로 스프링 컨테이너에 빈을 등록하는 방법
  • 개발자가 직접 제어가 불가능한 라이브러리를 빈으로 등록할 때 불가피하게 사용
  • 한 개 이상의 @Bean을 제공하는 클래스의 경우 반드시 @Configuration을 명시해 주어야 싱글톤이 보장됨

@Component

  • 자동으로 스프링 컨테이너에 빈을 등록하는 방법
  • 스프링의 컴포넌트 스캔 기능이 @Component 어노테이션이 있는 클래스를 자동으로 찾아서 빈으로 등록함
  • @Component 하위 어노테이션으로 @Configuration, @Controller, @Service, @Repository 등이 있음

마무리

스프링에 빈을 등록하는 방법에 대해 학습해 보았다. 이를 통해 무지성으로 사용하던 애노테이션들(@Component, @Controller, @Service, @Repository...)에 대해 보다 더 깊이 있게 알게 되었다. 이들이 비록 간단한 애노테이션들이지만 사용할 때, '내가 알고 있다.' 라는 마인드로 프로그래밍을 할 수 있게 된 것 같다.

0개의 댓글