인프런 스프링 핵심 원리-기본편 #4 스프링 컨테이너와 스프링 빈
2. Bean 조회하기
package hello.core.binfined;
import hello.core.AppConfig;
import hello.core.discount.DiscountPolicy;
import hello.core.member.MemberRepository;
import hello.core.member.MemoryMemberRepository;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.NoUniqueBeanDefinitionException;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.Map;
import static org.junit.jupiter.api.Assertions.*;
import static org.assertj.core.api.Assertions.*;
public class ApplicationContextSameBeanFindTest {
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(SameBeanConfig.class);
@Test
@DisplayName("타입으로 조회 시 같은 타입이 둘 이상 있으면, 중복 오류가 발생한다 ")
void findBeanByTypeDuplicate(){
MemberRepository bean = ac.getBean(MemberRepository.class);
assertThrows(NoUniqueBeanDefinitionException.class,
() -> ac.getBean(MemberRepository.class));
}
@Test
@DisplayName("타입으로 조회 시 같은 타입이 둘 이상 있으면, Bean 이름을 지정하면 된다. ")
void findBeanByName(){
MemberRepository memberRepository = ac.getBean("memberRepository1", MemberRepository.class);
assertThat(memberRepository).isInstanceOf(MemberRepository.class);
}
@Test
@DisplayName("특정 타입을 모두 조회하기")
void findAllBeanByType(){
Map<String, MemberRepository> beansOfType = ac.getBeansOfType(MemberRepository.class);
for (String key : beansOfType.keySet()) {
System.out.println("key = " + key + " value = " + beansOfType.get(key));
assertThat(beansOfType.size()).isEqualTo(2);
}
}
@Configuration
static class SameBeanConfig{
@Bean
public MemberRepository memberRepository1() {
return new MemoryMemberRepository();
}
@Bean
public MemberRepository memberRepository2() {
return new MemoryMemberRepository();
}
}
}
이렇게 Test 코드 내에서 Config 클래스를 지정해서, 테스트를 위한 함수를 만든다.
AnnotationConfigApplicationContext에 대한 객체를 만들 때 매개변수에 생성한 Config 클래스를 넣는다.
3. BeanFactory
: 스프링 컨테이너의 초상위 인터페이스
ApplicationContext
: BeanFactory의 기능을 상속받아 제공한다.
- 환경 변수 제공 : 로컬 / 개발 / 운영 을 구분해서 처리
- 메시지 소스를 활용한 국제화 기능
- 애플리케이션 이벤트
- 편리한 리소스 조회
4. XML을 이용한 Configuration
main > resources에 appConfig.xml 생성
(Enterprise 버전에서는 SpringConfig의 XML을 생성할 수 있다. )
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id = "memberService" class="hello.core.member.MemberServiceImpl">
<constructor-arg name="memberRepository" ref="memberRepository"/>
</bean>
<bean id = "memberRepository" class="hello.core.member.MemoryMemberRepository"></bean>
<bean id = "orderService" class = "hello.core.order.OrderServiceImpl">
<constructor-arg name ="memberRepository" ref = "memberRepository"/>
<constructor-arg name ="discountPolicy" ref = "discountPolicy"/>
</bean>
<bean id = "discountPolicy" class="hello.core.discount.RateDiscountPolicy"></bean>
</beans>
bean이라는 태그를 이용해, memberRepostiory 등 다양한 인스턴스의 구체 클래스를 지정한다.
XML 파일을 이용한 Test
package hello.core.xml;
import hello.core.member.MemberService;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.context.support.GenericXmlApplicationContext;
import static org.assertj.core.api.Assertions.*;
public class XmlAppContext {
@Test
void xmlAppContext(){
GenericXmlApplicationContext ac = new GenericXmlApplicationContext("appConfig.xml");
MemberService memberService = ac.getBean("memberService", MemberService.class);
assertThat(memberService).isInstanceOf(MemberService.class);
}
}
5. 빈 설정 메타 정보 - Bean Definition
스프링 컨테이너는 메타 정보를 기반으로 스프링 빈을 생성한다.
6. 싱글톤 컨테이너
고객의 memberService 요청에 대해서 각각 객체를 만들어서 반환하는 문제
package hello.core.singletone;
import hello.core.AppConfig;
import hello.core.member.MemberService;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.autoconfigure.data.cassandra.DataCassandraTest;
public class SingletoneTest {
@Test
@DisplayName("스프링 없는 순수한 DI 컨테이너")
void pureContainer(){
AppConfig appConfig = new AppConfig();
// 1. 조회 : 호출할 떄마다 객체 생성
MemberService memberService1 = appConfig.memberService();
// 2. 조회 : 호출할 때마다 객체 생성
MemberService memberService2 = appConfig.memberService();
// 참조 값이 다른 것을 확인
System.out.println("memberService1 = " + memberService1);
System.out.println("memberService2 = " + memberService2);
// memberService != memberService2
Assertions.assertThat(memberService1).isNotSameAs(memberService2);
}
}
이를,
해당 객체가 하나만 생성되고 이를 공유하도록 설계한다
package hello.core.singletone;
public class SingletonService {
// private static 선언
private static final SingletonService instance = new SingletonService();
// 조회
public static SingletonService getInstance(){
return instance;
}
// private 생성자
private SingletonService(){
}
public static void main(String[] args) {
SingletonService singletonService1 = new SingletonService();
SingletonService singletonService2 = new SingletonService();
}
}
getInstance를 통해서만 instance, SingletonService 객체를 생성할 수 있다.
외부에서는 SingletoneService에 대한 객체를 new할 수 없으며, 1개의 객체 인스턴스만 존재해야 하므로 private static을 사용한다.
이렇게 같은 객체가 만들어진 것을 알 수 있다.
그러나 !
스프링 컨테이너를 쓰면, 기본적으로 싱글톤 객체로 만들어 관리한다.
싱글톤 패턴을 적용하면, 고객의 요청이 올 떄마다 객체를 생성하지 않고 저장된 객체를 공유하게 된다.
문제점
- 싱글턴 패턴 구현을 위한 코드가 들어간다
- DIP 위반
- OCP 위반 가능성
- 테스트가 어렵다
- private 생성자 사용으로, 자식 클래스를 만들 수 없다
다음 포스팅에 이어서 싱글톤 컨테이너를 이용한 단점 해결을 알아보겠다.