1. 스프링 컨테이너 생성
//스프링 컨테이너 생성
ApplicationContext applicationContext =
new AnnotationConfigApplicationContext(AppConfig.class);
ApplicationContext 를 스프링 컨테이너이자 인터페이스이다.
그리고 new AnnotationConfigApplicationContext(AppConfig.class)는 ApplicationContext의 구현체이다.
스프링 컨테이너의 생성 과정을 알아보도록 하자.
1) 스프링 컨테이너 생성
스프링 컨테이너를 생성하면 그 안에 스프링 빈 저장소가 생성된다.
이때 구성 정보(AppConfing.class)를 지정해줘야 한다.
2) 스프린 빈 등록
@Bean이 붙은 코드의 내용을 호출한다.
빈 이름은 @Bean이 붙은 메소드의 메소드명이다.
빈 이름을 직접 부여할 수도 있다 -> @Bean(name="...")
이때 빈 이름은 중복되면 안 되고, 항상 다른 이름을 부여해야 한다.
3) 스프링 빈 의존관계 설정 - 준비
4) 스프링 빈 의존관계 설정 - 완료
스프링 컨테이너에 빈이 등록되면 등록된 빈들 사이의 의존관계가 설정된다. 이를 의존관계 주입(DI)라고 한다.
자바 코드로 스프링 빈을 등록하면 생성자 호출시 의존관계 주입도 한번에 처리된다.
2. 컨테이너에 등록된 모든 빈 조회
스프링 컨테이너에 실제 스프링 빈들이 잘 등록되었는지 확인할 수 있다.
src/test에 beanfind 패키지를 생성하고, 그 아래 ApplicationContextInfoTest 클래스를 생성한다.
public 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);
}
}
//스프링이 내부에서 등록한 빈 제외, 내가 등록한 빈만 출력
@Test
@DisplayName("애플리케이션 빈 출력하기")
void findApplicationBean(){
String[] beanDefinitionNames = ac.getBeanDefinitionNames();
for(String beanDefinitionName : beanDefinitionNames){
BeanDefinition beanDefinition = ac.getBeanDefinition(beanDefinitionName);
//Role ROLE_APPLICATION : 직접 등록한 애플리케이션 빈
//Role ROLE_INFRASTRUCTURE : 스프링이 내부에서 사용하는 빈
if(beanDefinition.getRole() == BeanDefinition.ROLE_APPLICATION){
Object bean = ac.getBean(beanDefinitionName);
System.out.println("name = " + beanDefinitionName + ", object = " + bean);
}
}
}
}
- ac.getBeanDefinitionNames()를 통해 등록된 모든 빈들의 이름을 가져온다.
- 가져온 빈의 이름을 반복문으로 순회하면서, ac.getBean(beanDefinitionName)을 통해 해당 빈의 객체를 가져와 빈의 이름과 객체를 출력한다.
- 각 빈에 대한 BeanDefinition을 가져와서 해당 빈의 Role이 ROLE_APPLICATION인지 확인한다.
- ROLE_APPLICATION은 직접 등록한 애플리케이션 빈을 나타내며, 이를 만족하는 빈들의 이름과 객체를 출력한다.
3. 스프링 빈 조회 - 기본
스프링 컨테이너에서 빈을 찾기 위해 src/test/beanfind에 ApplicationContextBasicFindTest 클래스를 생성한다.
class ApplicationContextBasicFindTest {
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);
@Test
@DisplayName("빈 이름으로 조회")
void findBeanByName() {
MemberService memberService = ac.getBean("memberService", MemberService.class);
//memberService가 MemberServiceImpl에 있으면 성공
assertThat(memberService).isInstanceOf(MemberServiceImpl.class);
}
@Test
@DisplayName("이름 없이 타입만으로 조회")
void findBeanByType() {
MemberService memberService = ac.getBean(MemberService.class);
assertThat(memberService).isInstanceOf(MemberServiceImpl.class);
}
// 구체 타입으로 조회 시 변경시 유연성이 떨어지는 단점이 있음
@Test
@DisplayName("구체 타입으로 조회")
void findBeanByName2() {
//MemberService라는 이름의 빈을 구체 타입인 MemberServiceImpl로 조회
MemberServiceImpl memberService = ac.getBean("memberService", MemberServiceImpl.class);
assertThat(memberService).isInstanceOf(MemberServiceImpl.class);
}
실패 시의 테스트도 생성해보자.
//실패 테스트
@Test
@DisplayName("빈 이름으로 조회X")
void findBeanByNameX() {
//ac.getBean("xxxxx", MemberService.class); -> xxxx는 없는 이름
//오른쪽에 있는 로직이 성공하면, 왼쪽에 있는 예외가 발생
Assertions.assertThrows(NoSuchBeanDefinitionException.class, () ->
ac.getBean("xxxxx", MemberService.class));
}
assertThrows()는 테스트 코드에서 예외 발생을 검증하기 위해 사용되는 메서드이다. 이 메서드는 특정 코드 실행 시 예외가 발생하는지를 확인하고, 해당 예외가 기대한 예외인지를 검증한다.
assertThrows(ExpectedExceptionClass.class, () -> {
// 예외가 발생할 것으로 예상되는 코드 블록
// 해당 코드 블록을 실행할 때 예외가 발생해야 테스트가 성공
});
ExpectedExceptionClass는 기대하는 예외 클래스를 나타내며, 코드 블록 내부에는 특정 동작이나 메서드 호출을 포함한 코드가 위치한다. 이 코드 블록이 실행될 때 예외가 발생하면 테스트는 성공하고, 발생한 예외가 기대한 예외와 일치하지 않으면 테스트는 실패한다.
전체 테스트 코드는 다음과 같다.
import hello.core.AppConfig;
import hello.core.member.MemberService;
import hello.core.member.MemberServiceImpl;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import static org.assertj.core.api.Assertions.*;
class ApplicationContextBasicFindTest {
AnnotationConfigApplicationContext 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("빈 이름으로 조회X")
void findBeanByNameX() {
//ac.getBean("xxxxx", MemberService.class);
Assertions.assertThrows(NoSuchBeanDefinitionException.class, () ->
ac.getBean("xxxxx", MemberService.class));
}
}
cf) 클래스 목록 확인 : Ctrl+E 키
4. 스프링 빈 조회 - 동일한 타입이 둘 이상
타입으로 조회시 같은 타입이 둘 이상이면 오류가 발생한다.
public class ApplicationContextSameBeanFindTest {
AnnotationConfigApplicationContext ac=new AnnotationConfigApplicationContext(SameBeanConfig.class);
@Test
@DisplayName("타입으로 조회시 같은 타입이 둘 이상 있으면, 중복 오류가 발생한다")
void findBeanByTypeDuplicate(){
MemberRepository bean = ac.getBean(MemberRepository.class);
}
//static이면 이 클래스에서만 적용된다
@Configuration
static class SameBeanConfig{
@Bean
public MemberRepository memberRepository1(){
return new MemoryMemberRepository();
}
@Bean
public MemberRepository memberRepository2(){
return new MemoryMemberRepository();
}
}
위와 같은 오류는 빈 이름을 지정해서 해결할 수 있다.
@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 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);
}
- ac.getBeansOfType(MemberRepository.class)를 사용하여 MemberRepository 타입의 모든 빈을 맵 형태로 가져온다.
- Map<String, MemberRepository> beansOfType에는 키는 빈의 이름이 되고, 값은 해당 빈의 객체가 된다.
- 가져온 빈들을 순회하면서 각 빈의 이름과 객체를 출력한다.
- 마지막으로, assertThat(beansOfType.size()).isEqualTo(2)를 사용하여 조회된 MemberRepository 타입의 빈이 2개인지 검증한다.
이 테스트는 MemberRepository 타입의 모든 빈을 스프링 애플리케이션 컨텍스트에서 조회하고, 그 개수가 예상한 것과 일치하는지를 확인한다. 이를 통해 해당 타입의 빈들이 모두 올바르게 등록되고 관리되고 있는지를 검증할 수 있다.
5. 스프링 빈 조회 - 상속 관계
부모 타입으로 조회하면, 자식 타입도 함께 조회한다.
그래서 모든 자바 객체의 최고 부모인 Object 타입으로 조회하면, 모든 스프링 빈을 조회한다.
위 내용을 보여주는 테스트 코드를 생성해보자.
src/test/beanfind에 ApplicationContextExtendsFindTest 클래스를 생성한다.
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 rateDiscountPolicy = ac.getBean("rateDiscountPolicy", DiscountPolicy.class);
assertThat(rateDiscountPolicy).isInstanceOf(RateDiscountPolicy.class);
}
@Configuration
static class TestConfig {
@Bean
public DiscountPolicy rateDiscountPolicy() {
return new RateDiscountPolicy();
}
@Bean
public DiscountPolicy fixDiscountPolicy() {
return new FixDiscountPolicy();
}
}
자식이 둘 이상 있으면 오류가 발생하기 때문에 빈 이름으로 지정해준다.
상속 관계에 따라 특정 하위 타입 혹은 부모 타입으로 조회할 수 있다.
@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));
}
}
Object 타입을 이용하여 모든 스프링 빈을 조회할 수 있다.
@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));
}
}
6. BeanFactory와 ApplicationContext
-BeanFactory
- 스프링 컨테이너의 최상위 인터페이스
- 스프링 빈을 관리하고 조회하는 역할을 담당.
- getBean() 제공
- 지금까지 우리가 사용했던 대부분의 기능은 BeanFactory가 제공하는 기능
- ApplicationContext는 BeanFactory의 기능을 상속받는다.
- ApplicationContext는 빈 관리기능 + 편리한 부가 기능을 제공한다.
- 주로 부가기능이 포함된 ApplicationContext를 사용하고, BeanFactory를 직접 사용할 일은 거의 없다.
- BeanFactory나 ApplicationContext를 스프링 컨테이너라 한다.
7. 다양한 설정 형식 지원 - 자바 코드, XML
스프링 컨테이너는 다양한 형식의 설정 정보를 받아들인다. ex) 자바 코드, XML, Groovy
지금까지 사용했던 AppConfig.class는 자바 코드를 이용한 것이다.
-XML 설정 사용
최근에는 스프링 부트를 많이 사용하면서 XML기반의 설정은 잘 사용하지 않는 추세이다.
그러나 아직 많은 프로젝트들이 XML로 되어 있고, XML을 사용하면 컴파일 없이 빈 설정 정보를 변경할 수 있다.
XML 설정 파일을 만들기 위해 src/test에 xml 패키지를 생성하고, 그 아래에 XmlAppContext 클래스를 생성한다.
import hello.core.member.MemberService;
import org.junit.jupiter.api.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.GenericXmlApplicationContext;
import static org.assertj.core.api.Assertions.*;
public class XmlAppContext {
@Test
void xmlAppContext() {
ApplicationContext ac = new GenericXmlApplicationContext("appConfig.xml");
MemberService memberService = ac.getBean("memberService", MemberService.class);
assertThat(memberService).isInstanceOf(MemberService.class);
}
}
다음으로 src/main/resources에 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 스프링 설정 정보와 자바 코드로 된 AppConfig.java 설정 정보를 비교해보면, 거의 비슷하다.
8. 스프링 빈 설정 메타 정보 - BeanDefinition
스프링은 BeanDefinition이라는 추상화를 이용해서 다양한 설정 형식을 지원한다.
즉, 역할과 구현을 개념적으로 나눈 것으로 XML, 자바코드를 읽어 BeanDefinition을 만들면 된다.
또한 스프링 컨테이너는 BeanDefinition만 알면 된다.
BeanDefinition을 빈 설정 메타정보라고 하는데, @Bean과 <bean> 당 각각 하나씩 메타 정보가 생성된다.
AnnotationConfigApplicationContext는 AnnotatedBeanDefinitionReader를 사용해서
AppConfig.class를 읽고 BeanDefinition을 생성한다.
코드를 통해 BeanDefinition에 대해 자세히 알아보도록 하자.
먼저 src/test/java에 beandefinition 패키지를 생성하고, 그 안에 BeanDefinitionTest 클래스를 생성한다.
import hello.core1.AppConfig;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public 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);
}
}
}
}
'Spring > 스프링 핵심 원리' 카테고리의 다른 글
인프런 스프링 기본 강의 정리 #6 (2) | 2023.11.25 |
---|---|
인프런 스프링 기본 강의 정리 #5 (0) | 2023.11.25 |
인프런 스프링 기본 강의 정리 #3 (0) | 2023.11.20 |
인프런 스프링 기본 강의 정리 #2 (0) | 2023.11.19 |
인프런 스프링 기본 강의 정리 #1 (1) | 2023.11.19 |