김영한 님의 스프링 핵심 원리 - 기본편 강의를 보고 작성한 내용입니다.
https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-%ED%95%B5%EC%8B%AC-%EC%9B%90%EB%A6%AC-%EA%B8%B0%EB%B3%B8%ED%8E%B8/dashboard
// 스프링 컨테이너 생성
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);
ApplicationContext
: 스프링 컨테이너 & 인터페이스
ApplicationContext
의 구현체 : AnnotationConfigApplicationContext
스프링 컨테이너는 XML을 기반으로 만들 수 있고, 어노테이션 기반의 자바 설정 클래스로 만들 수 있다
위의 코드는 어노테이션 기반의 자바 설정 클래스로 스프링 컨테이너를 만든 것이다
new AnnotationConfigApplicationContext(AppConfig.class)
스프링 컨테이너를 생성할 때는 구성 정보를 지정해주어야 함
위에서는 AppConfig.class
가 구성정보
스프링 컨테이너가 AppConfig.class
의 구성 정보를 활용해서 객체를 생성한다
스프링 컨테이너 내부에는 스프링 빈 저장소가 있다
@Bean
이 붙어있는 메소드의 이름을 빈 이름(key 값)으로, 메소드가 반환하는 객체를 빈 객체로 스프링 빈 저장소에 등록
@Bean(name="memberService2")
처럼 빈 이름을 직접 부여할 수도 있다
빈 이름은 항상 다른 이름을 부여해야 한다
스프링 컨테이너는 구성 정보 클래스 파일에서 객체의 생성자에 나타나는 의존 관계를 참고하여 의존관계를 주입한다
스프링은 빈을 생성하는 단계와 의존관계를 주입하는 단계가 나누어져 있지만, 자바 코드로 스프링 빈을 등록하면 생성자를 호출하면서 의존관계 주입도 한 번에 처리된다
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);
}
}
}
ac.getBeanDefinitionNames()
: 스프링에 등록된 모든 빈 이름을 조회
ac.getBean()
: 빈 이름으로 빈 객체(인스턴스)를 조회
@Test
@DisplayName("애플리케이션 빈 출력하기")
void findApplicationBean() {
String[] beanDefinitionNames = ac.getBeanDefinitionNames();
for (String beanDefinitionName : beanDefinitionNames) {
BeanDefinition beanDefinition = ac.getBeanDefinition(beanDefinitionName);
if(beanDefinition.getRole() == BeanDefinition.ROLE_APPLICATION) {
Object bean = ac.getBean(beanDefinitionName);
System.out.println("name = " + beanDefinitionName + "object = " + bean);
}
}
}
BeanDefinition.ROLE_APPLICATION
: 애플리케이션 개발을 위해 직접 등록한 애플리케이션 빈 (일반적으로 사용자가 정의한 빈)
BeanDefinition.ROLE_INFRASTRUCTURE
: 스프링이 내부에서 사용하는 빈
ac.getBean(빈이름, 타입)
ac.getBean(타입)
조회 대상 스프링 빈이 없으면 예외 발생
NoSuchBeanDefinitionException: No bean named 'xxxxx' available
@Test
@DisplayName("빈 이름으로 조회")
void findBeanByName() {
MemberService memberService = ac.getBean("memberService", MemberService.class);
assertThat(memberService).isInstanceOf(MemberServiceImpl.class);
}
memberService
: 빈 이름 (메소드 이름)
등록된 객체는 MemberServiceImpl
의 인스턴스
위 방식은 MemberService
라는 인터페이스로 조회하는 방식
인터페이스 이름으로 조회하면 인터페이스의 구현체가 대상이 된다
@Test
@DisplayName("구체 타입으로 조회")
void findBeanByName2() {
MemberService memberService = ac.getBean("memberService", MemberServiceImpl.class);
assertThat(memberService).isInstanceOf(MemberServiceImpl.class);
}
MemberServiceImpl
라는 객체가 등록되어 있으면 조회가 된다
@Test
@DisplayName("빈 이름으로 조회 실패")
void findBeanByNameX() {
//ac.getBean("xxxx", MemberService.class);
assertThrows(NoSuchBeanDefinitionException.class,
() -> ac.getBean("xxxx", MemberService.class));
}
오른쪽 람다로 작성된 로직을 실행하면 왼쪽의 예외가 발생한다는 의미
ac.getBean("xxxx", MemberService.class)
를 실행하면 NoSuchBeanDefinitionException
이 발생한다
org.junit.jupiter.api.Assertions.*
의 assertThrows()
를 사용
assertThat()
은 org.assertj.core.api.Assertions.*
를 import
@Configuration
static class SameBeanConfig {
@Bean
public MemberRepository memberRepository1() {
return new MemoryMemberRepository();
}
@Bean
public MemberRepository memberRepository2() {
return new MemoryMemberRepository();
}
}
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(SameBeanConfig.class);
SameBeanConfig를 스프링 컨테이너로 사용
스프링 컨테이너에는 객체 타입이 MemberRepository
로 동일한 두 개의 빈이 존재
@Test
@DisplayName("타입으로 조회 시 같은 타입이 둘 이상 있으면, 중복 오류 발생")
void findBeanByTypeDuplicate() {
assertThrows(NoUniqueBeanDefinitionException.class,
() -> ac.getBean(MemberRepository.class));
}
타입으로 조회할 때 같은 타입의 스프링 빈이 둘 이상이면 오류가 발생
NoUniqueBeanDefinitionException
이런 경우, 빈 이름을 지정해야함
@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()
을 사용하면 해당 타입의 모든 빈을 조회할 수 있다
부모 타입으로 조회하면, 자식 타입도 함께 조회한다
모든 자바 객체의 최고 부모인 Object
타입으로 조회하면, 모든 스프링 빈을 조회한다
@Test
@DisplayName("부모 타입으로 조회 시, 자식이 둘 이상 있으면 중복 오류가 발생")
void findBeanByParentTypeDuplicate() {
assertThrows(NoUniqueBeanDefinitionException.class,
() -> ac.getBean(DiscountPolicy.class));
}
@Test
@DisplayName("부모 타입으로 조회 시, 자식이 둘 이상 있으면 빈 이름 지정")
void findBeanByParentTypeBeanName() {
DiscountPolicy rateDiscountPolicy = ac.getBean("rateDiscountPolicy", DiscountPolicy.class);
assertThat(rateDiscountPolicy).isInstanceOf(RateDiscountPolicy.class);
}
@Test
@DisplayName("특정 하위 타입으로 조회")
void findBeanBySubType() {
RateDiscountPolicy bean = ac.getBean(RateDiscountPolicy.class);
assertThat(bean).isInstanceOf(RateDiscountPolicy.class);
}
자식이 두 개 이상 있는 경우, 특정 하위 타입으로 조회할 수 있음
but> 좋은 방법은 아님
6-1 은 부모 타입에 해당하는 DisCountPolicy
로 조회하면서 이름을 지정했는데 이번에는 특정 하위 타입 (자식 타입)으로 조회
ac.getBean()
에 하위타입을 작성
@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));
}
}
ac.BeansOfType()
에 Object.Class
를 작성하면 스프링 내부적으로 사용하는 빈까지 모든 것이 출력됨스프링 컨테이너의 최상위 인터페이스
스프링 빈을 관리하고 조회(검색)하는 역할을 담당
getBean()
을 제공
BeanFactory를 직접 사용할 일은 거의 없고 부가 기능이 포함된 ApplicationContext를 사용
BeanFactory
나 ApplicationContext
를 스프링 컨테이너라고 함
BeanFactory
기능을 모두 상속받아서 제공한다
ApplicatinContext = BeanFactory (빈 관리, 조회 기능) + 부가기능
ApplicationContext를 구현한 클래스 = AnnotationConfigApplicationContext
애플리케이션을 개발할 때는 빈을 관리하고 조회하는 기능은 물론이고, 수 많은 부가기능이 필요
public interface ApplicationContext extends EnvironmentCapable, ListableBeanFactory, HierarchicalBeanFactory,
MessageSource, ApplicationEventPublisher, ResourcePatternResolver
extends
한 인터페이스메시지소스를 활용한 국제화 기능
파일을 분리해서 작성해놓고
메세지 소스가 한국에서 들어오면 한국어로, 영어권에서 들어오면 영어로 된 파일을 출력
환경변수
로컬, 개발, 운영 등을 구분해서 처리
로컬 : 내 PC에서 개발하는 환경
개발 : 테스트 서버에 올려서 여러 시스템을 엮어 실제 테스트 서버에 띄워두고 테스트할 수 있는 서버
운영 : 실제 프로덕션이 나감
각 환경 별로 어떤 DB를 연결해야할지와 같은 것들이 다르기 때문에 환경변수와 관련된 정보를 처리해주는 기능
애플리케이션 이벤트
편리한 리소스 조회
스프링 컨테이너는 다양한 형식의 설정 정보를 받아드릴 수 있다
어노테이션 기반 자바 코드
xml : xml 문서를 설정 정보로 사용
임의로 구현해서 설정 정보로 사용
AnnotationConfigApplicationContext
클래스를 사용하면서 자바 코드로된 설정 정보를 넘기면 된다
여기서 자바 코드로 된 설정 정보 = AppConfig.class
new AnnotationConfigApplicationContext(AppConfig.class)
AppConfig는 @Configuration
어노테이션을 사용
xml 문서를 설정 정보로 사용
xml을 사용하면 컴파일 없이 빈 설정 정보를 변경할 수 있다
GenericXmlApplicationContext
를 사용하면서 xml 설정 파일을 넘기면 된다
GenericXmlApplicationContext ac = new GenericXmlApplicationContext("appConfig.xml");
아래 코드는 각각 Java, xml로 작성된 설정 정보 파일이지만 동일한 내용을 담고 있음
< appConfig.xml >
<bean id = "memberService" class = "com.ghkwhd.core.member.MemberServiceImpl">
<constructor-arg name = "memberRepository" ref = "memberRepository" />
</bean>
<bean id = "memberRepository" class = "com.ghkwhd.core.member.MemoryMemberRepository" />
< AppConfog.class >
@Bean
public MemberService memberService() {
return new MemberServiceImpl(memberRepository());
}
@Bean
public MemberRepository memberRepository() {
return new MemoryMemberRepository();
}
스프링이 다양한 설정 형식을 지원할 수 있는 이유 : BeanDefinition
이라는 추상화 ( 빈 정보에 대한 것 자체를 추상화 )
역할과 구현을 나눈 것이라고 생각하면 된다
BeanDefinitinon
을 만든다BeanDefinitinon
: 빈 설정 메타정보
@Bean
, <bean>
마다 각각 하나씩 메타 정보가 생성 스프링 컨테이너는 어떤 파일이든 관계 없이 BeanDefinitinon
에만 의존하고 이 메타 정보를 기반으로 스프링 빈을 생성
AnnotationConfigApplicationContext
는 AnnotatedBeanDefinitionReader
를 사용해서 AppConfig.class
를 읽고 BeanDefinition
을 생성한다
GenericXmlApplicationContext
는 XmlBeanDefinitionReader
를 사용해서 appConfig.xml
설정 정보를 읽고 BeanDefinition
을 생성한다
새로운 형식의 설정 정보가 추가되면, XxxBeanDefinitionReader
를 만들어서 BeanDefinition
을 생성하면 된다
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 + "beanDefinitnion" + beanDefinition);
}
}
}
BeanClassName: 생성할 빈의 클래스 명(자바 설정 처럼 팩토리 역할의 빈을 사용하면 없음)
factoryBeanName: 팩토리 역할의 빈을 사용할 경우 이름, 예) appConfig
factoryMethodName: 빈을 생성할 팩토리 메서드 지정, 예) memberService
Scope: 싱글톤(기본값)
lazyInit: 스프링 컨테이너를 생성할 때 빈을 생성하는 것이 아니라, 실제 빈을 사용할 때 까지 최대한 생성을 지연처리 하는지 여부
InitMethodName: 빈을 생성하고, 의존관계를 적용한 뒤에 호출되는 초기화 메서드 명
DestroyMethodName: 빈의 생명주기가 끝나서 제거하기 직전에 호출되는 메서드 명
Constructor arguments, Properties: 의존관계 주입에서 사용한다(자바 설정 처럼 팩토리 역할의 빈을 사용하면 없음)
BeanDefinition을 직접 생성해서 스프링 컨테이너에 등록할 수 도 있다. 하지만 실무에서 BeanDefinition을 직접 정의하거나 사용할 일은 거의 없다.
BeanDefinition에 대해서는 너무 깊이있게 이해하기 보다는, 스프링이 다양한 형태의 설정 정보를 BeanDefinition으로 추상화해서 사용하는 것 정도만 이해하면 된다
가끔 스프링 코드나 스프링 관련 오픈 소스의 코드를 볼 때, BeanDefinition 이라는 것이 보일 때가 있다. 이때 이러한 메커니즘을 떠올리면 된다
이전에 작업했던 파일 : Ctrl + E
리스트가 있을 때, 자동으로 for 문 완성 : iter + Tab