스프링 Core - 스프링 컨테이너와 스프링 빈
스프링 컨테이너
ApplicationContext를 스프링 컨테이너라고 한다.
이는 인터페이스로 어노테이션 기반의 자바 설정 클래스로 만들 수도 있고(AnnotationConfigApplicationContext)
XML 기반으로 스프링 컨테이너를 만들 수도 있다.(GenericXmlApplicationContext)
최근에는 XML 기반 설정은 잘 사용하지 않으나 기존의 많은 레거시 프로젝트들이 XML로 되어있는 경우가 많다.
BeanFactory는 스프링 컨테이너의 최상위 인터페이스로 스프링 빈을 관리하고 조회하는 역할을 담당한다.
getBean() 등 대부분의 기능을 BeanFactory에서 제공한다.
ApplicationContext는 BeanFactory 기능을 모두 상속받아 사용하는데, 대체로 BeanFactory를 사용하기보다
ApplicationContext를 사용한다.
이유는 애플리케이션을 개발할 때 부가적인 기능을 ApplicationContext에서 제공하기 때문이다.
스프링 컨테이너 생성 과정
1. 스프링 컨테이너 생성
AppConfig.class 작성을 통해 스프링 컨테이너를 생성한다.
그리고 어노테이션 기반 자바 설정 또는 XML 기반 설정을 통해 해당 구성 정보를 활용하도록 지정한다.
ex) ApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);
2. 스프링 빈 등록
스프링 컨테이너에 설정 클래스(AppConfig) 정보를 사용해 스프링 빈을 등록한다.
(빈 이름은 메서드 이름으로 기본 설정되며, @Bean(name="xxx") 를 통해 직접 설정할 수도 있다.)
주의할 점은 빈 이름이 항상 달라야 한다는 것이다. 같은 이름을 부여하면, 하나의 빈을 제외한 빈들이 무시되거나 기존 빈이 덮여쓰여 사라질 수 있기 때문이다.
최근에는 스프링 부트가 기본 설정으로 같은 이름으로 설정된 빈이 있으면 오류 코드를 내보낸다.(설정 변경 가능)
3. 스프링 빈 의존관계 설정
먼저 스프링은 빈을 생성하고 난 후에, (순서대로 이루어짐)
AppConfig의 설정 정보를 참고해서 스프링 컨테이너가 의존관계를 주입한다.(Dependency Injection)
단순히 자바 코드만을 호출하는 것 같지만, 스프링 빈으로 등록되어 사용되는 객체들은 모두 싱글톤 객체이다.
어디서 몇 번을 호출되든, 모두 같은 하나의 객체가 사용되는 것이다.
이는 데이터를 보관하는데 있어 호출할 때마다 별도의 객체를 만들지 않아도 되어 메모리 사용면에서 효율적이며
하나의 객체만을 공유해서 사용하기 때문에 데이터가 다중으로 존재하지 않아 데이터 무결성을 지킬 수 있다.
컨테이너에 등록된 빈 조회
class ApplicationContextBasicFindTest {
ApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);
@Test
@DisplayName("빈 이름으로 조회")
void findBeanByName() {
MemberService memberService = ac.getBean("memberService", MemberService.class);
assertThat(memberService).isInstanceOf(MemberServiceImpl.class);
}
@Test
@DisplayName("이름 없이 타입만으로 조회")
void findBeanByType() {
MemberService memberService = ac.getBean(MemberService.class);
assertThat(memberService).isInstanceOf(MemberServiceImpl.class);
}
@Test
@DisplayName("구체 타입으로 조회")
void findBeanByName2() {
MemberServiceImpl memberService = ac.getBean("memberService",
MemberServiceImpl.class);
assertThat(memberService).isInstanceOf(MemberServiceImpl.class);
}
@Test
@DisplayName("빈 이름으로 조회했는데 컨테이너에 존재하지 않을 경우")
void findBeanByNameX() {
Assertions.assertThrows(NoSuchBeanDefinitionException.class,
() ->ac.getBean("xxxxx", MemberService.class));
}
}
정의한 스프링 컨테이너인 ac에서 getBean() 메서드를 통해 빈 객체를 호출할 수 있다.
getBean() 메소드로 호출할 때는 빈 이름, 타입, 구체 타입, 빈 이름과 타입 을 매개 변수로 넣어 호출한다.
다만 주의할 점은타입으로 호출 시 부모타입으로 호출할 때,
해당 부모타입의 자식 타입이 2개 이상인 경우 어떤 빈 객체를 호출해야 할지 스프링이 몰라 오류가 발생한다.
따라서 이와 같은 경우 위와같은 오류를 방지하기 위해 빈 이름과 타입을 같이 매개변수로 넣어 호출해줘야 한다.
싱글톤 빈 객체를 생성해주는 @Configuration
@Configuration
public class AppConfig {
@Bean
public MemberService memberService() {
System.out.println("call AppConfig.memberService");
return new MemberServiceImpl(memberRepository());
}
@Bean
public OrderService orderService() {
System.out.println("call AppConfig.orderService");
return new OrderServiceImpl(memberRepository(),discountPolicy());
}
@Bean
public MemberRepository memberRepository() {
System.out.println("call AppConfig.memberRepository");
return new MemoryMemberRepository();
}
@Bean
public DiscountPolicy discountPolicy() {
return new RateDiscountPolicy();
}
}
이전에 스프링 컨테이너는 빈 객체를 싱글톤으로 만들어준다고 했다.
이는 AppConfig 설정 파일에 @Configuration 어노테이션을 붙여줬기 때문이다.
만약 해당 어노테이션을 붙이지 않고 위 설정 파일을 이용해
memberService, orderService를 사용한다고 생각해보자.
그러면 자바 코드상 스프링 빈을 등록하는 과정에서 memberRepository가 3번 호출될 것이다.
왜냐하면 @Configuration 어노테이션이 객체들을 싱글톤 객체로 생성해주어,
처음 정의될 때 한 번만 빈 객체로 생성했던 것을
이젠 memberRepository가 처음 빈 객체로 등록될 때, memberService()가 호출될 때,
orderService()가 호출될 때 총 3번 memberRepository()가 사용되어 생성되기 때문이다.