이 글은 김영한 님의 스프링 핵심 원리 - 기본편(https://www.inflearn.com/스프링-핵심-원리-기본편/dashboard)을 수강하며 학습한 내용을 정리한 글입니다. 모든 출처는 해당 강의에 있습니다.
//스프링 컨테이너 생성
ApplicationContext applicationContext = new
AnnotationConfigApplicationContext(AppConfig.class);
BeanFactory
ApplicationContext
AnnotationConfigApplicationContext
→ 구현체AppConfig
new AnnotationConfigApplicationContext(AppConfig.class)
AppConfig.class
) 지정 필요파라미터로 넘어온 설정 클래스 정보를 사용하여 스프링 빈 등록
@Bean(name="memberService2")
💡 스프링은 빈을 생성하고 의존관계를 주입하는 단계가 나누어져 있으나, 자바 코드로 스프링 빈을 등록하면 생성자 호출과 의존관계 주입이 한 번에 처리 된다.
[src/test/java/hello/core/bean/find/ApplicationContextInfoTest.java]
package hello.core.bean.find;
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;
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);
if(beanDefinition.getRole() == BeanDefinition.ROLE_APPLICATION) {
Object bean = ac.getBean(beanDefinitionName);
System.out.println("name = " + beanDefinitionName + ", object = " + bean);
}
}
}
}
모든 빈 출력
ac.getBeanDefinitionNames()
: 스프링에 등록된 모든 빈 이름 조회ac.getBean()
: 빈 이름으로 빈 객체(인스턴스) 조회애플리케이션 빈 출력
getRole()
: 빈 구분ROLE_APPLICATION
: 일반적으로 사용자가 정의한 빈ROLE_INFRASTRUCTURE
: 스프링이 내부에서 사용하는 빈스프링 컨테이너에서 스프링 빈을 찾는 가장 기본적인 조회 방법은 다음과 같다.
ac.getBean(빈이름, 타입)
ac.getBean(타입)
조회 대상 스프링 빈이 존재하지 않으면 예외가 발생한다.
NoSuchBeanDefinitionException: No bean named 'xxxxx' available
[src/test/java/hello/core/bean/find/ApplicationContextBasicFindTest.java]
package hello.core.bean.find;
import hello.core.AppConfig;
import hello.core.member.MemberService;
import hello.core.member.MemberServiceImpl;
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.assertThat;
import static org.junit.jupiter.api.Assertions.assertThrows;
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() {
MemberService memberService =
ac.getBean("memberService", MemberServiceImpl.class);
assertThat(memberService).isInstanceOf(MemberServiceImpl.class);
}
@Test
@DisplayName("빈 이름으로 조회 x")
void findBeanByNameX() {
// ac.getBean("xxxxx", MemberService.class);
assertThrows(NoSuchBeanDefinitionException.class, () ->
ac.getBean("xxxxx", MemberService.class));
}
}
💡 구체 타입으로 조회하면 변경 시 유연성이 떨어짐
ac.getBeansOfType()
: 해당 타입의 모든 빈 조회[src/test/java/hello/core/bean/find/ApplicationContextSameBasicFindTest.java]
package hello.core.bean.find;
import hello.core.member.MemberRepository;
import hello.core.member.MemoryMemberRepository;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.NoUniqueBeanDefinitionException;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.Map;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertThrows;
class ApplicationContextSameBeanFindTest {
AnnotationConfigApplicationContext ac = new
AnnotationConfigApplicationContext(SameBeanConfig.class);
@Test
@DisplayName("타입으로 조회시 같은 타입이 둘 이상 있으면, 중복 오류가 발생한다")
void findBeanByTypeDuplicate() {
// DiscountPolicy 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);
}
@Configuration
static class SameBeanConfig {
@Bean
public MemberRepository memberRepository1() {
return new MemoryMemberRepository();
}
@Bean
public MemberRepository memberRepository2() {
return new MemoryMemberRepository();
}
}
}
Object
타입으로 조회 → 모든 스프링 빈 조회[src/test/java/hello/core/bean/find/ApplicationContextExtendsFindTest.java]
package hello.core.bean.find;
import hello.core.discount.DiscountPolicy;
import hello.core.discount.FixDiscountPolicy;
import hello.core.discount.RateDiscountPolicy;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.NoUniqueBeanDefinitionException;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.Map;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertThrows;
class ApplcationContextExtendsFindTest {
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);
}
@Test
@DisplayName("특정 하위 타입으로 조회")
void findBeanBySubType() {
DiscountPolicy 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") //스프링 내부 Bean까지 조회
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();
}
}
}
getBean()
제공스프링 애플리케이션 전반에 걸쳐 모든 구성요소의 제어 작업을 담당하는 IoC 엔진
BeanFactory
기능을 모두 상속 받아 제공💡 리플렉션
- 구체적인 클래스 타입을 알지 못해도, 그 클래스의 메소드, 타입, 변수들에 접근할 수 있도록 해주는 자바 API
- 컴파일한 클래스 정보를 활용해 동적으로 프로그래밍이 가능하도록 지원하는 API
💡 싱글톤
소프트웨어 디자인 패턴에서 싱글턴 패턴(Singleton pattern)을 따르는 클래스는, 생성자가 여러 차례 호출되더라도 실제 생성 객체는 하나이고 최초 생성 이후에 호출된 생성자는 최초의 생성자가 생성한 객체를 리턴한다. 이와 같은 디자인 유형을 싱글턴 패턴이라고 한다.
메시지소스를 활용한 국제화 기능
ex) 한국에서 접속 시 한국어로, 영어권에서 접속 시 영어로 출력
환경변수 : 로컬, 개발, 운영 등을 구분해서 처리
애플리케이션 이벤트 : 이벤트 발행 및 구독 모델 편리하게 지원
편리한 리소스 조회 : 리소스를 편리하게 조회
ex) 파일, 클래스패스, 외부 등
new AnnotationConfigApplicationContext(AppConfig.class)
AnnotationConfigApplicationContext
사용하여 자바 코드로 작성된 설정 정보 전달GenericXmlApplicationContext
사용하여 xml
설정 파일 전달[src/test/java/hello/core/xml/XmlAppContext.java]
package hello.core.xml;
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.assertThat;
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>
@Bean
/ <Bean>
당 하나씩 메타 정보 생성항목 | 설명 |
---|---|
BeanClassName | ◾ 생성할 빈의 클래스 명 ◾ 팩토리 역할의 빈 사용 시 존재 x |
factoryBeanName | 팩토리 역할의 빈 사용하는 경우 이름 ex) appConfig |
factoryMethodName | 빈을 생성할 팩토리 메서드 지정 ex) memberService |
Scope | 싱글톤(기본값) |
lazyInit | 실제 빈을 사용할 때 까지 최대한 빈 생성을 지연처리 하는지 여부 |
InitMethodName | 빈 생성 및 의존관계 적용 후 호출되는 초기화 메서드 명 |
DestroyMethodName | 빈의 생명주기가 끝나서 제거하기 직전에 호출되는 메서드 명 |
Constructor arguments, Properties | ◾ 의존관계 주입에서 사용 ◾ 팩토리 역할의 빈 사용 시 존재 x |
[src/test/java/hello/core/bean/definition/BeanDefinitionTest.java]
package hello.core.bean.definition;
import hello.core.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;
import org.springframework.context.support.GenericXmlApplicationContext;
public class BeanDefinitionTest {
// AnnotationConfigApplicationContext ac =
// new AnnotationConfigApplicationContext(AppConfig.class);
GenericXmlApplicationContext ac = new
GenericXmlApplicationContext("appConfig.xml");
@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
을 직접 생성해서 스프링 컨테이너에 등록할 수 있으나, 실무에서는 직접 정의하거나 사용할 일이 거의 없다.