//스프링 컨테이너 생성
ApplicationContext applicationContext =new AnnotationConfigApplicationContext(AppConfig.class);
ApplicationContext
를 스프링 컨테이너라고함
ApplicationContext
는 인터페이스임 → 다형성 O
new AnnotationConfigApplicationContext(AppConfig.class);
이부분이 구현체
스프링 컨테이너 생성
new AnnotationConfigApplicationContext(AppConfig.class);
생성시 구성정보를 지정해주어야해서 AppConfig.class를 매개변수로 넣는것
스프링 빈 등록
@Bean붙은 메소드를 다 호출해서 메소드 이름을 빈 이름(키)
, 리턴하는 객체를 빈 객체(값)
으로 빈 저장소에 저장
빈이름은 메소드 이름을 사용하지 @Bean(name="memberService2")이런식으로 부여도 가능
빈 이름은 항상 다른 이름
을 부여해야함
스프링 빈 의존관계 설정 - 준비
스프링 빈 의존관계 설정 - 완료
설정 정보를 참고해서 동적 의존관계를 주입
스프링은 빈을 생성하고, 의존관계를 주입하는 단계가 나누어져있다
public class ApplicationContextInfoTest {
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);
@Test
@DisplayName("모든 빈 출력하기")
void findAllBean(){
String[] beanDefinitionNames = ac.getBeanDefinitionNames(); // 모든 빈 이름 꺼내기
// iter
for (String beanDefinitionName : beanDefinitionNames) {
Object bean = ac.getBean(beanDefinitionName);
System.out.println("name = " + beanDefinitionName + "object = "+bean);
}
}
@Test
@DisplayName("애플리케이션 빈 출력하기")
void findApplicationBean(){
String[] beanDefinitionNames = ac.getBeanDefinitionNames(); // 모든 빈 이름 꺼내기
// iter
for (String beanDefinitionName : beanDefinitionNames) {
BeanDefinition beanDefinition = ac.getBeanDefinition(beanDefinitionName);
// 역할은 3가지 존재
// ROLE_APPLICAITON : 내가 등록한 빈만 고르는 조건
if(beanDefinition.getRole() == BeanDefinition.ROLE_APPLICATION){
Object bean = ac.getBean(beanDefinitionName);
System.out.println("name = " + beanDefinitionName + "object = "+bean);
}
}
}
}
getBeanDefinitionNames()
: 스프링에 등록된 모든 빈 이름 조회 → String 빈 이름 리턴
getBean(빈이름)
: 빈이름으로된 빈객체를 조회
beanDefinition.getRole
ROLE_APPLICATION
: 일반적으로 사용자가 정의한 빈ROLE_INFRASTRUCTURE
: 스프링이 내부에서 사용하는 빈가장 기본적인 빈 조회 방법
ac.getBean(빈이름, 타입)
ac.getBean(타입)
조회대상 스프링 빈이 없으면 예외 발생NoSuchBeanDefinitionException: No bean named 'xxxxx' available
public class ApplicationContextBasicFindTest {
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);
@Test
@DisplayName("빈 이름으로 조회")
void findBeanByName(){
MemberService memberService = ac.getBean("memberService", MemberService.class);
System.out.println("memberService = " + memberService);
System.out.println("memberService.getClass() = " + memberService.getClass());
// MemberServiceImpl이 잘 출력됨
Assertions.assertThat(memberService).isInstanceOf(MemberServiceImpl.class);
}
@Test
@DisplayName("이름없이 타입으로만 조회")
void findBeanByType(){
MemberService memberService = ac.getBean(MemberService.class);
Assertions.assertThat(memberService).isInstanceOf(MemberServiceImpl.class);
}
// 인터페이스 아닌 구체타입도 가능하다. 그러나 아래는 추상이 아닌 구현에 의존한 코드이므로 좋은 코드는 아니긴함
@Test
@DisplayName("구체 타입으로 조회")
void findBeanByName2(){
MemberService memberService = ac.getBean("memberService",MemberServiceImpl.class);
Assertions.assertThat(memberService).isInstanceOf(MemberServiceImpl.class);
}
@Test
@DisplayName("빈 이름으로 조회X")
void findBeanByNameX(){
// MemberService xxxx = ac.getBean("xxxx", MemberService.class);
assertThrows(NoSuchBeanDefinitionException.class,
()->ac.getBean("xxxx", MemberService.class));
}
}
빈 타입으로 조회, 빈이름+타입으로 조회, 구체타입으로 조회 모두 가능
Assertions의 isInstanceOf(MemberServiceImpl.class)로 테스트 가능하다
구체타입으로 조회는 추천 X 유연성이 떨어짐
없는 빈 이름으로 조회하면 예외가 터짐
assertThrow(..)은 예외가 발생해야지 테스트가 성공하는 테스트
조회 시 같은 타입의 스프링 빈이 두개 이상이면 오류 발생 : NoUniqueBeanDefinitionException
→ 빈 이름 지정해줘야함
ac.getBeansOfType()으로 타입의 모든 빈 조회가능
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("타입으로 조회시 같은 타입이 둘 이상 있으면, 빈 이름을 지정하면 된다")
void findBeanByName(){
MemberRepository memberRepository = ac.getBean("memberRepository1",MemberRepository.class);
assertThat(memberRepository).isInstanceOf(MemberRepository.class);
}
@Test
@DisplayName("특정 타입을 모두 조회하기")
void findBeanByType(){
Map<String, MemberRepository> beansOfType = ac.getBeansOfType(MemberRepository.class);
for (String key : beansOfType.keySet()) {
System.out.println("key = " + key+"value = "+beansOfType.get(key));
}
System.out.println("beansOfType = " + beansOfType);
assertThat(beansOfType.size()).isEqualTo(2);
}
// 중복 타입이 있는 config를 여기서만 사용할 수 있게 생성
// static : 이 안에서만 쓰겠다
@Configuration
static class SameBeanConfig{
@Bean
public MemberRepository memberRepository1(){
return new MemoryMemberRepository();
}
@Bean
public MemberRepository memberRepository2(){
return new MemoryMemberRepository();
}
}
}
동일 타입의 빈이 두개이상이라면 빈이름을 통해서 getBean해주면됨
getBeansOfType
은 빈이름, 빈객체를 맵
으로 리턴
부모타입으로 조회하면 자식타입도 함께
조회된다.
자바 객체의 최고 부모인 Object 타입으로 조회하면 모든 스프링빈 조회
public class ApplicationContextExtendsFindTest {
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(TestConfig.class);
@Test
@DisplayName("부모 타입으로 조회시, 자식이 둘 이상있으면, 중복 오류가 발생한다")
void findBeanByParentTypeDuplicate(){
//DiscountPolicy bean = ac.getBean(DiscountPolicy.class);
assertThrows(NoUniqueBeanDefinitionException.class,
()->ac.getBean(DiscountPolicy.class));
}
@Test
@DisplayName("부모 타입으로 조회시, 자식이 둘 이상있으면, 빈 이름을 지정하면 된다")
void findBeanByParentTypeBeanName(){
DiscountPolicy rateDiscoutPolicy = ac.getBean("rateDiscountPolicy",DiscountPolicy.class);
assertThat(rateDiscoutPolicy).isInstanceOf(DiscountPolicy.class);
}
@Test
@DisplayName("특정 하위 타입으로 조회")
void findBeanBySubType(){
RateDiscountPolicy bean = ac.getBean(RateDiscountPolicy.class);
assertThat(bean).isInstanceOf(RateDiscountPolicy.class);
}
@Test
@DisplayName("부모 타입으로 모두 조회")
void findAllBeanByParentType(){
Map<String, DiscountPolicy> beansOfType = ac.getBeansOfType(DiscountPolicy.class);
assertThat(beansOfType.size()).isEqualTo(2);
for(String key:beansOfType.keySet()){
System.out.println("key = "+key+"value = "+beansOfType.get(key));
}
}
@Test
@DisplayName("부모 타입으로 모두 조회하기 - Object")
void findAllBeanByObjectType(){
Map<String, Object> beansOfType = ac.getBeansOfType(Object.class);
for(String key:beansOfType.keySet()){
System.out.println("key = "+key+"value = "+beansOfType.get(key));
}
}
@Configuration
static class TestConfig{
@Bean
public DiscountPolicy rateDiscountPolicy(){
return new RateDiscountPolicy();
}
@Bean
public DiscountPolicy fixDiscountPolicy(){
return new FixDiscountPolicy();
}
}
}
부모타입으로 조회 → 자식이 둘이상이면 오류 발생 → 빈 이름 지정해줘야함
특정 하위 타입으로도 조회가능 → 비추
부모타입으로 모두 조회 가능 getBeansOfType → 자바 최상위 클래스인 Object.class로도 조회 가능
BeanFactory
스프링 컨테이너의 최상위
인터페이스
스프링 빈 관리, 조회
역할
getBean()을 제공
ApplicationContext
BeanFactory기능을 모두 상속받아서 제공
→ 둘의 차이가 무엇일까? : 애플리케이션 개발에는 빈 관리, 조회 뿐만 아닌 많은 기능들이 추가로 필요함
MessageSource 메세지 소스를 활용한 국제화 기능 : 한국에서 들어오면 한국어로, 영어권에서 들어오면 영어로 출력하는 기능
EnvironmentCapable 환경변수 : 로컬, 개발, 운영등을 구분해서 처리
ApplicationEventPublisher 애플리케이션 이벤트 : 이벤트를 발행하고 구독하는 모델 편리하게 지원
ResourceLoader 편리한 리소스 조회 : 파일, 클래스패스, 외부 등에서 리소스를 편리하게 조회
정리하자면
ApplicationContext는 BeanFactory의 기능(관리, 조회) + 편리한 부가기능
을 제공
BeanFactory를 직접 사용할일은 거의 없고, ApplicationContext만 쓴다고 보면됨 → 두개 모두 스프링 컨테이너
라고 한다
스프링 컨테이너는 다양한 형식의 설정 정보를 받아드릴 수 있게 유연하게 설계되어있음 → 자바 코드, XML, Groovy 등등…
annotation 기반 자바 코드 설정 사용
XML 설정 사용
public class XmlAppContext {
@Test
void xmlAppContext(){
ApplicationContext ac = new GenericXmlApplicationContext("appConfig.xml");
MemberService memberService = ac.getBean("memberService", MemberService.class);
Assertions.assertThat(memberService).isInstanceOf(MemberService.class);
}
}
GenericXmlApplicationContext(”appConfig.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 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"/>
</beans>
코드 형식만 다르지 기존 AppConfig와 다르지 않음
스프링은 어떻게 이런 다양한 설정 형식을 지원하는 것일까
→ 이것도 역할과 구현
을 개념적으로 나눈 것
→ 자바코드인지, XML인지 스프링 컨테이너는 몰라도됨. 그냥 읽어서 BeanDefinition을 만들면됨
BeanDefinition
을 빈 설정 메타정보
라고함 → @Bean, 당 각각 하나씩 메타정보가 생성됨
스프링 컨테이너는 이 메타정보를 기반으로 스프링빈 생성
BeanDefinition 자체가 인터페이스임
AnnotationConfigApplicationContext : AnnotatedBeanDefinitionReader를 통해서 Appconfig.class를 읽음
→ 설정 정보를 읽어서 BeanDefinition을 생성
GenericXmlApplicationContext : 동일하게 XmlBeanDefinitionReader를 통해 appConfig.xml을 읽고 BeanDefinition 생성
ublic class BeanDefinitionTest {
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);
@Test
@DisplayName("빈 설정 메타정보 확인")
void findApplicationBean(){
String[] beanDefinitionNames = ac.getBeanDefinitionNames();
for(String beanDefinitionName : beanDefinitionNames){
BeanDefinition beanDefinition = ac.getBeanDefinition(beanDefinitionName);
if(beanDefinition.getRole() == BeanDefinition.ROLE_APPLICATION){
System.out.println("beanDefinitionName = " + beanDefinitionName+" beanDefinition = "+beanDefinition);
}
}
}
}
BeanDefinition 정보
BeanClassName: 생성할 빈의 클래스 명(자바 설정 처럼 팩토리 역할의 빈을 사용하면 없음)
factoryBeanName: 팩토리 역할의 빈을 사용할 경우 이름, 예) appConfig
factoryMethodName: 빈을 생성할 팩토리 메서드 지정, 예) memberService
Scope: 싱글톤(기본값)
lazyInit: 스프링 컨테이너를 생성할 때 빈을 생성하는 것이 아니라, 실제 빈을 사용할 때 까지 최대한 생성을 지연
처리 하는지 여부
InitMethodName: 빈을 생성하고, 의존관계를 적용한 뒤에 호출되는 초기화 메서드 명
DestroyMethodName: 빈의 생명주기가 끝나서 제거하기 직전에 호출되는 메서드 명
Constructor arguments, Properties: 의존관계 주입에서 사용한다. (자바 설정 처럼 팩토리 역할의 빈을 사용
하면 없음)
BeanDefinition을 직접 생성해서 스프링 컨테이너에 등록할수도 있다 → 그러나 쓸일은 잘 없음
인프런 스프링 핵심 원리 기본편 - 김영한 강의를 듣고 정리한 내용입니다