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 어노테이션을 사용하여 수동으로 등록하는 경우도 있다.
대표적으로 다음과 같은 경우이다.
- 개발자가 직접 제어 불가능한 라이브러리를 사용할 때
- 애플리케이션 전범위적으로 사용되는 클래스를 등록할 때
- 다형성을 활용하여 여러 구현체를 등록해야할 때
@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
'♾️Language & Framework > ☕Java' 카테고리의 다른 글
[Java 기술 면접 대비] - 5. extends VS implements (0) | 2023.11.09 |
---|---|
[Java] - Lombok이란? (0) | 2023.11.09 |
[Java 기술 면접 대비] - 3. Java Bean VS Spring Bean (0) | 2023.11.07 |
[Java 기술 면접 대비] - 2. 제네릭 메서드(Generic Method)란? (0) | 2023.10.21 |
[Java 기술 면접 대비] - 1. JDK vs JRE (1) | 2023.10.20 |