Spring Boot Bean 사용법 - Spring Boot Bean sayongbeob

[Spring] 스프링 빈(Bean)의 개념과 생성 원리

빈(Bean)

Spring IoC 컨테이너가 관리하는 자바 객체를 빈(Bean)이라는 용어로 부른다.

우리가 new 연산자로 어떤 객체를 생성했을 때 그 객체는 빈이 아니다.

ApplicationContext.getBean()으로 얻어질 수 있는 객체는 빈이다.

즉 Spring에서의 빈은 ApplicationContext가 알고있는 객체, 즉 ApplicationContext가 만들어서 그 안에 담고있는 객체를 의미한다.

어떻게 Spring IoC 컨테이너에 빈을 등록할까?

빈을 만드는 방법은 다양하지만 기본적으로 크게 두가지 방법이 있다.

① Component Scanning

② 빈 설정파일에 직접 빈을 등록

Component Scan

@ComponentScan 어노테이션과 @Component 어노테이션을 사용해서 빈을 등록하도록 하는 방법이다.

간단히 말하면 @ComponentScan 어노테이션은 어느 지점부터 컴포넌트를 찾으라고 알려주는 역할을 하고 @Component는 실제로 찾아서 빈으로 등록할 클래스를 의미한다.

Spring IoC 컨테이너가 IoC 컨테이너를 만들고 그 안에 빈을 등록할때 사용하는 인터페이스들을 라이프 사이클 콜백이라고 부른다.

라이프 사이클 콜백 중에는 @Component 애노테이션을 찾아서 이 애노테이션이 붙어있는 모든 클래스의 인스턴스를 생성해 빈으로 등록하는 작업을 수행하는 어노테이션 프로세서가 등록돼있다.

Spring Boot 프로젝트에서 @ComonentScan 애노테이션이 붙어있는 클래스가 이에 해당한다.

다음은 Spring의 PetClinic 예제 소스이다.

Spring Boot Bean 사용법 - Spring Boot Bean sayongbeob

PetClinicApplication 클래스에 @SpringBootApplication 애노테이션이 붙어있는데

Spring Boot Bean 사용법 - Spring Boot Bean sayongbeob

이 애노테이션은 내부적으로 @ComponentScan 애노테이션을 사용한다.

@ComponentScan 애노테이션은 어디서부터 컴포넌트를 찾아볼 것인지 알려주는 역할을 한다.

@ComponentScan이 붙어있는 클래스가 있는 패키지에서부터 모든 하위 패키지의 모든 클래스를 훑어보며 @Component 애노테이션(또는 @Component 애노테이션을 사용하는 다른 애노테이션)이 붙은 클래스를 찾는다.

Spring이 IoC 컨테이너를 만들때 위와 같은 과정을 거쳐 빈으로 등록해주는 것이다.

Spring의 PetClinic 예제에서

Spring Boot Bean 사용법 - Spring Boot Bean sayongbeob

위의 클래스는 @Controller 애노테이션이 붙어있는데

Spring Boot Bean 사용법 - Spring Boot Bean sayongbeob

내부적으로 @Component 애노테이션을 사용한다.

즉 OwnerController는 Spring에 의해 IoC 컨테이너에 빈으로 등록된다.

빈 설정파일에 직접 빈을 등록하는 방법

위와 같이 @Component 애노테이션을 사용하는 방법 말고도 빈 설정파일에 직접 빈으로 등록할 수 있다.

빈 설정파일은 XML과 자바 설정파일로 작성할 수 있는데 최근 추세는 자바 설정파일을 좀 더 많이 사용한다.

자바 설정파일은 자바 클래스를 생성해서 작성할 수 있으며 일반적으로 xxxxConfiguration와 같이 명명한다.

그리고 클래스에 @Configuration 애노테이션을 붙인다.

그 안에 @Bean 애노테이션을 사용해 직접 빈을 정의한다.

@Configuration

public class SampleConfiguration {

@Bean

public SampleController sampleController() {

return new SampleController;

}

}

cs

sampleController()에서 리턴되는 객체가 IoC 컨테이너 안에 빈으로 등록된다.

물론 이렇게 빈을 직접 정의해서 등록하면 @Component 애노테이션을 붙이지 않아도 된다.

@Configuration 애노테이션을 보면 이 애노테이션도 @Component를 사용하기 때문에 @ComponentScan의 스캔 대상이 되고 그에 따라 빈 설정파일이 읽힐때 그 안에 정의한 빈들이 IoC 컨테이너에 등록되는 것이다.

References

인프런 - 백기선님의 예제로 배우는 스프링 입문(개정판)을 수강하며 정리한 포스팅입니다.

Spring Boot를 사용하면 자연스럽게 Spring Bean을 직접 생성하고 사용할 일이 많아진다. 따라서 우리는 Spring Bean을 생성하는 방법은 당연히 알고 있어야 하고 더 나아가 어떠한 방식으로 등록되는지 알고 있어야 한다.

이 글에서 개발자가 생성하거나 Spring Boot가 제공하는 Spring Bean을 어떻게 사용할 수 있게 되는지 간략하게 알아보도록 하자.

개발자가 직접 Spring Bean을 만드는 방법은 두 가지가 있다.

@Component 어노테이션을 이용하는 방법과 @Bean 어노테이션을 이용하는 방법이다.

@Component 어노테이션의 경우 아래와 같이 클래스에 명시해 주면 된다.

@Component
public class MyCustomComponent {
    private final String name = "my custom component";
}

@Bean 어노테이션은 아래와 같이 @Configuration이 등록된 클래스 내부 메서드에 명시해 주면 된다.

public class MyCustomBean {
    private final String name = "my custom bean";
}

@Configuration
public class MyBeanConfiguration {
    @Bean
    public MyCustomBean myCustomBean() {
        return new MyCustomBean();
    }
}

Spring Bean의 등록

그렇다면 개발자가 작성하거나 Spring Boot가 제공하는 Spring Bean은 어떻게 등록되는 것일까?

Spring Bean이 등록되는 방법은 두 가지가 있다.

Component Scan과 Auto Configuration 방식이다.

Component Scan

component scan 방식은 이름에서 알 수 있듯 @Component 어노테이션이 작성된 클래스를 Spring Bean으로 등록하는 방식이다. 그리고 @Bean 어노테이션을 이용해서 반환하는 객체들도 Spring Bean으로 등록을 한다.

이렇게 @Component 어노테이션과 @Bean 어노테이션을 사용하기만 했는데 자동으로 Spring Bean으로 등록되고 사용할 수 있는 이유는 @ComponentScan이 동작하기 때문이다.

개발자가 프로젝트를 생성하고 @Component, @Bean 어노테이션을 사용은 했지만 @ComponentScan을 직접 작성한 적은 없다. 그럼에도 불구하고 component scan 기능이 동작하는 이유는 @ComponentScan 어노테이션이 main 메서드가 있는 Application 클래스에 같이 생성된 @SpringBootApplication 어노테이션에 포함되어 있기 때문이다.

Spring Boot Bean 사용법 - Spring Boot Bean sayongbeob

Spring Boot Bean 사용법 - Spring Boot Bean sayongbeob

@ComponentScan 어노테이션은 value 혹은 basePackages의 값으로 component scan의 시작지점(패키지 경로)을 명시할 수 있다. 시작지점으로 지정된 패키지를 포함하여 하위 패키지에 있는 @Component 어노테이션과 @Bean 어노테이션을 찾아서 Spring Bean으로 등록을 한다. 만약 value 혹은 basePackages을 명시하지 않는다면 @ComponentScan이 작성된 패키지 아래의 모든 @Component, @Bean 어노테이션을 찾아서 Spring Bean으로 등록한다.

Spring docs - @ComponentScan

Either basePackageClasses() or basePackages() (or its alias value()) may be specified to define specific packages to scan. If specific packages are not defined, scanning will occur from the package of the class that declares this annotation.

@SpringBootApplication 어노테이션에 포함되어 있는 @ComponentScanvaluebasePackages도 값이 지정되어있지 않기 때문에 Application 클래스가 위치한 패키지를 포함한 하위 패키지를 component scan 대상으로 하게 된다. 따라서 개발자가 @Component@Bean을 이용해서 만든 객체가 Spring Bean으로 등록될 수 있는 것이다.

Auto Configuration

auto configuration 방식은 Spring Boot가 제공하는 클래스를 Spring Bean으로 등록하는 방법이다. @ComponentScan과 마찬가지로 @SpringBootApplication 어노테이션에 포함된 @EnableAutoConfiguration 어노테이션 덕에 Spring Boot가 제공하는 클래스를 Spring Bean으로 사용 할 수 있다.

Spring Boot Bean 사용법 - Spring Boot Bean sayongbeob

Spring Boot Bean 사용법 - Spring Boot Bean sayongbeob

@EnableAutoConfiguration은 spring-boot-autoconfigure-[version].jar/META-INF/spring.factories 내부에 명시된 클래스들의 풀 패키지 경로를 기반으로 Bean을 초기화한다.

Spring Boot Bean 사용법 - Spring Boot Bean sayongbeob

만약 spring.factories 에 명시된 클래스 정보를 불러오는 부분을 직접 확인해 보고 싶다면 AutoConfigurationImportSelector.getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) 부분 부터 IDE의 debug 기능을 이용해서 천천히 살펴보면 된다.

Spring Boot Bean 사용법 - Spring Boot Bean sayongbeob

Condition

모든 일에 순서가 있듯이 Spring Bean을 생성하는 것도 순서가 있다. Spring Boot는 component scan 이후 auto configuration 순으로 Spring Bean을 초기화한다.

기본적으로 Spring Bean은 Singleton scope를 가지는 단일 객체로 생성이 된다. 그런데 개발자가 @Component, @Bean 어노테이션을 이용해서 만든 Spring Bean이 auto configuration 대상이라면 어떻게 될까? 개발자가 생성한 Spring Bean이 auto configuration으로 인해 덮어써 지거나 error가 발생하면서 개발자가 원하는 Spring Bean이 생성되지 않는 문제가 발생할 것이다.

이러한 문제를 해결하기 위해서 Spring 4.0부터 @Conditional 계열 어노테이션(ex. @ConditionalOnMissingBean…)이 등장해서 Spring Bean 생성과 사용의 유연성을 제공해준다.

맺으며

필자는 Multi module 프로젝트를 만들면서 생성한 객체가 @Component@Bean으로 명시해줬음에도 Spring Bean으로 만들어지지 않아 한번 고생한 적이 있다. 그 당시 어떻게 Spring Boot가 Spring Bean을 탐색해서 만드는지에 대한 이해가 부족했다. 이 글을 읽은 독자는 필자와 같은 고생을 하지 않길 바란다.

참고

https://docs.spring.io/spring-boot/docs/current/reference/html/spring-boot-features.html#boot-features-understanding-auto-configured-beans