스프링은 IoC 컨테이너를 통해 적재적소에 빈 객체를 반환해준다.
@Configuration, @Component 어노테이션을 붙여주면 해당 클래스는 스프링 관리 대상이 된다는 점을 보면 크게 차이는 없어보일 수 있다.
어떠한 상황에 @Configuration을 쓰고 어떠한 상황에 @Component를 쓰는 것이 좋을까?
1. @Configuration은 @Component를 포함하고 있다
둘의 차이점을 비교하기 전에, @Configuration이 @Component를 포함하고 있다는 것을 확인하자.
그렇기 때문에 @Configuration 어노테이션이 붙여진 클래스도 @Component 어노테이션이 붙여진 클래스처럼 스프링 컨테이너 관리 대상이다.
참고로 @Component는 아래와 같이 소개돼있다.
Indicates that an annotated class is a "component". Such classes are considered as candidates for auto-detection when using annotation-based configuration and classpath scanning.
Since: 2.5
See Also:
Repository, Service, Controller, org.springframework.context.annotation.ClassPathBeanDefinitionScanner
ClassPathBeanDefinitionScanner는 아래와 같이 소개돼있다.
A bean definition scanner that detects bean candidates on the classpath, registering corresponding bean definitions with a given registry (BeanFactory or ApplicationContext).
@Component가 붙은 클래스들을 찾아 BeanDefinition으로 변환 후 스프링 컨테이너 관리 대상으로 등록되는 것이다.
@Configuration는 @Component를 포함하므로 @Configuration으로 등록된 클래스도 마찬가지로 스프링 컨테이너 관리 대상이 된다.
2. @Configuration은 스프링 빈으로 등록할 객체를 싱글톤으로 반환해준다
스프링 컨테이너는 싱글톤 레지스트리로써, 싱글톤 객체를 관리하는 싱글톤 컨테이너 역할을 한다.
웹 요청은 굉장히 많은 요청으로 이루어지기 때문에 싱글톤이 아닌 프로토타입으로 스프링 빈을 반환하게 된다면, 매 요청마다 새 객체를 생성하여 힙 메모리가 터질 수 있다.
@Configuration을 사용하면 빈을 반환할 때 싱글톤으로 반환할 수 있다.
아래와 같은 코드를 보자.
@Configuration
public class MemberConfig1 {
@Bean
public MemberServiceConfig hello() {
return new MemberServiceConfig();
}
}
@Component
public class MemberConfig2 {
@Bean
public MemberServiceConfig hello() {
return new MemberServiceConfig();
}
}
MemberConfig1에는 @Configuration을, MemberConfig2에는 @Component를 붙여주었다.
@Component 클래스와 달리 @Configuration이 붙은 클래스는 CGLIB 바이트 조작 라이브러리를 통해 CGLIB Proxy 객체가 생성된 것을 확인할 수 있다. 바로 이 CGLIB Proxy 객체가 스프링 빈을 반환할 때 싱글톤이 보장되게 해주는 것이다.
@Configuration 어노테이션을 타고 들어가면 boolean proxyBeanMethods() default true; 메서드가 있다.
이 메서드의 설명에 아래와 같이 되어있다.
Specify whether @Bean methods should get proxied in order to enforce bean lifecycle behavior,
e.g. to return shared singleton bean instances even in case of direct @Bean method calls in user code. This feature requires method interception, implemented through a runtime-generated CGLIB subclass which comes with limitations such as the configuration class and its methods not being allowed to declare final.
The default is true, allowing for 'inter-bean references' via direct method calls within the configuration class as well as for external calls to this configuration's @Bean methods, e.g. from another configuration class.
If this is not needed since each of this particular configuration's @Bean methods is self-contained and designed as a plain factory method for container use, switch this flag to false in order to avoid CGLIB subclass processing. Turning off bean method interception effectively processes @Bean methods individually like when declared on non-@Configuration classes, a.k.a. "@Bean Lite Mode" (see @Bean's javadoc). It is therefore behaviorally equivalent to removing the @Configuration stereotype.
요약하자면 @Configuration을 사용하면 @Bean 메서드들 역시 CGLIB 바이트코드 조작 라이브러리를 통해 싱글톤으로 반환하게 해준다는 것이다. 해당 옵션을 사용하지 않도록 설정할 수 있지만, 이는 @Configuration 성격을 죽이는 것과 다름없다. 라고 되어있다.
CGLIB 내부 바이트코드를 뜯어보는 방법을 찾기 힘들어 직접 뜯어보진 못했지만, 예상 코드는 아래와 같다고 한다.
new MemberServiceConfig()를 반환하는 메서드를 @Bean으로 등록했다고 하더라도,
@Configuration이 아닌 @Component를 사용한다면 Java의 new 키워드때문에 매번 힙 인스턴스로 생성되어 메모리에 부하가 가게 된다. 외부 라이브러리 또는 설정 정보 클래스를 반환하는 클래스일 경우 @Configuration를 꼭 붙여주도록 하자.
+) 22.11.28. 추가
헷갈릴까봐 요약본을 추가한다.
요약 내용이므로 참고하자.
3. @Configuration 사용 예제
대표적인 예로 queryDSL을 사용할 때 JPAQueryFactory를 스프링 컨테이너 관리 대상으로 등록하는 코드에서 볼 수 있다.
queryDSL을 사용하기 위해 new JPAQueryFactory를 해주고 EntityManager를 DI해주는 모습이다.
위 코드만 봐서는 new 키워드가 한번만 쓰이므로 크게 문제없을 것 같지만, 메시지 도메인 뿐만 아니라 회원 도메인, 모임 도메인, 롤링페이퍼 도메인 등등. 다양한 도메인의 repository에서 new JPAQueryFactory() 코드를 작성해주어야 한다. 정작 JPAQueryFactory가 도메인마다 다 다른 JPAQueryFactory일 필요도 없는데 말이다.
하지만 만약 위와 같이 별도의 Config 클래스를 생성하여 @Configuration으로 등록해주고 init 메서드에 @Bean으로 설정해줬다면?
JPAQueryFactory는 스프링 컨테이너 관리 대상이자, CGLIB 바이트코드 조작을 통해 JPAQueryFactory는 싱글톤으로 반환될 것이다.
@Controller, @Service, @Repository, @Component들은 우리 비즈니스 로직에 직접적인 영향을 주는, 우리가 제어할 클래스들에 붙여주고, @Configuration를 통한 수동 빈 등록은 설정 정보 또는 외부 라이브러리를 사용할 때 이용하면 좋아보인다.
해당 내용에 틀린 부분이 있거나 질문이 있다면 댓글로 남겨주세요.
감사합니다 :)
'JAVA > JAVA | Spring 학습기록' 카테고리의 다른 글
[Spring] 여러 포트에서 동일 애플리케이션 실행하기(Gradle, Maven) (0) | 2022.12.22 |
---|---|
[Spring] MapStruct를 이용한 Entity <-> DTO 고찰 (2) (7) | 2022.12.10 |
[Spring] REQUIRED, REQUIRES_NEW 옵션과 Try-Catch (4) | 2022.10.25 |
[Spring] REQUIRES_NEW 옵션만으론 자식이 롤백될 때 부모도 롤백된다 (2) | 2022.10.20 |
[Spring] @Transactional의 전파 레벨에 대해 알아보자 (4) | 2022.10.17 |