일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | ||||||
2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 | 29 |
30 | 31 |
- Til
- 코딩테스트준비
- BinarySearch
- 네트워크 계층
- Spring
- 99클럽
- lower bound
- 브루트포스
- 그리디
- 우선순위큐
- 정렬
- 동적 프로그래밍
- BFS
- 백트래킹
- 백준
- Java
- DFS
- 개발자취업
- 스프링
- 완전탐색
- 그래프
- 트리
- 데이터베이스
- 알고리즘
- 스프링 핵심 원리 - 기본편
- DP
- 그래프 이론
- 항해99
- 프로그래머스
- 자바
- Today
- Total
AtraFelis's Develop Diary
[Spring] 스프링 컨테이너와 스프링 빈 (스프링 핵심원리 - 기본편 section5) 본문
스프링 핵심 원리 - 기본편 강의를 수강하며 작성한 글입니다.
Section 5
이제 순수한 자바에서 벗어나, 스프링을 사용해보자.
먼저 기존의 AppConfig
를 직접 사용하여 DI하던 것을 스프링을 사용하는 형태로 수정한다.
@Configuration
public class AppConfig {
@Bean
public MemberService memberService() {
return new MemberServiceImpl(memberRepository());
}
@Bean
public MemberRepository memberRepository() {
return new MemoryMemberRepository();
}
@Bean
public OrderService orderService() {
return new OrderServiceImpl(memberRepository(), discountPolicy());
}
@Bean
private static DiscountPolicy discountPolicy() {
return new FixDiscountPolicy();
}
}
@Configuration
애노테이션으로 AppConfig를 설정 파일로 등록해준 후, 각 메소드에 @Bean
를 붙여준다. @Bean
애노테이션이 붙은 매소드에서 반환되는 구현체들은 스프링 컨테이너라는 곳에 저장되어 사용된다. 이렇게 스프링 컨테이너에 등록된 개체들을 스프링 빈이라고 한다.
또한 기존에 AppConfig를 사용하던 OrderApp과 같은 클래스도 스프링을 활용하도록 수정을 해준다.
public class OrderApp {
public static void main(String[] args) {
// AppConfig appConfig = new AppConfig();
// MemberService memberService = appConfig.memberService();
// OrderService orderService = appConfig.orderService();
ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
MemberService memberService = context.getBean("memberService", MemberService.class);
OrderService orderService = context.getBean("orderService", OrderService.class);
...
}
}
이렇게 하면, 스프링을 이용하여 DI를 할 수 있게 되었다.
여기서 ApplicationContext가 스프링 컨테이너이다. 이 스프링 컨테이너에서 getBean()
메소드를 이용해 등록해둔 스프링 빈을 꺼내어 사용하면 된다.
이렇게 수정하면 뭐가 좋은 건가요?
이것에 대한 것은 Section 6. 싱글톤 컨테이너에서 다룬다. 그래도 간략하게 설명하자면, 이렇게 함으로써, 단 하나의 객체만을 생성하여 공유할 수 있게 된다. 이를 통해 메모리 소모를 줄일 수 있다.
스프링 컨테이너와 스프링 빈
스프링 컨테이너를 만드는 방식에는 두 가지가 있다.
XML 방식과 애노테이션 기반의 자바 설정 클래스 방식이다. 위에서 AppConfig클래스를 이용한 방식이 후자의 방식이다. 이 방식이 매우 간편하고 잘 되어있기 때문에, XML 방식은 요즘 잘 쓰지 않는다.
스프링 컨테이너의 생성 과정
1. 스프링 컨테이너 생성
new AnnotationConfigApplicationContext(AppConfig.class)
와 같은 방식으로 스프링 컨테이너를 생성할 수 있다.
한글로 풀어 적어보자면, 애노테이션 기반의 스프링 컨테이너를 생성하되 구성 정보는 AppConfig.class
를 참고하라, 라고 할 수 있겠다.
XML 기반이라면 new GenericXmlConfigApplicationContext(AppConfig.class)
이 될 것이다.
2. 스프링 빈 등록
스프링 내부적으로 AppConfig.class
를 보며 스프링 컨테이너에 스프링 빈을 채워넣기 시작한다.
위에서 상술했듯 AppConfig.class
내부의 메소드들이 반환하는 객체들을 스프링 컨테이너에 등록하는 것이다. 빈 이름은 따로 지정하지 않을 시 메소드의 이름과 동일하게 설정된다.
빈 이름 지정하는 방법
이렇게 따로 빈 이름을 지정할 수는 있지만, 추후에 헷갈릴 수 있기 때문에 권장하지 않는다. 보통 이렇게 이름을 따로 지정하는 경우는 같은 타입의 객체를 스프링 빈에 등록해주어야 할 때인데, 애초부터 중복이 일어나지 않게끔 만들자.
java @Configuration public class AppConfig { @Bean(name = "ms") public MemberService memberService() { return new MemberServiceImpl(memberRepository()); } ... }
3. 스프링 빈 의존관계 설정
설정 정보를 참고하여 의존관계를 주입한다.
이와 관련된 내용은 바로 다음 포스팅 6. 싱글톤 컨테이너에서 다룬다.
컨테이너에서 빈 조회하기
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);
이 예시에서 모든 스프링 컨테이너는 ac라는 이름으로 이와 같이 선언되어 있다.
ac.getBeanDefinitionNames()
: 스프링에 등록된 모든 빈 이름을 조회하여 String 배열의 형태로 반환한다.
`ac.getBean(타입)
: 타입만으로 검색한다. -ac.getBean(MemberService.class)
- 같은 타입의 빈이 두 개 존재할 경우,
NoUniqueBeanDefinitionException
예외 발생.
- 같은 타입의 빈이 두 개 존재할 경우,
ac.getBean(빈이름, 타입)
: 빈이름과 타입으로 검색한다. - `MemberService bean = ac.getBean("memberService", MemberService.class)- 조회 대상 빈이 없다면,
NoSuchBeanDefinitionException
예외 발생.
- 조회 대상 빈이 없다면,
ac.getBeansOfType(타입)
: 해당 타입을 가지는 모든 빈을Map<빈이름, 타입>
의 형태로 반환한다. -Map<String, MemberRepository> beansOfType = ac.getBeansOfType(MemberRepository.class);
- 부모 타입으로 조회할 경우 하위 타입의 모든 스프링 빈을 조회할 수 있다.
ac.getBeansOfType(Object.class)
와 같이 조회하면, 존재하는 모든 스프링 빈을 조회할 수 있다.
BeanFactory
ApplicationContext
를 스프링 컨테이너라고 했었는데, 사실 스프링 컨테이너라고 하면 BeanFactory
까지 포함하여 스프링 컨테이너라고 할 수 있다. (용어가 좀 모호한 감이 없지 않아 있다. 보통 BeanFactory
를 직접 사용할 일은 없으므로 스프링 컨테이너라고 하면 ApplicationContext
를 지칭한다고 보면 될 것 같다.)
위 그림과 같이, ApplicationContext
가 BeanFactory
를 상속받는 구조이다. 즉, BeanFactory
에서 조금 더 기능을 추가해 놓은 것이 ApplicationContext
이다.
ApplicationContext
의 코드를 직접 확인해보자.
public interface ApplicationContext extends
EnvironmentCapable, ListableBeanFactory, HierarchicalBeanFactory,
MessageSource, ApplicationEventPublisher, ResourcePatternResolver
{
@Nullable
String getId();
String getApplicationName();
String getDisplayName();
long getStartupDate();
@Nullable
ApplicationContext getParent();
AutowireCapableBeanFactory getAutowireCapableBeanFactory() throws IllegalStateException;
}
BeanFactory
와 함께 다른 여러가지 인터페이스도 상속받고 있는 것을 볼 수 있다.
그냥 BeanFactory가 아니라 다른 걸 상속받고 있는데요?
눈썰미가 좋은 분들은 눈치챘겠지만, 확인해보면 바로 BeanFactory를 상속받는 것이 아니라, BeanFactory를 상속하는 ListableBeanFactory, HierarchicalBeanFactory를 상속 받는 것을 확인할 수 있다.
ListableBeanFactory는 여러 Bean 정보를 목록 형태로 가져올 수 있는 추가 메서드들을 정의하는 BeanFactory의 확장 인터페이스이다. 이 인터페이스에서 getBeansOfType()과 같은 메소드를 정의하고 있다.
HierarchicalBeanFactory는 부모(Parent) BeanFactory를 참조하는 기능을 제공함으로써, 계층적인(계승되는) 컨테이너 구조를 지원한다. (by ChatGPT o1)
위 두 인터페이스는 BeanFactory에서 약간의 확장 기능만을 추가하고 있으므로, 그냥 BeanFactory를 상속받는다고 생각해도 될 듯하다. 그래서 김영한 님도 강의에서 별 다른 언급이 없으셨던게 아닐까? 아니면 심화 강의에서 언급하실 수도 있고.
각 인터페이스들은 아래와 같은 역할을 하는 메소드들을 정의하고 있다.
- 메시지소스를 활용한 국제화 기능 -
MessageSource
- 예를 들어서 한국에서 들어오면 한국어로, 영어권에서 들어오면 영어로 출력
- 환경변수 -
EnvironmentCapable
- 로컬, 개발, 운영등을 구분해서 처리
- 애플리케이션 이벤트 -
ApplicationEventPublisher
- 이벤트를 발행하고 구독하는 모델을 편리하게 지원
- 편리한 리소스 조회 -
ResourcePatternResolver
- 파일, 클래스패스, 외부 등에서 리소스를 편리하게 조회
BeanDefinition
스프링 컨테이너를 설정하는 방법은 여러가지가 있다고 언급했었다.
지금까지 해왔던 것처럼 자바 코드 기반의 애노테이션을 이용할 수도 있고 XML 기반의 구성 정보를 이용할 수도 있다. 혹은 사용자가 원하는 형식으로(예를 들어 JSON으로 한다거나) 만들어 사용할 수도 있다.
이렇게 보았을 때 지금까지 해왔던 것들이 슥 스쳐지나가면서, 무언가 떠오르지 않는가?
바로 역할과 구현의 분리다.
클라이언트 코드인 OrderApp
이 어떤 할인 정책(DiscountPolicy
)를 사용하든, 어떤 저장소(MemberRepository
)를 사용하든 상관 없이 그 기능을 실행하는데만 집중했던 것처럼, 스프링 컨테이너도 마찬가지다.
스프링 컨테이너는 사용자가 구성 파일을 자바 코드로 작성했든, XML로 작성했든, JSON으로 작성했든 상관하지 않는다. 그냥 읽고 BeanDefinition
이라는 빈 설정 메타정보를 만들면 된다.
김영한 님의 말에 따르면, AnnotationConfigApplicationContext
를 이용하여 BeanDefinition
을 생성한다고 한다.
직접 코드를 뜯어 확인해보자.
public class AnnotationConfigApplicationContext extends GenericApplicationContext implements AnnotationConfigRegistry {
private final AnnotatedBeanDefinitionReader reader;
private final ClassPathBeanDefinitionScanner scanner;
public AnnotationConfigApplicationContext() {...}
public AnnotationConfigApplicationContext(DefaultListableBeanFactory beanFactory) {...}
public AnnotationConfigApplicationContext(Class<?>... componentClasses) {...}
public AnnotationConfigApplicationContext(String... basePackages) {...}
public void setEnvironment(ConfigurableEnvironment environment) {...}
public void setBeanNameGenerator(BeanNameGenerator beanNameGenerator) {...}
public void setScopeMetadataResolver(ScopeMetadataResolver scopeMetadataResolver) {...}
public void register(Class<?>... componentClasses) {...}
public void scan(String... basePackages) {...}
public <T> void registerBean(@Nullable String beanName, Class<T> beanClass, @Nullable Supplier<T> supplier, BeanDefinitionCustomizer... customizers) {...}
}
AnnotationConfigApplicationContext
클래스 파일을 열어보면, 이렇게 정의되어 있다.
강의에 언급된 것처럼 AnnotatedBeanDefinitionReader
도 선언되어 있는 것을 확인할 수 있다. 이것을 활용하여 구성 정보 파일을 읽고, 빈 메타정보를 생성한다.
강의에 있는 BeanDefinition
을 확인하는 테스트 코드를 실행하면 이렇게 메타 정보를 조회할 수도 있다. (BeanDefinitionTest.class
에서 선언한 findApplicationBean()
메소드)
각 BeanDefinition
이 무슨 역할을 담당하고 있는지는 굳이 외울 필요 없다. 필요할 때 공식 문서에서 찾아보면 된다. (실제로 별로 사용할 일 없다고 한다.)
여기서 중요한 것은 스프링 컨테이너가 어떻게 구성 파일을 읽고 BeanDefinition
을 생성하는지에 대한 일련의 과정이다. 이것만 기억하고 있으면 될 것 같다.
References
'Programming > Spring' 카테고리의 다른 글
[Spring] 컴포넌트 스캔 (스프링 핵심원리 - 기본편 Section7) (1) | 2025.01.17 |
---|---|
[Spring] 싱글톤 컨테이너 (스프링 핵심원리 - 기본편 Section6) (0) | 2025.01.13 |
[Spring] 객체 지향 원리 적용하기 (스프링 핵심원리 - 기본편 section4) (0) | 2025.01.11 |
[Spring] 객체 지향 설계와 스프링 (스프링 핵심 원리 - 기본편 Section2) (0) | 2025.01.10 |
[Spring] 스프링 프레임워크와 스프링 부트 (0) | 2025.01.08 |