본문 바로가기
Spring/스프링 핵심 원리 - 기본편

4. 스프링 컨테이너와 스프링 빈 + 람다식, Map 데이터 전체 조회

by hk27 2022. 1. 5.
스프링 컨테이너는 스프링 빈을 어떻게 등록하고 관리할까요?
스프링 빈은 어떻게 꺼낼 수 있을까요?

 

 

안녕하세요.

오늘은 '섹션 4. 스프링 컨테이너와 스프링 빈'을 리뷰하겠습니다. 

 

지난 시간에 스프링 컨테이너와 스프링 빈이 무엇인지를 알아보았습니다.

스프링 컨테이너는 자바 객체의 생명 주기를 관리하며, 생성된 자바 객체들에 추가적인 기능을 제공하는 역할을 하는 컨테이너입니다[6]. 여기서 말하는 자바 객체를 스프링에서는 빈(Bean)이라고 부릅니다. 
스프링 컨테이너는 @Configuration이 붙은 파일을 구성 정보로 사용하고, @Bean이 붙은 메서드를 호출해 반환된 객체를 스프링 컨테이너에 스프링 빈으로 등록합니다. 

 

이번 시간에는 스프링 컨테이너의 생성과 스프링 빈 조회 등을 학습하며 스프링 컨테이너와 빈에 대해 더욱 자세히 알아보겠습니다.

 

스프링 컨테이너 생성

스프링 컨테이너 ApplicationContext는 아래 코드로 생성할 수 있습니다.

ApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);

 

ApplicationContext가 스프링 컨테이너이고, 인터페이스입니다. AnnotationConfigApplicationContext는 구현체이고, 파라미터로 구성 정보를 받습니다. 여기서는 AppConfig.class를 구성 정보로 지정하였습니다.

 

스프링 컨테이너가 만들어지면 구성 정보 클래스를 사용해서 스프링 빈을 등록합니다.

빈 이름은 메서드 이름이고, 빈 객체로 return 된 객체를 저장합니다. 

 

다음으로 의존관계를 주입(DI)합니다.

스프링은 빈을 생성하고, 의존관계를 주입하는 두 사이클이 있습니다. 

참고로 이렇게 자바 코드로 스프링 빈을 등록하면, 생성자를 호출할 때 의존관계 주입이 한 번에 처리됩니다. 스프링 빈을 만들 때 생성자를 불러야 하고, 의존 관계가 주입되기 때문이죠.

이해할 때는, 스프링 빈이 등록되고 의존 관계가 이후에 주입된다고 이해를 해도 되지만, 실제로 생성자로 의존관계를 주입받을 때는, 스프링 빈 생성과 의존 관계 주입이 동시에 일어납니다[2]. 

 

스프링 빈 조회

스프링 빈 이름으로 빈 객체를 조회하는 코드는 ac.getBean()입니다.

ac.getBean(빈 이름, 타입)이나 ac.getBean(타입)으로 조회할 수 있습니다.

타입에는 클래스 타입 객체를 넣습니다.

클래스와 인터페이스 이름에 .class를 붙이면 각각 Class<클래스>, Class<인터페이스>타입을 가지는 객체를 반환합니다[3]. ex) MyObject.class는 Class<MyObject>타입을 가지는 객체를 반환

따라서 Class 객체를 요구하는 곳에 "인터페이스.class"를 전달함으로써 Class<인터페이스> 타입을 가지는 객체가 전달할 수 있습니다. 

예를 들어서, 빈 이름으로 객체를 조회해봅시다.

void findByName(){
	MemberService memberService = ac.getBean("memberService",MemberService.class);
	assertThat(memberService).isInstanceOf(MemberServiceImpl.class);
}

빈 저장소에서 "memberService"라는 이름의 빈을 찾으면 MemberServiceImpl 타입의 객체가 반환됩니다. 

따라서 위의 테스트는 통과합니다. 

 

스프링에 관련된 내용은 아니지만...

is instance of는 자식 is instance of 부모는 true이고 역은 false입니다. 

그래서 처음에는 MemberService가 부모인데 왜 memberService가 MemberServiceImpl의 instance인 것이 true이지? 라고 생각했는데 생각해보니 memberService의 타입이 MemberService가 아니고 MemberServiceImpl 타입입니다.

상속과 관련된 개념을 혼동하고 있는 것 같아서 (자바의 근본이 되는 개념인데...!!!!) 아래 게시글에 정리하였습니다. 

https://passionate.tistory.com/9

 

[Java] 상속 extends, implements, abstract

안녕하세요. 스프링을 공부하면서 자바 상속을 복습해야겠다는 생각이 들어서 정리할겸 상속에 대한 게시글을 작성합니다. 의문점이 든 부분은 '부모 a = new 자식()'일 때, a 객체의 타입은 무엇

passionate.tistory.com

 

스프링 빈 조회 - 동일한 타입이 둘 이상

스프링 빈을 조회할 때, 동일한 타입이 둘 이상이면 오류가 발생하기 때문에 빈 이름을 지정해줍니다.

그리고 특정 타입을 모두 조회하고 싶으면 getBean이 아니라 getBeansOfType 함수를 이용하면 됩니다.

 

예제 테스트 코드를 봅시다. 

첫 번째 테스트에서는 동일한 타입이 둘 이상이면 예외가 발생하는 것을 테스트하였고,

두 번째 테스트에서는 빈 이름을 지정해서 한 개의 빈을 꺼내는 것을 테스트하였고,

세 번째 테스트에서는 getBeansOfType 함수를 이용해 해당 타입의 모든 빈을 꺼내는 것을 테스트하였습니다. 

public class ApplicationContextSameBeanFindTest {
    AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(SameBeanConfig.class);
    @Test
    @DisplayName("타입으로 조회 시 같은 타입이 둘 이상 있으면, 중복 오류가 발생한다")
    void findBeanByTypeDuplicate(){
        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 findAllBeanByType(){
        Map<String, MemberRepository> beansOfType = ac.getBeansOfType(MemberRepository.class);
        System.out.println("beansOfType= "+beansOfType);
        assertThat(beansOfType.size()).isEqualTo(2);
    }

    @Configuration
    static class SameBeanConfig{
        @Bean
        public MemberRepository memberRepository1(){
            return new MemoryMemberRepository();
        }
        @Bean
        public MemberRepository memberRepository2(){
            return new MemoryMemberRepository();
        }
    }
}

 

참고로 스프링 빈을 조회할 때, 부모 타입으로 조회하면 자식 타입도 함께 조회됩니다.

예를 들어서 Object 타입으로 조회하면 모든 스프링 빈이 조회됩니다. 

 

BeanFactory와 ApplicationContext

지금까지 ApplicationContext를 스프링 컨테이너로 이용했습니다. 사실 스프링 컨테이너는 하나 더 있는데요, BeanFactory입니다. ApplicationContext는 BeanFactory를 상속받고 있으며, ApplicationContext가 메시지 소스를 활용한 국제화 기능, 환경 변수 등의 기능을 추가로 지원합니다. 보통 BeanFactory보다는 부가 기능이 포함된 ApplicationContext를 사용합니다. 

 

 

Further Study

1. 람다식

매개변수로 넘어간 타입의 빈이 2개 이상일 때, 에러가 발생합니다.

에러가 발생하는지를 테스트하기 위해서 아래 테스트코드를 사용하였습니다.

void findBeanByTypeDuplicate(){
	assertThrows(NoUniqueBeanDefinitionException.class, ()->ac.getBean(MemberRepository.class));
}

여기서 () -> 라는 낯선 식이 보입니다.

 

assertThrows는 클래스 타입과 executable이 필요합니다. 

함수를 하나 만들어도 되지만, 테스트를 위해 작성하기는 귀찮습니다.

그래서 함수인데 함수를 따로 만들지 않고 코드 한 줄에 함수를 써서 그것을 호출하는 방식인 람다식을 사용합니다[4]. 

(매개변수, ...) -> { 실행문 ... }

으로 람다식을 표현합니다.  -> 기호는 매개 변수를 이용해서 중괄호 { } 바디를 실행한다는 뜻입니다.

테스트코드에서는 ()->ac.getBean(MemberRepository.class)라는 람다식을 사용했습니다. 

중괄호는 한 줄이면 생략 가능하기 때문에 생략하였습니다.

 

 

2. Map의 데이터 전부 조회하기

getBeansOfType 메소드를 쓰면, 타입이 맞는 모든 빈을 Map으로 꺼낼 수 있었습니다.

그렇다면 Map의 데이터는 어떻게 전부 조회할 수 있을까요?

'keySet()' 함수를 이용하면 됩니다. 

예시 코드를 봅시다.

 

void findAllBeanByType(){
	Map<String, MemberRepository> beansOfType = ac.getBeansOfType(MemberRepository.class);
	for (String key : beansOfType.keySet()) {
		System.out.println("key= "+key+" value= "+ beansOfType.get(key));
	}
}

beansOfType이라는 Map에서 keySet을 꺼내면 key 값들이 전부 Set으로 꺼내집니다. 이 값으로 반복문을 돌리고,  get(key)을 활용하면 value 값도 찾을 수 있습니다. 

 

 

이번 시간에는 스프링 컨테이너와 스프링 빈에 대해 자세히 학습하였습니다.

다음 시간에는 싱글톤 컨테이너에 대해 학습해보겠습니다. 

 

인프런  '스프링 핵심 원리 - 기본편' 강의를 듣고 공부하며 정리한 자료입니다. 

잘못된 부분은 피드백 주시면 감사하겠습니다. 

글 읽어주셔서 감사합니다 :-)

 

참고자료

[1] 스프링 핵심 원리 - 기본편, 섹션 4. 스프링 컨테이너와 스프링 빈 https://www.inflearn.com/course/스프링-핵심-원리-기본편

[2] https://www.inflearn.com/questions/127085

[3] https://www.inflearn.com/questions/354670

[4] https://coding-factory.tistory.com/265

 

 

 

 

 

 

댓글