지난 시간까지 순수 자바 코드로 만든 예제 프로젝트의 문제점을 개선하면서 스프링으로 전환해봤다.

그러면서 왜 스프링이 필요한지, 스프링의 DI 개념에 대해 배웠다. 이번 시간에는 스프링 그 자체에 대해 배운다. 

목표

1.  스프링 컨테이너의 생성 과정을 배운다. 
2. 스프링 빈을 찾는 기본 방법을 배운다. 

'스프링 컨테이너와 스프링 빈' 목차

1. 스프링 컨테이너 생성  (이번 포스팅)

2. 컨테이너에 등록된 모든 빈 조회

3. 스프링 빈 조회 - 기본

4. 스프링 빈 조회 - 동일한  타입이 둘 이상

5. 스프링 빈 조회 - 상속 관계

6. BeanFactory와 ApplicationContext 

7. 다양한 설정 형식 지원 - 자바 코드, XML

8. 스프링 빈 설정 메타 정보 - BeanDefinition


1. 스프링 컨테이너 생성 과정

이전 시간에 AppConfig.class를 넘겨서 생성한 스프링 컨테이너 코드를 떠올려보자. 

컨테이너라는게 '객체를 담고 있다'는 뜻이다. 

ApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);

ApplicationContext는 스프링 컨테이너다. 

ApplicationContext 는 인터페이스인데, 그것을 구현한 클래스가 AnnotationConfigApplicationContext다. 

스프링 컨테이너를 생성하는 방법에는 XML 기반과 애노테이션 기반이 있다.

우리가 AppConfig.class 에 @Configuration 애노테이션을 달고, 메서드에 @Bean을 달았는데. 이 방법이 애노테이션 기반 자바 설정 클래스로 스프링 컨테이너를 생성한 것이다.

 

참고 : 더 정확히는 스프링 컨테이너를 부를 때 BeanFactory와 ApplicationContext를 구분한다. 뒤에서 더 자세히 배운다. 

 

1) 스프링 컨테이너 생성 과정 

스프링 컨테이너를 생성 할 때 구성 정보(설정 정보)를 정해줘야 한다. 여기서는 AppConfig.class 를 구성 정보로 지정했다. 

2) 스프링 빈 등록

스프링 컨테이너는 파라미터로 넘어온 설정 클래스 정보를 사용해서 @Bean이 붙은 메서드의 반환 객체를 모두 스프링 빈으로 등록한다.

 

{ @Bean 이름 - @Bean 객체 } 쌍으로 컨테이너에 저장된다. 

아래에 코드를 보자. memberService() 메서드에 @Bean 애노테이션이 붙어있다. 

memberService 이름의 스프링빈은 아래의 쌍으로 저장된다. 

{ @Bean이름: memberService  - @Bean객체: new MemberServiceImpl(memberRepository())  }

빈 이름은 디폴트로 메서드 이름과 똑같이 정해지는데 직접 이름을 지을 수도 있다. 

주의: 빈 이름은 중복 불가

 

3) 스프링 빈 의존관계 설정 - 준비 

애노테이션 기반 자바 설정 클래스(AppConfig.class)를 기반으로 스프링 컨테이너를 생성한다. 

@Bean 을 달아놓은 메서드를 전부 호출하여 메서드 명 그대로 이름붙여서 컨테이너에 스프링 Bean으로 등록한다. 

4) 스프링 빈 의존관계 설정 - 완료 

스프링 컨테이너가 설정 정보를 참고해서 의존관계를 주입(DI) 한다. 

어떤 인터페이스에 어떤 구현체를 생성해서 인스턴스 레퍼런스를 넘길지 정보를 보고 의존관계를 주입한다. 

단순히 자바 코드를 호출하는 것 같아보이지만, 차이가 있다. 이 차이는 뒤에서 싱글톤 컨테이너에서 설명한다. 

[ 참고 ]

스프링 컨테이너는 빈을 생성하고 의존관계를 주입한다는 것이 핵심이다. 

스프링 빈을 생성하고 의존관계를 주입하는 단계를 나눠서 그림으로 그렸다. 사실 스프링에서는 이것이 한 번에 처리되는 것이고 이해를 위해 나눠 그린 것이다. 이제 스프링 컨테이너에서 데이터를 조회해보자. 


2. 컨테이너에 등록된 모든 빈 조회

테스트 코드를 작성해서 스프링 컨테이너에 스프링 빈이 등록되었는지 확인하자

테스트를 실행한 결과. 

파랗게 드래그한 부분은 appConfig.class를 포함해서 @Bean 을 달아놓은 스프링빈이 출력되었다. 

드래그한 윗 부분은 스프링이 내부적으로 스프링 자체를 확장하기 위해 필요한 스프링빈이다. 

스프링 내부적으로 필요한 것 말고, 내가 정의한 스프링 빈만 출력하자.  
getRole()== ROLE_APPLICATION : 개발을 위해 등록한 (일반적으로 사용자가 등록한) 빈만 출력된다. 

파랗게 드래그한 부분을 보면 appConfig.class를 포함해서 내가 정의한 빈 4개가 출력된다. 


3. 스프링 빈 조회 - 기본

1) getBean() 메서드로 빈 이름을 넘기면 스프링 빈을 조회할 수 있다. 

테스트 실행 결과, appConfig를 비롯한 스프링빈이 출력된다. 

2) 이름 없이 타입으로만 조회할 수 있다. 

memberService 빈 이름을 호출하지 않고, memberService.class 타입으로 빈을 조회할 수 있다. 

3) 구체 타입 으로 조회할 수 있다. 

memberService 빈을 호출하면 구체클래스 memberServiceImpl을 반환해준다. 

AppConfig.class 코드를 열어 보면 memberService 스프링 빈의 반환 타입을 확인할 수 있다. 

따라서 스프링 빈을 memberServiceImpl 구체 타입으로 조회 가능하다. 

좋은 코드는 아니다.

왜냐하면 프로그래머는 "추상화에 의존해야지, 구현체에 의존하면 안된다." 는 SOLID원칙을 다시 떠올리자! 

 

4) 존재하지 않는 빈을 조회해보자. 

테스트 작성 시, 항상 실패 케이스도 만들어야 한다. XXX 라는 이름의 빈을 조회하면, 없는 빈이니까. 아래의 예외가 터져야 한다. 

NoSuchBeanDefinitionException: No bean named 'XXX' available

XXX라는 빈 없다는 에러 내용을 확인할 수 있다.

빨간 글씨를 보긴 했지만 예쁘게 고쳐보자. 

5) 실패 케이스 "@@예외가 터지면 성공이다."라는 테스트를 작성하자. 

다음 시간에는 "동일한 타입의 빈이 2개 이상 있으면 어떻게 조회 하는지 " 알아보자. 


다음 강의에서는 '스프링 빈 조회와 BeanFactory'를 배운다. 

공부 내용 출처 :  스프링 핵심 원리 기본편 

728x90

목표

1.  예제 프로젝트에 객체 지향 원리를 적용한다.
2. IoC, DI, 그리고 컨테이너를 배우고 순수 자바코드만 사용했던 프로젝트를 이제 스프링으로 전환한다. 

'객체 지향 원리 적용' 목차

1. 새로운 할인 정책 개발

2. 새로운 할인 정책 적용과 문제점

3. 관심사의 분리

4. AppConfig 리팩터링 

5. 새로운 구조와 할인 정책 적용

6. 전체 흐름 정리 

7. 좋은 객체 지향 설계의 5가지 원칙의 적용

8. IoC, DI, 그리고 컨테이너 (이번 포스팅)

9. 스프링으로 전환하기

 


8. IoC, DI, 그리고 컨테이너 

제어의 역전 IoC (Inversion of Control)

 

1) 기존 프로그램 : 구현 객체가 스스로 제어 흐름을 조종

기존 프로그램은 구현 객체가 스스로 필요한 객체를 생성하고, 연결하고, 실행했다. 

 

2) AppConfig가 외부에서 프로그램 동작 방식을 제어

반면에 AppConfig를 도입한 후에는 구현 객체는 자신의 로직을 실행하는 역할만 담당했다. 

프로그램을 제어하는건 전부 AppConfig가 맡는다. 어떤 인터페이스에 어떤 구현체를 선택할지 정해준다. 

예를 들어, OrderServiceImpl은 필요한 인터페이스를 호출할 때 어떤 구현 객체가 실행될지는 모른다. 몰라도 자신의 로직 실행에 아무런 영향을 받지 않는다. 

 

이렇게 프로그램의 제어 흐름을 구현체들이 직접 제어하는게 아니라 외부에서 관리하는 것을 제어의 역전이라고 한다. 

 

프레임워크 vs 라이브러리

프레임워크가 내가 작성한 코드를 제어하고, 대신 실행하면 그것은 프레임워크가 맞다. (JUnit) 

JUnit 테스트 프레임워크는 자신만의 라이프사이클이 있다. 순서대로 테스트를 진행하되, 내가 작성한 테스트를 그 안에 집어넣어서 실행시켜주는 것이다. 

반면 내가 작성한 코드가 직접 제어의 흐름을 담당한다면, 그것은 라이브러리다.

 

의존관계 주입 DI(Dependency Injection)

구현체는 인터페이스에 의존한다. OrderServiceImpl은 DiscountPolicy 인터페이스에만 의존한다. 

OrderServiceImpl 구현체는 기능을 호출하면서도 인터페이스를 통해 실제 어떤 구현체가 사용될지는 모른다. 

의존관계는 정적인 클래스 의존관계와 실행 시점에 결정되는 동적인 객체(인스턴스)의존관계 2가지를 분리해서 생각해야 한다. 

 

정적인 클래스 의존관계

애플리케이션을 실행하지 않아도, 클래스가 사용하는 import 코드만 보고 의존관계를 분석할 수 있다. 

FixDiscountPolicy 와 RateDiscountPolicy 클래스는 DiscountPolicy 인터페이스에 의존한다. 

OrderServiceImpl 구현체는 MemberRepository 인터페이스와 DiscountPolicy 인터페이스에 의존한다. 

화살표 방향으로 -> 의존관계를 표현하고 있다.

동적인 객체(인스턴스)의존관계

애플리케이션 실행 시점(런타임)에 실제 생성된 객체 인스턴스의 참조가 연결된 의존관계다. 

외부에서 실제 구현체를 생성하고 연결해주는 것을 의존관계 주입이라 한다. (AppConfig가 해준다)

 

의존관계 주입의 효과 

클라이언트 코드를 변경하지 않고, 클라이언트가 호출하는 인스턴스를 변경할 수 있다. 외부에서 구현체를 정해주기 때문이다. 

정적인 클래스 의존관계를 변경하지 않고, 동적인 객체 인스턴스를 쉽게 변경할 수 있다. (중요하니까 반복한다)

IoC컨테이너, DI 컨테이너 

AppConfig 처럼 객체를 생성하고 관리하면서 의존관계를 연결해주는 것을 IoC컨테이너 또는 DI컨테이너라 한다. 

 


9. 스프링으로 전환하기 

지금까지 순수 자바코드만으로 DI를 적용했다. 이제 스프링으로 전환해보자. 코드 부터 작성하자. 스프링으로 전환한 코드 private repo

 

1) 스프링 컨테이너에 등록하기

AppConfig에 '구성 정보'를 의미하는 @Configuration 애노테이션을 붙인다.

DI 하는 메서드에 @Bean애노테이션을 붙인다. 

2) ApplicationContext에 Bean을 등록 

테스트를 위해 작성했던 MemberApp를 열어보자. 

기존에는 AppConfig 에서 직접 필요한 객체를 꺼냈었다. 이제 스프링을 쓰자. 

스프링은 ApplicationContext 로 시작한다. 이것이 객체(Bean)을 관리해주는 스프링 컨테이너다. 

AppConfig.class 를 파라미터로 넘겨서 ApplicationContext를 생성하자. 

AppConfig에 있는 설정 정보를 가지고 Bean을 스프링 컨테이너에 넣고 관리해준다. 

객체(Bean)의 이름은 각각의 메서드 이름으로 붙여진다. ex) memberService 빈, memberRepository 빈

 

memberService 객체가 필요할 때 AppConfig에서 꺼내지 않고, 스프링 ApplicationContext에서 꺼낸다. 

스프링 컨테이너에 빈으로 등록된 인스턴스가 로그에 뜬다! 

3) OrderApp도 스프링으로 바꿔보자. 

AppConfig.class 를 파라미터로 넘겨서 ApplicationContext를 생성하자. 

ApplicationContext 에서 memberService와 orderService를 꺼낸다. 

4) 스프링 컨테이너 (== DI 컨테이너) 

  • ApplicationContext를 스프링 컨테이너라 한다. 
  • 기존에는 개발자가 AppConfig 에 객체를 생성하고 직접 DI를 구현했다. 
  • 이제 스프링 컨테이너가  @Configuration 애노테이션이 붙은 AppConfig를 설정(구성)정보로 사용한다. 여기서 @Bean이 붙은 메서드를 모두 호출해서 반환된 객체를 스프링 컨테이너에 모두 등록한다. 이렇게 스프링 컨테이너에 등록된 메서드를 스프링 빈이라고 한다. 
  • 스프링 빈은  @Bean애노테이션이 붙은 메서드 이름을 스프링 빈의 이름으로 사용한다. 
  • 스프링 빈은 applicationContext.getBean() 메서드로 찾을 수 있다. 

 

코드가 더 복잡해진 것 같은데. 스프링 컨테이너를 사용하면 어떤 장점이 있을까 ? 다음 시간에 장점을 배우자.


다음 강의에서는 '스프링 컨테이너 생성 과정'을 배운다. 

공부 내용 출처 :  스프링 핵심 원리 기본편 

728x90

+ Recent posts