스프링 핵심 원리 - 기본편의 복습을 위한 글이며
이 글에 나오는 모든 소스코드의 저작원은 인프런의 김영한 강사님께 있습니다.
1. 스프링 컨테이너
1.1 스프링 컨테이너 생성
다음은 스프링 컨테이너가 생성되는 과정이다.
// 스프링 컨테이너 생성
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);
- ApplicationContext : 스프링 컨테이너
ApplicationContext는 인터페이스이다. 스프링 컨테이너는 XML을 기반으로 만들 수 있고, 애노테이션 기반의 자바 설정 클래스로도 만들 수 있다. 직전에 AppConfig를 사용했던 방식이 어노테이션 기반의 자바 설정 클래스로 스프링 컨테이너를 만든 것이다.
다음은 자바 설정 기반으로 스프링 컨테이너(ApplicationContext)를 만든 것이다.
new AnnotationConfigApplicationContext(AppConfig.class);
이 클래스는 ApplicationContext 인터페이스의 구현체이다.
1.2 스프링 컨테이너의 생성 과정
new AnnotationConfigApplicationContext(AppConfig.class);
스프링 컨테이너를 생성할 때는 구성 정보를 지정해주어야 한다. 위 코드에서는 AppConfig를 구성 정보로 지정해주었다.
1.3 스프링 빈 등록
스프링 컨테이너는 파라미터로 넘어온 설정 클래스 정보를 사용해서 스프링 빈을 등록한다.
빈(Bean) 이름
- 빈 이름은 메소드 이름을 직접 사용한다.
- 빈 이름은 직접 부여할 수 있다. →
@Bean(name="memberService2")
주의!
빈 이름은 항상 다른 이름을 부여해야 한다. 같은 이름을 부여하면, 다른 빈이 무시되거나, 기존 빈을 덮어버리거나 설정에 따라 오류가 발생한다.
1.4 스프링 빈 의존관계
스프링 컨테이너는 설정 정보를 참고해서 의존관계를 주입(DI)한다.
스프링은 빈을 생성하고, 의존관계를 주입하는 단계가 나누어져 있다. 그런데 이렇게 자바 코드로 스프링 빈을 등록하면 생성자를 호출하면서 의존관계 주입도 한번에 처리된다. 여기서는 이해를 돕기 위해 개념적으로 나누어 설명했지만, 자세한 내용은 의존관계 자동 주입에서 다시 설명하겠다.
2. 컨테이너에 등록된 모든 빈 조회하기
스프링 컨테이너에 실제 스프링 빈들이 잘 등록 되었는지 확인해보겠다.
class ApplicationContextInfoTest {
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);
@Test
@DisplayName("모든 빈 출력하기")
void findAllBean() {
String[] beanDefinitionNames = ac.getBeanDefinitionNames();
for (String beanDefinitionName : beanDefinitionNames) {
Object bean = ac.getBean(beanDefinitionName);
System.out.println("name = " + beanDefinitionName + " object = " + bean); " object = " + bean);
}
}
}
위 코드는 스프링에 등록된 모든 빈 정보를 출력한다.
[그림 5]는 위 코드의 실행 결과이다. 위의 코드는 스프링에 등록된 모든 빈 정보를 출력하는 것임으로 개발자가 직접 등록한 빈 외에 스프링 내부의 빈들까지 추가로 출력된다.
ac.getBeanDefinitionNames()
: 스프링에 등록된 모든 빈 이름을 조회한다.ac.getBean()
: 빈 이름으로 빈 객체(인스턴스)를 조회한다.
다음은 스프링 내부에서 사용하는 빈은 제외하고 개발자가 직접 등록한 빈들만 출력하는 코드이다.
class ApplicationContextInfoTest {
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);
@Test
@DisplayName("모든 빈 출력하기")
void findApplicationBean() {
String[] beanDefinitionNames = ac.getBeanDefinitionNames();
for (String beanDefinitionName : beanDefinitionNames) {
BeanDefinition beanDefinition = ac.getBeanDefinition(beanDefinitionName);
// BeanDefinition.ROLE_APPLICATION : 직접 등록한 애플리케이션 빈
// BeanDefinition.ROLE_INFRASTRUCTURE : 스프링 내부에서 사용하는 빈
if ( beanDefinition.getRole() == BeanDefinition.ROLE_APPLICATION) {
Object bean = ac.getBean(beanDefinitionName);
System.out.println("name = " + beanDefinitionName + " object = " + bean);
}
}
}
}
스프링이 내부에서 사용하는 빈은 getRole()
로 구분할 수 있다.
- ROLE_APPLICATION : 일반적으로 사용자가 정의한 빈
- ROLE_INFRASTRUCTURE : 스프링이 내부에서 사용하는 빈
3. 스프링 빈 조회 - 기본
스프링 컨테이너에서 스프링 빈을 찾는 가장 기본적인 방법은 다음과 같다.
ac.getBean(빈 이름, 타입)
ac.getBean(타입)
만약 조회할 빈이 없다면 다음과 같은 오류가 발생한다.
NoSuchBeanDefinitionException: No bean named 'xxxxx' available
3.1 Assertions.assertThat
assertThat은 jUnit Test에서 두 값을 비교할 때 사용된다.
assertThat(T actual, Matcher<? super T> matcher)
첫번째 파라미터는 비교 대상의 실제값을, 두번째 파라미터로는 비교로직이 담긴 Matcher가 사용되게된다.
아래의 두 Matcher를 주로 사용하게 될 것이다.
equalTo
- 두 값이 같은지 여부를 체크한다.
assertThat("foo", equalTo("foo"));
instanceOf
- 비교값이 Matcher의 타입과 동일한지 여부를 체크한다. any와는 다르게 매쳐의 값은 비교값과 연관없는 경우에도 사용할 수 있다.
assertThat(new Canoe(), instanceOf(Paddlable.class));
3.2 빈 조회하기
빈 이름으로만 조회
class ApplicationContextBasicFindTest {
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);
@Test
@DisplayName("빈 이름으로 조회")
void findBeanByName() {
MemberService memberService = ac.getBean("memberService", MemberService.class);
assertThat(memberService).isInstanceOf(MemberServiceImpl.class);
}
}
memberService라는 이름과 MemberService.class의 타입을 가진 빈을 가져와서 memberService 객체에 저장한다. memberService의 타입과 MemberServiceImpl.class의 타입이 같은지 검증한다.
이름 없이 타입으로만 조회
class ApplicationContextBasicFindTest {
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);
@Test
@DisplayName("이름 없이 타입으로만 조회")
void findBeanByType() {
MemberService memberService = ac.getBean(MemberService.class);
assertThat(memberService).isInstanceOf(MemberServiceImpl.class);
}
}
구체 타입으로 조회
class ApplicationContextBasicFindTest {
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);
@Test
@DisplayName("구체 타입으로 조회") // 좋은 조회 방법은 아니다 Why? 역할과 구현에서 역할에 의존해야하는데 구현에 의존하기 때문!
void findBeanByName2() {
MemberService memberService = ac.getBean("memberService", MemberServiceImpl.class);
assertThat(memberService).isInstanceOf(MemberServiceImpl.class);
}
}
빈 이름으로 조회 X
class ApplicationContextBasicFindTest {
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);
@Test
@DisplayName("빈 이름으로 조회X") void findBeanByNameX() {
//ac.getBean("xxxxx", MemberService.class);
Assertions.assertThrows(NoSuchBeanDefinitionException.class, () ->
ac.getBean("xxxxx", MemberService.class));
}
}
스프링 컨테이너에 'xxxxx'라는 이름을 가진 빈이 등록되어있지 않기 때문에 NoSuchBeanDefinitionException이 발생하게 된다.
4. 스프링 빈 조회 - 동일한 타입이 둘 이상
먼저, 빈을 조회하기 전에 테스트용도로 사용할 Config를 만들어주겠다.
SameBeanConfig
@Configuration
static class SameBeanConfig {
@Bean
public MemberRepository memberRepository1() {
return new MemoryMemberRepository();
}
@Bean
public MemberRepository memberRepository2() {
return new MemoryMemberRepository();
}
}
타입으로 조회하는 경우 같은 타입의 스프링 빈이 둘 이상이면 오류가 발생되게 된다. 이럴 땐, 빈 이름을 지정해주면 된다.
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));
}
}
같은 타입의 두개 있기 때문에 NoUniqueBeanDefinitionException이 발생한다.
빈 이름을 지정하여 조회
public class ApplicationContextSameBeanFindTest {
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(SameBeanConfig.class);
@Test
@DisplayName("타입으로 조회시 같은 타입이 둘 이상 있으면, 빈 이름을 지정하면 된다.")
void findByBeanName() {
MemberRepository memberRepository = ac.getBean("memberRepository1",MemberRepository.class);
assertThat(memberRepository).isInstanceOf(MemberRepository.class);
}
}
NoUniqueBeanDefinitionException 오류를 발생하지 않으려면 이름을 지정하여 조회하면 된다.
특정 타입을 모두 조회
public class ApplicationContextSameBeanFindTest {
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(SameBeanConfig.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));
}
System.out.println("beansOfType = " + beansOfType);
assertThat(beansOfType.size()).isEqualTo(2);
}
}
특정 타입을 모두 조회하고 싶으면 getBeansOfType()
메소드를 사용하면 된다.
5. 스프링 빈 조회 - 상속 관계
스프링에서는 부모 타입을 조회하면 자식 타입도 함께 조회한다. 그래서 모든 자바 객체의 최고 부모인 Object 타입으로 조회하면, 모든 스프링 빈을 조회하게 된다.
빈을 조회하기 전에 테스트에서 사용할 Config를 먼저 만들어주겠다.
TestConfig
@Configuration
static class TestConfig {
@Bean
public DiscountPolicy rateDiscountPolicy() {
return new RateDiscountPolicy();
}
@Bean
public DiscountPolicy fixDiscountPolicy() {
return new FixDiscountPolicy();
}
}
부모 타입으로 조회
public class ApplicationContextExtendsFindTest {
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(TestConfig.class);
@Test
@DisplayName("부모 타입으로 조회시, 자식이 둘 이상 있으면, 중복 오류가 발생한다.")
void findBeanByParentTypeDuplicate() {
// DiscountPolicy bean = ac.getBean(DiscountPolicy.class);
Assertions.assertThrows(NoUniqueBeanDefinitionException.class,
() -> ac.getBean(DiscountPolicy.class));
}
}
자식 타입이 둘 이상 있기 때문에 NoUniqueBeanDefinitionException 에러가 발생하게 된다.
부모 타입으로 조회 - 빈 이름을 지정해서 조회
public class ApplicationContextExtendsFindTest {
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(TestConfig.class);
@Test
@DisplayName("부모 타입으로 조회시, 자식이 둘 이상 있으면, 빈 이름을 지정하면 된다.")
void findBeanByParentTypeBeanName() {
DiscountPolicy rateDiscountPolicy = ac.getBean("rateDiscountPolicy", DiscountPolicy.class);
assertThat(rateDiscountPolicy).isInstanceOf(RateDiscountPolicy.class);
}
}
하위 타입으로 조회
public class ApplicationContextExtendsFindTest {
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(TestConfig.class);
@Test
@DisplayName("특정 하위 타입으로 조회") // 별로 좋은 방법은 아니다.
void findBeanBySubType() {
RateDiscountPolicy bean = ac.getBean(RateDiscountPolicy.class);
assertThat(bean).isInstanceOf(RateDiscountPolicy.class);
}
}
부모타입으로 모두 조회
public class ApplicationContextExtendsFindTest {
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(TestConfig.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));
}
}
}
자식 타입을 모두 조회하고 싶으면 getBeansOfType()
메소드를 사용해 자식 타입을 조회하면 된다.
자바의 최상위 부모는 Object 클래스이다. 부모인 DiscountPolicy 대신 Object 클래스로 조회하게 되면 Object 하위의 클래스들이 모두 출력되게 된다.
잘못된 정보가 있거나 오타가 있으면 댓글 달아주세요!
감사합니다 :)
'Backend > Spring - Core' 카테고리의 다른 글
Spring #6. 싱글톤 컨테이너 (0) | 2023.01.12 |
---|---|
Spring #5. BeanFactory와 ApplicationContext (0) | 2022.12.29 |
Spring #3. 객체 지향 원리 적용 (2) (0) | 2022.12.22 |
Spring #3. 객체 지향 원리 적용 (1) (0) | 2022.12.22 |
Spring #2. 스프링 핵심 원리 기본편 - 비즈니스 요구사항과 설계 (1) | 2022.12.21 |