만쥬의 개발일기
article thumbnail

Spring으로 개발을 하다보면 @Bean과 @Component를 언제 써야할지 헷갈릴때가 있다.

둘다 목적이 명확하지 않은 Bean을 생성할때 사용하는 어노테이션인데, 정확히 차이를 짚고 넘어가보자.

 

@Bean VS @Component

(@Bean)

 

(@Component)

 

위 예시처럼,  @Bean의 경우 개발자가 컨트롤이 불가능한 외부 라이브러리들을 Bean으로 등록하고 싶은 경우에 사용된다. 

(예를 들면 ObjectMapper의 경우 ObjectMapper Class에 @Component를 선언할수는 없으니 ObjectMapper의 인스턴스를 생성하는 메소드를 만들고 해당 메소드에 @Bean을 선언하여 Bean으로 등록한다.)

 

개발자가 생성한 Class에 @Bean을 선언하는 것 또한 불가능하다.

개발자가 직접 컨트롤이 가능한 Class들의 경우엔 @Component를 사용한다.

 

@Bean과 @Component는 각자 선언할 수 있는 타입이 정해져있어 해당 용도외에는 컴파일 에러를 발생시킨다.

 

 

Configuration 안에 @Bean을 사용하는 이유


@Bean 어노테이션을 이용한 수동 빈 등록

스프링에서는 일반적으로 컴포넌트 스캔을 사용해 자동으로 빈을 등록하는 방법을 이용한다.
하지만 @Bean 어노테이션을 사용하여 수동으로 등록하는 경우도 있다.
대표적으로 다음과 같은 경우이다.

  1. 개발자가 직접 제어 불가능한 라이브러리를 사용할 때
  2. 애플리케이션 전범위적으로 사용되는 클래스를 등록할 때
  3. 다형성을 활용하여 여러 구현체를 등록해야할 때

 

@Bean을 이용한 수동 빈 메서드는 스프링 빈 안에만 구현되어 있다면 모두 동작한다.
하지만 꼭 @Configuration을 활용하기를 강조하는데, 그 이유는 @Configuration에 특별한 부가 기능이 적용되기 때문이다.

@Configuration에 적용된 프록시 패턴

@Configuration 어노테이션 안에는 @Component 어노테이션이 포함되어 있다. 그래서 @Configuration 어노테이션 붙어있는 클래스 역시 빈으로 등록된다.

그럼에도 불구하고 @Configuration을 따로 만든 이유는

CGLib으로 프록시 패턴을 적용해 수동으로 등록하는 스프링 빈이 반드시 싱글톤으로 생성됨을 보장하기 위해서이다.

예를 들어 다음과 같이 스프링 빈으로 등록하고자 하는 클래스가 있다고 하자.

@Component
public class Resource {

}

위의 클래스를 @Component를 이용해 자동으로 빈 등록하면 스프링이 해당 클래스의 객체의 생성을 제어하게 되고 싱글톤 객체로 존재함을 보장해준다.
하지만 위의 클래스를 @Bean을 사용해 직접 빈으로 등록해준다고 하자. 그러면 우리는 다음과 같이 해당 빈 등록 메소드를 여러 번 호출하게 된다.

  • Resource() , MyFirstBean(Resource()) , MySecondBean(Resource())
@Configuration
public class MyBeanConfiguration { 

    @Bean 
    public Resource Resource() {
        return new Resource(); 
    } 
    
    @Bean 
    public MyFirstBean myFirstBean() { 
        return new MyFirstBean(Resource()); 
    } 
    
    @Bean 
    public MySecondBean mySecondBean() { 
        return new MySecondBean(Resource()); 
    } 
}

실수로 위와 같이 빈을 생성하는 메소드를 여러 번 호출하였다면 여러 개의 빈이 생성이 된다.
스프링은 이러한 문제를 방지하고자 @Configuration이 있는 클래스를 객체로 생성할 때 CGLib 라이브러리를 사용해 프록시 패턴을 적용한다.

이를 통해 @Bean이 있는 메소드를 여러 번 호출하여도 항상 동일한 객체를 반환하여 싱글톤을 보장한다.

이를 이해하기 쉬운 코드로 나타내면 다음과 같다.

@Configuration
public class MyBeanConfigurationProxy extends MyBeanConfiguration { 

    private Object source;

    @Override
    public Resource Resource() {
        if (Resource == null) {
            source = super.Resource();
        }

        return source; 
    } 
    
    @Override
    public MyFirstBean myFirstBean() { 
        return super.myFirstBean();
    } 
    
    @Override
    public MySecondBean mySecondBean() { 
        return super.mySecondBean();
    } 
}

CGLib은 상속을 사용해서 프록시를 구현하므로 다음과 같이 프록시가 구현된다고 할 수 있다.
실제로 위와 정확히 같지는 않고 이해를 돕기 위한 코드이다.

 

싱글톤 여부를 제어하기 위한 proxyBeanMethods

대부분의 경우에는 @Bean에 의한 수동 빈으로 등록할 때 싱글톤으로 생성되길 원한다.


하지만 싱글톤으로 생성되길 원하지 않을 경우 @Configuration 어노테이션의 proxyBeanMethods를 false로 주면 된다.
예를 들어 아까 봤던 코드에 위의 옵션을 준다고 하자.

@Configuration(proxyBeanMethods=false)
public class MyBeanConfigurationProxy extends MyBeanConfiguration { 

    private Object source;

    @Override
    public Resource Resource() {
        if (Resource == null) {
            source = super.Resource();
        }

        return source; 
    } 
    
    @Override
    public MyFirstBean myFirstBean() { 
        return super.myFirstBean();
    } 
    
    @Override
    public MySecondBean mySecondBean() { 
        return super.mySecondBean();
    } 
}

그러면 @Bean 메소드를 호출마다 새로운 객체를 생성해준다.
하지만 이렇게 사용할 경우는 거의 없고 @Configuration이 아니라면 빈이 싱글톤임을 보장받을 수 없으므로 @Configuration 안에 @Bean을 사용해주도록 하자.



reference

https://jojoldu.tistory.com/27

https://mangkyu.tistory.com/234

profile

만쥬의 개발일기

@KangManJoo

포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!