[스프링/Spring] 스프링 컨테이너(Spring Container)와 스프링 빈(Spring Bean)에 대해 알아보자.
kindof
·2021. 8. 19. 21:32
📚 0. 들어가면서
이전 포스팅에서 의존관계 주입(DI)가 왜 필요한가에 대해 예제 코드를 가지고 이야기해보았습니다.
궁극적으로 DI는 객체지향 설계의 원칙인 DIP와 OCP를 지키기 위한 노력에서 탄생했다는 것을 알 수 있었습니다.
이번 시간에는 예전에 자바로 작성했던 AppConfig를 스프링을 이용해 리팩토링해보고, 이 때 사용되는 스프링 컨테이너와 빈(Bean)의 개념에 대해 공부해보려고 합니다.
💻 1. 예제 코드
우선 아래처럼 자바 기반으로 작성한 코드가 있다고 해봅시다.
MemberRepository는 멤버를 저장하는 데이터베이스이고 discountPolicy는 어떤 물건을 살 때 적용되는 할인 정책입니다. 그리고 orderService를 통해 멤버는 주문을 하게 됩니다.
지금은 AppConfig라는 객체를 통해 의존관계 주입(생성자 주입)을 하고 있습니다.
public class AppConfig {
public MemberService memberService() {
return new MemberServiceImpl(memberRepository());
}
public MemberRepository memberRepository(){
return new MemoryMemberRepository();
}
public OrderService orderService(){
return new OrderServiceImpl(memberRepository(), discountPolicy());
}
public DiscountPolicy discountPolicy() {
return new RateDiscountPolicy();
// return new FixedDiscountPolicy();
}
}
이를 통해 OrderService의 구현 객체(OrderServiceImpl)는 추상화(MemberRepository, DiscountPolicy 등 인터페이스)에만 의존할 수 있게 되었습니다.
public class OrderServiceImpl implements OrderService{
// private final DiscountPolicy discountPolicy = new FixDiscountPolicy(); // 고정 할인 정책
// private final DiscountPolicy discountPolicy = new RateDiscountPolicy(); // 비율 할인 정책
private final MemberRepository memberRepository;
private final DiscountPolicy discountPolicy;
// OrderServiceImpl 입장에서 생성자를 통해 어떤 구현 객체가 주입될지는 알 수 없다. - AppConfig에서 결정
public OrderServiceImpl(MemberRepository memberRepository, DiscountPolicy discountPolicy){
this.memberRepository = memberRepository;
this.discountPolicy = discountPolicy;
}
...
...
}
public class orderServiceTest {
MemberService memberService;
OrderService orderService;
@BeforeEach
public void beforeEach(){
// 의존관계 주입
AppConfig appConfig = new AppConfig();
MemberService memberService = appConfig.memberService();
OrderService orderService = appConfig.OrderService();
}
...
...
}
이제 위 예시 코드까지 흐름을 이해했다고 가정하고, 지금까지 작성한 자바 기반의 AppConfig를 스프링을 이용해 바꿔보겠습니다.
📋 2. 스프링 컨테이너
위에서 작성한 AppConfig를 스프링을 이용해 작성하면 아래와 같이 바꿀 수 있습니다.
아래 코드에서 @Configuration과 @Bean을 주목해주세요.
@Configuration
public class AppConfig {
@Bean
public MemberService memberService() {
return new MemberServiceImpl(memberRepository());
}
@Bean
public MemberRepository memberRepository(){
return new MemoryMemberRepository();
}
@Bean
public OrderService orderService(){
return new OrderServiceImpl(memberRepository(), discountPolicy());
}
@Bean
public DiscountPolicy discountPolicy() {
return new RateDiscountPolicy();
// return new FixedDiscountPolicy();
}
그리고 이전에 구현했던 OrderServiceImpl을 사용할 때는 아래처럼 사용할 수 있습니다. 아래 코드에서는 ApplicationContext에 대해 주목해주세요.
public class OrderServiceImpl implements OrderService{
private final MemberRepository memberRepository;
private final DiscountPolicy discountPolicy;
public OrderServiceImpl(MemberRepository memberRepository, DiscountPolicy discountPolicy){
this.memberRepository = memberRepository;
this.discountPolicy = discountPolicy;
}
...
...
}
public class OrderApp {
public static void main(String[] args) {
// 의존관계 주입
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);
MemberService memberService = applicationContext.getBean("memberService", MemberService.class);
OrderService orderService = applicationContext.getBean("orderService", OrderService.class);
...
...
}
}
(1)에서 써놨던 예제 코드가 스프링 코드로 작성되었을 때 달라진 점은 @Configuration, @Bean, ApplicationContext의 등장입니다.
여기서 "ApplicationContext"를 스프링 컨테이너라고 하며, 기존에는 개발자가 AppConfig를 사용해서 직접 객체를 생성하고 DI를 했지만, 이제부터는 스프링 컨테이너의 도움을 받게 됩니다.
스프링 컨테이너는 어노테이션 기반의 @Configuration이 붙은 @AppConfig를 설정 정보로 사용하게 되고, 여기서 @Bean이라 적힌 메서드를 모두 호출해서 반환된 객체를 스프링 컨테이너에 등록합니다.
그리고 이렇게 스프링 컨테이너에 등록된 객체를 스프링 빈(Spring Bean)이라고 합니다.
즉, new AnnotationConfigApplicationContext(AppConfig.class)를 통해 스프링 컨테이너가 생성이 되고 AppConfig.class를 통해 구성 정보(스프링 빈)를 등록해주게 되는 것입니다. 그림으로 보면 아래와 같습니다.
이후에 스프링 컨테이너는 설정 정보를 참고해서 의존관계를 주입(DI)합니다.
3. 스프링 컨테이너 확인해보기
실제로 등록된 스프링 빈을 확인하는 테스트 코드를 작성해보겠습니다.
public class ApplicationContextInfoText {
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);
@Test
@DisplayName("애플리케이션 빈 출력하기")
void findApplicationBean(){
String[] beanDefinitionNames = ac.getBeanDefinitionNames();
for (String beanDefinitionName : beanDefinitionNames) {
BeanDefinition beanDefinition = ac.getBeanDefinition(beanDefinitionName);
// Role ROLE_APPLICATION : 직접 등록한 애플리케이션 빈
// Role ROLE_INFRASTRUCTURE : 스프링이 내부에서 사용하는 빈
if(beanDefinition.getRole() == BeanDefinition.ROLE_APPLICATION){
Object bean = ac.getBean(beanDefinitionName);
System.out.println("beanDefinitionName = " + beanDefinitionName + " object = " + bean);
}
}
}
}
getBeanDefinitionNames()는 해당 스프링 컨테이너에 등록된 스프링 빈의 이름을 string 배열 형태로 리턴해주며 getBean(빈 이름, 타입)을 통해 스프링 컨테이너에서 스프링 빈을 조회할 수 있게 해줍니다.
위 코드에서 ROLE_APPLICATION으로만 조회했기 때문에 사용자가 직접 등록한 애플리케이션 빈만 결과로 나타나게 되고, 결과는 아래와 같습니다.
지금까지 스프링 컨테이너와 스프링 빈, 그리고 등록된 스프링 빈을 조회하는 것까지 공부했습니다.
다음 포스팅에서 스프링 컨테이너와 스프링 빈을 어떻게 사용하는지, 이게 왜 필요한지에 대해 정리해보겠습니다.
4. Reference
- 자바 ORM 표준 JPA 프로그래밍(에이콘 오픈 소스 프로그래밍 시리즈) | 김영한 | 에이콘출판
'Spring & Springboot' 카테고리의 다른 글
[스프링/Spring] 의존관계 주입(DI)은 어떻게 할 수 있을까? - 여러가지 의존관계 주입 방법 (0) | 2021.08.25 |
---|---|
[스프링/Spring] 싱글톤(Singleton) 패턴과 스프링 컨테이너 (0) | 2021.08.23 |
[Spring/React] CORS? CORS에러를 해결해보자 (3) | 2021.08.13 |
[스프링/Spring] 객체 지향 설계와 DI(Dependency Injection)의 시작(feat. 생성자 주입) (0) | 2021.08.08 |
[스프링/Spring] 스프링(Spring)은 왜 만들어졌는가? (0) | 2021.08.02 |