인프런의 김영한님의 스프링 강의의 내용이 포함되어있습니다.
[스프링 핵심 원리 - 기본편 - 인프런 | 강의](https://www.inflearn.com/course/스프링-핵심-원리-기본편) |
오늘은 스프링 컨테이너를 생성해 직접 스프링 빈을 등록하고 조회하는 과정에 대해 알아보자.
들어가기전에 스프링 컨테이너와 스프링 빈에 대해 설명하고 가겠다.
스프링 컨테이너와 스프링 빈?
스프링 컨테이너는 스프링 빈의 메타 데이터를 읽어 인스턴스화, 구성 및 조립을 담당한다.
스프링 빈은 스프링 컨테이너에게 관리 당하는 자바 객체를 말한다.
ApplicationContext 나 BeanFactory 를 스프링 컨테이너라 부른다.
- BeanFactory : 스프링 컨테이너의 최상위 인터페이스, 스프링 빈을 관리하고 조회하는 역할을 제공한다.
- ApplicationContext : BeanFactory를 모두 상속받아 기능을 제공하고 많은 부가 기능을 제공한다.
ApplicationContext를 많이 사용한다.
ApplicationContext를 스프링 빈의 메타 데이터를 읽어오는 방법 별로 구현한 객체들이 있다.
- AnnotaionConfigApplication : @애너태이션 기반으로 메타 데이터를 읽어온다.
- GenericXmlApplication : XML 기반으로 메타 데이터를 읽어온다.
1
2
ApplicationContext ac = new GenricXmlApplicationContext(appConfgi.xml);
ApplicationContext ac = new AnnotationApplicationContext(appConfig.class);
스프링 컨테이너의 스프링 빈 등록 과정
- 스프링 컨테이너에 설정 파일을 매개변수로 넣어 주면 스프링 컨테이너가 스프링 빈의 메타데이터를 읽는다.
- 메타 데이터를 읽어온 스프링 컨테이너는 객체를 스프링 빈으로 등록한다.
스프링 컨테이너는 객체의 메타 데이터를 읽어 스프링 빈으로 등록하고 관리 해준다
자 이제 코드로 구현해 보자.
구현
스프링 컨테이너는 Annotaion방식을 사용할것이다.
Repository
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public interface MemberRepository {
void save(Member member);
Member findById(Long memberId);
}
public class MemoryMemberRepository implements MemberRepository{
private static HashMap<Long, Member> store = new HashMap<>();
@Override
public void save(Member member) {
store.put(member.getId(), member);
}
@Override
public Member findById(Long memberId) {
return store.get(memberId);
}
}
Service
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public interface MemberService {
void join(Member member);
Member findMember(Long memberId);
}
public class MemberServiceImp implements MemberService{
MemberRepository memberRepository;
public MemberServiceImp(MemberRepository memberRepository) {
this.memberRepository = memberRepository;
}
@Override
public void join(Member member) {
memberRepository.save(member);
}
@Override
public Member findMember(Long memberId) {
return memberRepository.findById(memberId);
}
}
MemberReposiroy는 인터페이스만 의존하고 구현 객체는 외부(AppConfig)에서 주입해줄것이다.
AppConfig.class
1
2
3
4
5
6
7
8
@Configuration
public class AppConfig {
@Bean
public MemberService memberService(){
return new MemberServiceImp(new MemoryMemberRepository());
}
}
- @Configuration : 스프링 컨테이너가 설정파일을 식별할 수 있게 해주는 애너테이션
- @Bean : 스프링 컨테이너에 등록될 객체를 지정해 주는 애너테이션
Bean의 이름을 지정해줄 수 있지만 지정해주지 않으면 Bean의 이름은 매서드의 이름으로 지정된다.
스프링 컨테이너가 설정 파일을 읽어 memberService라는 이름의 스프링 빈이 스프링 컨테이너에 등록될것이다.
main.class
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class MemberApp {
public static void main(String[]args){
ApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);
MemberService memberService = ac.getBean("memberService",MemberService.class);
Member member = new Member(1L, "김인엽");
memberService.join(member);
Member findMember = memberService.findMember(1L);
System.out.println("member = " + member);
System.out.println("findMember = " + findMember);
System.out.println(member == findMember);
}
}
ApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);
- 애너테이션 기반 스프링 컨테이너 생성과 스프링 빈 등록
MemberService memberService = ac.getBean("memberService",MemberService.class);
- getBean(”Bean이름”,Bean 객체 타입) : memberservice라는 이름을 가진 스프링빈을 반환한다.
- 스프링 컨테이너가 MemberService가 의존하는 MemberRepository의 구현 객체를 생성해 주입해준다.
스프링 컨테이너를 생성하고, 스프링 빈을 직접 등록해보았다.
이제는 스프링 빈의 조회방법들을 알아 보자.
스프링 빈 조회하기
위의 예제에선 스프링 컨테이너에 등록한 빈이 1개밖에 없다.
예제를 위해 빈을 추가 해주었다.
Appconfig.class
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@Configuration
public class Appconfig{
@Bean
MemberService memberService(){
return new MemberServiceImp(memberRepository());
}
@Bean
OrderService orderService(){
return new OrderServiceImp(memberRepository(),discountPolicy());
}
@Bean
MemberRepository memberRepository(){
return new MemoryMemberRepository();
}
@Bean
DiscountPolicy discountPolicy(){
return new RateDiscountPolicy();
}
스프링 컨테이너의 모든 빈 조회하기
1
2
3
4
5
6
7
8
9
10
11
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("bean = " + beanDefinitionName + ", object = "+bean);
}
}
밑에서 부터 4개는 우리가 직접 등록한 (memberService, orderService, discountPolicy, memberRepository) 빈들이 보인다.
근데 자세히 보면 appConfig도 스프링 빈으로 등록된것도 보인다.
사실 스프링 컨테이너는 설정클래스도 스프링 빈으로 등록해준다.
그리고 나머지는 스프링 자체에서 생성해주는 스프링 빈들이다.
내가 등록한 빈만 조회
1
2
3
4
5
6
7
8
9
10
11
12
13
@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("bean = "+ beanDefinitionName + " , object = "+bean);
}
}
}
- ROLE_APPLICATION : 직접 등록한 빈
- ROLE_INFRASTRUCTURE: 스프링이 내부에서 사용하는 빈
결과
빈 이름으로 조회
1
2
3
4
5
6
@Test
@DisplayName("이름으로 빈 조회")
void findBeanByName(){
MemberService memberService = ac.getBean("memberService",MemberService.class);
System.out.println("memberService = " + memberService);
}
결과
이름없이 타입만으로 조회
1
2
3
4
5
6
@Test
@DisplayName("이름없이 타입만으로 조회")
void findBeanByName(){
MemberService memberService = ac.getBean(MemberService.class);
System.out.println("memberService = " + memberService);
}
결과
타입으로 조회시 같은 타입이 둘 이상 일 경우
설정class
1
2
3
4
5
6
7
8
9
10
11
12
@Configuration
static class SameBeanConfig {
@Bean
public MemberRepository memberRepository1() {
return new MemoryMemberRepository();
}
@Bean
public MemberRepository memberRepository2() {
return new MemoryMemberRepository();
}
- 타입이 같은 스프링 빈 등록
1 2 3 4 5 6
@Test @DisplayName("타입으로 조회시 같은 타입이 둘 이상 있을 경우") void findBeanByTypeDuplicate() { MemberRepository bean = ac.getBean(MemberRepository.class); System.out.println("bean = " + bean); }
결과 (NoUniqueBeanDefinitionException 발생!!)
타입만으로 조회 시 같은 타입의 빈들이 여러 있을 경우 중복 오류를 발생한다.
검색할 때 타입과 이름을 모두 매개변수에 지정해주자.
특정 타입의 빈들 조회하기
1
2
3
4
5
6
7
8
@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));
}
}
결과
부모타입으로 조회시 자식이 둘 이상일 경우
설정class
1
2
3
4
5
6
7
8
9
10
11
12
13
@Configuration
static class TestConfig {
@Bean
public DiscountPolicy rateDiscountPolicy() {
return new RateDiscountPolicy();
}
@Bean
public DiscountPolicy fixDiscountPolicy() {
return new FixDiscountPolicy();
}
}
DIscountPolicy를 구현한 두개의 객체를 스프링 빈으로 등록
1
2
3
4
5
6
7
Test
@DisplayName("부모 타입으로 조회시, 자식이 둘 이상 있을 경우")
void findBeanByParentTypeDuplicate() {
DiscountPolicy bean = ac.getBean(DiscountPolicy.class);
}
결과 (NoUniqueBeanDefinitionException 발생!!)
부모 타입으로 조회시 자식이 둘 이상이라면 오류를 발생한다.
오류를 잡기 위해선 매개변수로 부모타입과 이름을 같이 넘겨주어야한다.
부모타입으로 자식타입 모두 조회하기
1
2
3
4
5
6
7
8
@Test
@DisplayName("부모 타입으로 모두 조회하기")
void findAllBeanByParentType() {
Map<String, DiscountPolicy> beansOfType = ac.getBeansOfType(DiscountPolicy.class);
for(String key : beansOfType.keySet()){
System.out.println("key = "+key +", value = "+beansOfType.get(key));
}
}
결과