김영한님의 실전! 스프링부트와 JPA활용1  을 학습하고 복습을 위해 포스팅한다. 

목표

* 실무에 가까운 예제 스프링 프로젝트를 만들면서 jpa에 대한 감을 잡는다. 

* 도메인 주도 설계를 이해한다. 

목차

  1. 프로젝트 환경설정
  2. 도메인 분석 설계
  3. 애플리케이션 구현준비: 요구사항 및 아키텍처
  4. 회원 도메인 개발
  5. 상품 도메인 개발
  6. 주문 도메인 개발
  7. 웹 계층 개발

 

내용출처 : 김영한님의 실전! 스프링부트와 JPA활용1  

 

728x90

'프로그래밍 > JPA' 카테고리의 다른 글

H2 데이터베이스 설치하고 JPA 동작확인  (0) 2022.01.10
view 환경 설정  (0) 2022.01.10
프로젝트 생성  (0) 2022.01.10
JPA 다대일 단방향 양방향  (0) 2021.07.03
연관관계 매핑시 고려할 3가지  (0) 2021.07.03

목표

1. 애노테이션을 직접 만들어본다.
[참고 : 애노테이션을 모아서 사용하는 기능은 자바가 아니라 스프링이 제공하는 기능이다 ]

'의존관계 자동 주입' 목차

1. 다양한 의존관계 주입 방법 

2. 옵션 처리 

3. 생성자 주입을 선택해라!  

4. 롬복과 최신 트랜드

5. 조회 빈이 2개 이상 - 문제

6. @Autowired 필드 명, @Qualifier, @Primary

7. 애노테이션 직접 만들기 (이번 포스팅)

8. 조회한 빈이 모두 필요할 때, List, Map

9. 자동, 수동의 올바른 실무 운영 기준


 

7.  애노테이션 직접 만들기

지난 시간에 @Qualifier에 @Qualifier("mainDiscountPolicy") 이름을 지정해서 사용했다. 

그런데 컴파일러는 @Qualifier("mainnnDiscountPolicy")  라는 실수가 있는지 @Qualifier("nainDiscountPolicy")  인지 모른다.

이런 문자 실수는 컴파일 타임에 잡을 수 없다. 

 

@Qualifier 용도로 쓸 @MainDiscountPolicy 라는 애노테이션을 만들어서 해결하자. 

 

1) 애노테이션 파일 MainDiscountPolicy 를 생성한다. 

2) @Qualifier 에 있던 애노테이션들을 다 복사해서 애노테이션 파일 MainDiscountPolicy 에 붙여넣는다. 

Qualifier 용도의 애노테이션이니까 @Qualifier("mainDiscountPolicy") 도 넣어준다. 

3) rate할인정책에 main할인정책이라는 @Qualifier 애노테이션을 붙이고 싶다. 

4) 혹시 @MainnnDiscountPolicy 로 애노테이션 이름에 에러가 나면? 

바로 컴파일 에러가 난다. 

 

5) RateDiscountPolicy 를 의존관계로 주입하려는 클래스 OrderServiceImpl 코드를 열어보자. 

DiscountPolicy 에 @Qualifier 를 지정하되, 방금 만든 @MainDiscountPolicy를 쓰자. 

6) 로그를 보면 정상적으로 스프링 빈이 등록되고 의존관계도 주입됬음을 확인할 수 있다. 

Creating shared instance of singleton bean 'orderServiceImpl'
Autowiring by type from bean name 'orderServiceImpl' via constructor to bean named 'memoryMemberRepository'
Autowiring by type from bean name 'orderServiceImpl' via constructor to bean named 'rateDiscountPolicy'

OrderServiceImpl 싱글톤 빈이 생성됬고,

생성자를 통해 memoryMemberRepository와 rateDiscountPolicy 빈이 의존관계 주입이 됬다.


다음 강의에서는 조회한 빈이 모두 필요할 때 List, Map 으로 처리하는 방법을 배운다. 

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

728x90

목표

1. 조회할 빈이 2개 이상일 때의 문제를 어떻게 처리해야 할지 배운다. 
2. @Autowired 필드 명을 매칭, @Qualifier, @Primary 사용법을 배운다. 

'의존관계 자동 주입' 목차

1. 다양한 의존관계 주입 방법 

2. 옵션 처리 

3. 생성자 주입을 선택해라!  

4. 롬복과 최신 트랜드

5. 조회 빈이 2개 이상 - 문제  (이번 포스팅)

6. @Autowired 필드 명, @Qualifier, @Primary

7. 애노테이션 직접 만들기

8. 조회한 빈이 모두 필요할 때, List, Map

9. 자동, 수동의 올바른 실무 운영 기준


5. 조회할 빈이 2개 이상일 때의 문제 해결하기

@Autowired타입으로 빈을 조회한다.

 

1) 만약 타입이 같은 빈이 2개가 조회되면 문제가 생길까? 

인터페이스 DiscountPolicy구현체 2개가 있다. RateDiscountPolicy와 FixDiscountPolicy 는 타입이 같다. 

RateDiscountPolicy 만 컴포넌트 스캔으로 스프링 빈 등록했었는데. FixDiscountPolicy도 스프링 빈 등록하려고 한다. 

스프링 빈 등록을 위해 컴포넌트 스캔을 하자.

2) orderServiceImple 에서 의존성 예외 발생했는데, DiscountPolicy 싱글 빈이 매칭되기를 기대했는데, 2개가 발견됬기 때문이다. 

beans.factory.UnsatisfiedDependencyException: 
Error creating bean with name 'orderServiceImpl' Unsatisfied dependency 
nested exception is NoUniqueBeanDefinitionException: 
No qualifying bean of type 'DiscountPolicy' available: 
expected single matching bean but found 2: fixDiscountPolicy,rateDiscountPolicy

@Autowired 로 의존관계를 주입하려고 DiscountPolicy 타입의 스프링 빈을 뒤져봤더니

1개 매칭을 기대했는데, 2개의 스프링 빈이 RateDiscountPolicy와 FixDiscountPolicy 발견되서 에러가 났다. 

 

3) 하위 타입인 fixDiscountPolicy 나 rateDiscountPolicy 를 적어야 할까? 아니다. 하위타입에 의존하면 DIP를 위반한다. 

다음 시간에 해결책을 배워보자. 


6. @Autowired 필드 명, @Qualifier, @Primary

타입이 같은 빈이 여러개 조회되면, 3가지 방법으로 해결할 수 있다.

 

1) @Autowired 필드 명을 매칭한다

@Autowired 의 스프링 빈 조회 순서->   1. 타입 매칭2. 필드 명 또는 파라미터 명을 매칭 

@Autowired 는 가장 먼저 스프링 빈의 타입을 매칭해보고. 빈이 2개 이상 조회되면 필드 명을 확인해서 매칭되는 하나를 조회한다. 

 

이전 코드 

@Autowired 필드 명을 매칭시키도록 변경한 코드 

 

2) @Qualifier 추가 구분자를 붙여주기

추가적인 방법을 제공하는 것이지 빈 이름을 변경하는건 아니다. 

 

예를 들어, RateDiscountPolicy 에 @Qualifier("mainDiscountPolicy") 를 지정하고, 

FixDiscountPolicy 에는 @Qualifier("fixDiscountPolicy") 를 지정한다. 

생성자 주입을 할 때 @Qualifier를 써주면 된다.

만약 해당 @Qualifier를 못찾으면 스프링은 그 이름으로 등록된 빈을 찾아본다. 

@Qualifier의 스프링 빈 조회 순서->   1.  @Qualifier 끼리 매칭,  2. 빈 이름 매칭 

@Qualifier 는 @Qualifier 를 찾는 용도로만 사용하는게 명확하고 좋다. 헷갈리게 하는 요소를 만들지 말자. 

 

3) @Primary 우선순위를 지정하기 [ 편하고 자주 사용한다 ] 

rate 할인정책을 항상 먼저 적용하고 싶다면, RateDiscountPolicy에 @Primary 를 붙인다. 

생성자에는 DiscountPolicy 타입 그대로 둔다. 

[ 참고 ] @Primary 는 언제 사용할까? 

예를 들어, 메인 DB connection 을 주로 사용하고, 백업용 DB connection 이 있는 상황에서,

메인 DB connection 에 @Primary 를 걸어두자고 팀내에서 룰을 지정할 수 있다. 


다음 강의에서는 애노테이션을 직접 만드는 방법 배운다. 

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

728x90

목표

1. 자바 설정 정보 없이 스프링 빈을 등록할 수 있는 '컴포넌트 스캔'을 배운다. 
2. 의존관계를 자동으로 주입하는 @Autowired 기능을 배운다. 

'컴포넌트 스캔' 목차

1. 컴포넌트 스캔과 의존관계 자동 주입 시작하기 (이번 포스팅)

2. 탐색 위치와 기본 스캔 대상 

3. 필터 

4. 중복 등록과 충돌 


1. 컴포넌트 스캔과 의존관계 자동 주입 시작하기

지금까지 애노테이션 기반 자바 설정 정보로 스프링 빈을 생성했다. 

실무에서는 스프링 빈이 수십 수백개가 필요한데, 관리할 것이 많으면 실수가 나오게 된다. 좀 더 편리한 방법이 있을까? 

스프링은 설정 정보 파일이 없어도 자동으로 스프링 빈을 등록하는 컴포넌트 스캔 기능을 제공한다. 

 

컴포넌트 스캔으로 자바 설정 정보를 대체해보자. 

AppConfig.class 는 그대로 두고, 새롭게 AutoAppConfig.class 를 만들자. 이제 컴포넌트 스캔으로 스프링 빈을 등록해보자. 

 

1) 자바 설정 정보 AutoAppConfig.class 만들고, @Configuration 과 @ComponentScan 를 붙인다. 

기존의 AppConfig.class와는 다르게 @Bean으로 등록한 클래스가 하나도 없다. 

@ComponentScan 애노테이션은 @Component가 붙은 클래스를 찾아서 전부 스프링 빈으로 등록한다. 

 

2) [ 참고 ]

excludeFilters = @ComponentScan.Filter() 는 기존 예제 코드들을 유지하기 위한 작업이다.

AppConfig.class와 여러 테스트 코드 에 @Configuration이 붙어있다. 이것들을 컴포넌트 스캔에서 제외하기 위한 코드를 입력하자. 

@Configuration 를 열어보면 @Component 애노테이션이 붙어있다. 그래서 @Configuration 도 컴포넌트 스캔의 대상이다. 

 

3) 각 클래스가 컴포넌트 스캔의 대상이 되도록 @Component를 붙여준다. 

MemoryMemberRepository, RateDiscountPolicy, MemberServiceImpl 에 붙인다. 

4) '컴포넌트 스캔'에서 의존관계 주입은 어떻게 할까?

이전 자바 설정 파일에서는  MemberServiceImpl 구현체에 MemberRepository 의존관계를 생성자를 통해 주입했다.

컴포넌트 스캔에서는 생성자에 @Autowired 를 붙여서 자동으로 의존관계를 주입한다. 

이전에 설정 파일 AppConfig.class 에서는 @Bean 으로 직접 설정 정보를 작성했고 의존관계도 직접 명시했다. 

이제는 설정 정보 파일 자체가 없기 때문에 의존관계 주입도 클래스 안에서 애노테이션으로 해결해야 한다. 

 

OrderServiceImple 클래스에도 @Component를 붙여서 스프링 빈으로 등록하고 @Autowired 로 의존관계를 주입하자. 

5) 컴포넌스 스캔으로 스프링 빈을 등록하고 Autowired로 의존관계가 주입됬는지 테스트 코드를 작성해보자.

* 로그에서 컴포넌트 스캔의 내용을 확인할 수 있다. 

org.springframework.context.annotation.ClassPathBeanDefinitionScanner 
- Identified candidate component class: 
file [/Users../corebasic/discount/RateDiscountPolicy.class]

org.springframework.context.annotation.ClassPathBeanDefinitionScanner 
- Identified candidate component class: 
file [/Users../corebasic/member/MemberServiceImpl.class]

* 로그에서 의존관계 주입을 확인할 수 있다. memoryMemberRepository 이름의 빈이 생성자를 통해 자동주입됨. 

Creating shared instance of singleton bean 'orderServiceImpl'
Autowiring by type from bean name 'orderServiceImpl' via constructor to bean named 'memoryMemberRepository'
Autowiring by type from bean name 'orderServiceImpl' via constructor to bean named 'rateDiscountPolicy'

6) 컴포넌트 스캔과 자동 의존관계 주입이 어떻게 동작하는지 그림으로 알아보자. 

 

@ComponentScan @Component가 붙은 클래스를 찾아서 전부 스프링 빈으로 등록한다. 

스프링 빈의 기본 이름은 클래스명을 쓰는데 앞글자만 소문자로 바꿔서 쓴다. 직접 빈 이름을 지정할 수 도 있다. 

MemberServiceImpl - > memberServiceImpl

생성자에 @Autowired 를 붙이면, 스프링 컨테이너가 자동으로 해당 빈을 찾아서 주입한다. 

기본 조회 전략은 타입이 같은 빈을 찾는 것이다. 

아래 코드를 보면 MemberRepository 빈을 의존관계로 주입해야 하는 상황이다.

MemberRepository와 타입이 같은 것을 찾는다. 

memoryMemberRepository 가 MemberRepository의 구현체니까 타입이 맞다.

그래서 memoryMemberRepository를 꺼내서 주입한다. 

getBean(MemberRepository.class)와 동일하다고 이해하면 된다. 


2. 탐색 위치와 기본 스캔 대상

컴포넌트 스캔을 시작하는 위치와 대상에 대해 알아보자. 

 

1) 탐색할 패키지의 시작 위치 지정 : 탐색 위치를 지정 할 수 있다!

AutoAppConfig.class 파일을 열어보자. 어떤 패키지부터 탐색할 지 basePackages 를 지정할 수 있다. 

모든 자바 클래스를 다 컴포넌트 스캔하면 시간이 오래 걸리기 때문이다. 그래서 꼭 필요한 위치부터 탐색하도록 시작 위치를 지정할 수 있다. 

현재 프로젝트 네비게이션은 아래와 같이 여러개의 패키지로 구성되어 있다. 

member 패키지를 basePackages 로 지정하고 컴포넌트 스캔하면 어떻게 될까? 

basePackages = member 패키지를 포함해서 하위 패키지를 모두 탐색한다.

그래서 memberServiceImpl, memoryMemberRepository 만 스프링 빈으로 등록됬다.

orderServiceImpl같은 다른 패키지의 빈은 제외됬다. 

2) 탐색 위치를 지정하지 않으면 어떻게 될까? 

@ComponentScan 이 붙은 설정 정보 클래스의 위치 부터 하위 패키지를 모두 탐색한다. 

hello.corebasic 패키지 하위의 모든 자바 파일에 @Component가 붙었는지 검사한다. 

3) 권장하는 방법 : @ComponentScan 이 붙은 설정 정보 클래스의 위치를 최상단에 두자 ! 

최근 스프링부트도 이 방법을 기본으로 제공한다. 

 

예를 들어, 패키지가 아래와 같은 구조라면, hello.corebasic 여기가 프로젝트 시작 루트가 된다. (지금 AutoAppConfig.class위치)

프로젝트 시작 루트에 @ComponentScan 를 붙인 메인 설정 정보 클래스를 두고 basePackage 지정은 생략한다.

이렇게 하면 hello.corebasic  를 포함한 하위는 모두 컴포넌트 스캔의 대상이 된다. 

 

[ 참고 ] 스프링 부트를 사용하는 경우

스프링 부트의 대표 시작 정보인 @SpringBootApplication 를 이 프로젝트 시작 루트 위치에 두는 것이 관례이다. 

 

스프링 부트 프로젝트를 만들면 main() 메서드가 있는 프로젝트명으로 된 클래스가 자동 생성되어 있다. 

이 클래스에 이미 @SpringBootApplication 가 붙어있다. 

 @SpringBootApplication  애노테이션이 뭔지 싶어서 ctrl 을 누르고 들어가보면,  @ComponentScan 이 붙어있음을 확인할 수 있다.

그래서 스프링 부트를 실행하면 hello.corebasic 위치 부터 하위까지 다 스프링 빈이 등록된다.

스프링 부트를 쓰면 @ComponentScan 을 따로 달아줄 필요가 없다. 

 


4) 컴포넌트 스캔의 기본 대상

@ComponentScan뿐만 아니라 아래 내용도 컴포넌트 스캔 대상에 포함된다. 

  *  @Component 

  *  @Controller 

  *   @Service

  *   @Repository

  *   @Configuration

스프링 MVC를 다뤄봤다면 몇개는 익숙한 애노테이션일 것이다. 해당 클래스의 소스코드를 열어보면 @Component를 포함하고 있음을 확인할 수 있다. 

 

[ 참고 ] 애노테이션에는 상속관계 라는 것이 없다. 

애노테이션이 특정 애노테이션을 인식할 수 있는 것인 자바 언어가 지원하는게 아니라, 스프링이 지원하는 기능임을 인지하고 넘어가자. 

 

컴포넌트 스캔의 대상이 되면서도 아래의 애노테이션이 있으면 스프링은 부가 기능을 수행한다. 

예를 들어, @Controller 라면, MVC 컨트롤러로 인식한다. @Repository라면 데이터 계층의 예외를 스프링 예외로 변환해준다. 

 


다음 강의에서는 '컴포넌트 스캔의 필터와 빈 이름 중복'에 대해  배운다. 

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

728x90

목표

1. 스프링 빈 조회 방법을 배운다.
2. BeanFactory와 ApplicationContext 의 차이를 배운다.

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

1. 스프링 컨테이너 생성  

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

3. 스프링 빈 조회 - 기본

4. 스프링 빈 조회 - 동일한  타입이 둘 이상 (이번 포스팅)

5. [ 중요 ] 스프링 빈 조회 - 상속 관계

6. BeanFactory와 ApplicationContext 

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

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


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

1) 타입으로 조회 시, 같은 타입이 2개 이상 있으면 중복 오류가 발생한다. 

임시로 MemberRepository 타입 빈이 2개 있는 SameBeanConfig.class 설정 파일을 만들었다. 

여기서 MemberRepository.class 타입 빈을 가져오면? 

아래와 같은 NoUniqueBeanDefinitionException 이 발생한다. 설명이 굉장히 친절하다.

싱글 매칭 빈을 기대했는데, 2개 였다는 내용. 

org.springframework.beans.factory.NoUniqueBeanDefinitionException
: No qualifying bean of type 'hello.corebasic.member.MemberRepository' available
: expected single matching bean but found 2: memberRepository1,memberRepository2

 

이렇게 특정 에러가 발생하는지 확인하려면 assertThrow와 Exception 클래스명을 써서 확인한다.

2) 타입으로 조회 시, 같은 타입이 2개 이상 있으면 빈 이름을 지정하면 된다!

빈 이름은 중복 불가다. 빈 이름을 안겹치게 지어서 조회하면 된다.

3) 특정 타입을 모두 조회하기

getBeansOfType() 메서드로 Map<이름, 타입> 특정 타입의 모든 빈을 모두 조회할 수 있다. 

나중에 @Autowired같이 자동으로 주입되는 기능을 배울 때 이런식으로 적용이 된다. 

ac.getBeansOfType(MemberRepository.class);


[ 중요 ] 5. 스프링 빈 조회 - 상속 관계 

1) 부모 타입으로 빈을 조회하면, 자식 타입도 함께 조회한다.

스프링 빈 대 원칙 하나는 "스프링 빈 하나 조회하면? 그 빈의 자식을 싹다 조회한다. " 

그래서 모든 자바 객체의 최고 부모인 Object 타입. Object 타입으로 조회하면, 모든 스프링 빈을 조회한다. 

테스트 코드로 확인하자. TestConfig 설정파일을 하나 만들자. DiscountPolicy 이름으로 생성될 빈이 2개다!  반환되는 생성자만 다르다. 

2) 부모 타입을 조회하면, 자식까지 다 딸려서 조회되니까 자식이 둘 이상인 경우 오류가 발생한다. 

이럴 땐 이름을 지어서 구분하면 된다. 

3) 부모 타입을 조회 시, 자식이 둘 이상 있으면, 빈 이름을 지정하면 된다. 

rateDiscountPolicy 특정 빈 이름으로 조회하자. 

4) 특정 하위 타입으로 조회할 수 있다. 

구체적인 타입으로 딱 지정하면 조회할 수 있지만 안 좋은 방법이다. 

5) 부모 타입으로 모두 조회해보자. 

할인 정책 인터페이스를 구현한 구현체 클래스 2개가 조회 된다. 

rateDiscountPolicy, fixDiscountPolicy 자식 타입 스프링 빈 2개가 출력됨을 확인할 수 있다. 

[ 참고 ] 실무에서 테스트 코드를 작성할 때는 이렇게 출력물을 만들면 안된다

지금은 공부용이라서 system.out.print를 하지만, 실무에서 테스트 코드를 작성할 때는 이렇게 출력물을 만들면 안된다. 

테스트 통과/실패 여부만 판단하도록 작성하는게 좋다. 

 

6) 최고 부모 타입인 Object 로 조회해보자. 

모든 자바 객체의 부모는 Object 라서. 모든 스프링 빈이 조회된다. 

7) 부모 타입으로 조회하면, 자식 타입도 함께 조회한다

실무에서는 ApplicationContext를 통해 getBean() 빈을 조회할 일이 거의 없다. 스프링 컨테이너가 자동으로 의존관계를 주입해주기 때문이다.

굳이 이렇게 배운 이유는, 빈 조회는 스프링의 기본 기능이기도 하고 아주 가끔 순수한 자바 애플리케이션에서 스프링 컨테이너를 생성해서 사용할 일이 있기 때문이다. 
그리고 부모 타입으로 조회 시, 자식 타입 빈까지 조회됨을 알고 있어야 자동 의존관계 주입을 배울 때 잘 사용할 수 있다.


6. BeanFactory 와 ApplicationContext

BeanFactory 와 ApplicationContext에 대해서 알아보자. 아래 계층 구조를 보자.

  • 최상위에 있는 인터페이스 BeanFactory 가 있다.
  • 최상위에 있는 인터페이스 BeanFactory를 상속받은 인터페이스 ApplicationContext 가 있다. 
  • ApplicationContext 는 BeanFactory에 부가 기능을 더한 것이다. 

BeanFactory

  • BeanFactory 는 스프링 컨테이너의 최상위 인터페이스다. 
  • 스프링 빈을 관리하고 조회하는 역할을 담당한다. 지금까지 빈 조회에 사용한 getBean() 을 BeanFactory이 제공한 것이다. 

ApplicationContext 

  • BeanFactory의 모든 기능을 상속 받아서 제공한다. 

BeanFactory와 ApplicationContext의 차이는 뭘까? 

  • 애플리케이션을 개발할 때는 빈을 관리하고 조회하는 기능은 물론이고 수 많은 부가기능이 필요하다. 
  • ApplicationContext는 아래와 같이 여러 인터페이스를 상속받아서 국제화, 이벤트, 리소스 조회 등의 기능을 제공한다. 
  • 우리는 BeanFactory를 직접 사용할 일은 거의 없고, 빈 관리와 부가기능이 포함된 ApplicationContext를 사용한다. 

메시지 소스를 활용한 국제화 기능 

 : 예를 들어 한국에서 접속하면 한국어로, 영어권에서 접속하면 영어로 출력해준다 

환경 변수 

 : 로컬, 개발, 운영 등을 구분해서 환경 변수를 처리 

애플리케이션 이벤트

 : 이벤트를 발행하고 구독하는 모델을 편리하게 지원 

편리한 리소스 조회 

 : 파일, 클래스패스, 외부 등에서 리소스를 편리하게 조회

 

BeanFactory 나 ApplicationContext를 스프링 컨테이너라 한다. 


다음 강의에서는 '스프링 컨테이너가 지원하는 자바코드, XML 형식의 설정 정보'를 배운다. 

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

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

목표

1.  예제 프로젝트에 객체 지향 원리를 적용한다.
2. 이전 시간에 만든 AppConfig를 리팩터링하고 공부한 내용을 정리한다.

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

1. 새로운 할인 정책 개발

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

3. 관심사의 분리

4. AppConfig 리팩터링 (이번 포스팅)

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

6. 전체 흐름 정리 

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

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

9. 스프링으로 전환하기


4. AppConfig 리팩터링

현재 AppConfig의 중복을 제거하자. 역할에 따른 구현을 분명하게 만들자. 

아래는 목표로 하는 도메인 협력관계다. AppConfig 에서 이 그림의 모든 요소를 드러나도록 바꾸자. 

리팩토링 전 AppConfig 

리팩토링 후 AppConfig 코드

메서드 명을 보면 역할 마다의 구현이 모두 드러나있다. 애플리케이션의 구성이 한 눈에 들어온다. 

AppConfig를 도입해서 애플리케이션이 사용 영역과 객체를 생성하고 구성하는 영역으로 분리됬다. 

 


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

기획자의 요구사항대로 정률% 할인 정책으로 변경하자. 

FixDiscountPolicy를 RateDiscountPolicy로 구현체가 변경되어야 하는데. 어디를 바꿔야 할까? 

 

클라이언트를 건드리지 않고 AppConfig만 수정하면 된다. 

DiscountPolicy 역할(인터페이스)의 구현체를 RateDiscountPolicy로 바꾸면 끝이다!

공연기획자 AppConfig 가 모든 역할과 구현을 선택한다. 

사용 영역의 변경이 필요없다!


6. 전체 흐름 정리 

지금까지 배운 것을 정리해보자. 

 

1. 새로운 할인 정책 개발

할인 정책 인터페이스와 할인 정책 구현체를 분리해뒀다.

역할과 구현이 분리되어 있으니 새로운 정률 할인 정책 코드를 추가 개발하는 것에 문제가 없다.

 

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

새로 개발한 정률 할인 정책을 적용하려고 하니까 클라이언트 코드를 변경해야 하는 문제를 발견했다. 

주문 서비스가 할인 정책 인터페이스 DiscountPolicy 뿐만 아니라 구현체 FixDiscountPolicy도 둘다 의존하고 있었기 때문이다. 

-> DIP 위반! 추상에만 의존해야 하는데. 구현체에도 의존하고 있는 문제.

 

3. 관심사의 분리

기존에는 구현체 다른 구현체를 직접 선택하는 다양한 책임을 가지고 있었다. 

구현체는 기능 구현에만 집중하게 두자.

역할 마다의 구현체를 지정하는 책임을 맡을 공연 기획자가 필요해졌다!

공연 기획자 AppConfig를 만들어서 전체 동작 방식을 구성하도록 했다. 

AppConfig는 구현 객체를 생성하고 연결하는 책임을 가진다. 

클라이언트는 기능 구현이라는 책임이 명확해졌다. 

 

4. AppConfig 리팩터링

중복을 제거했다. 구성 정보에서 역할과 구현을 명확하게 분리했다. 

 

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

새로 개발한 정률 할인 정책을 적용했다. 구성영역인 AppConfig만 수정하면 됬고 사용영역은 변경할 필요가 없었다. 


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

예제 프로젝트에서 SOLID원칙이 적용된 것을 확인해보자. 여기서 SRP, DIP, OCP가 적용되었다. 

 

SRP 단일 책임 원칙 

한 클래스는 하나의 책임만 가져야 한다. 

  • 클라이언트 객체는 객체 생성, 연결, 실행하는 다양한 책임을 가지고 있었다. 
  • 구현 객체를 생성하고 연결하는 책임을 AppConfig가 담당하도록 넘겼다. 클라이언트 객체는 기능 실행만 맡게 됬다. 

DIP 의존관계 역전 원칙  

"추상화에 의존해야지, 구체화에 의존하면 안된다."

  • 새로운 할인 정책을 개발하고 적용하려고 하니 클라이언트 코드를 변경해야 하는 문제를 발견했다. 
  • 왜냐하면 클라이언트 코드 OrderServiceImpl이 DiscountPolicy 인터페이스에만 의존하는 것 같았지만, FixDiscountPolicy 구현체에도 의존하고 있었기 때문이다. 
  • 클라이언트 코드 OrderServiceImpl이 DiscountPolicy 인터페이스에만 의존하도록 변경했다. 
  • AppConfig가 FixDiscountPolicy 인스턴스 생성하여 의존관계를 클라이언트 코드에 주입했다. 

OCP 개방 폐쇄 원칙

"소프트웨어 요소는 확장에는 열려 있으나 변경에는 닫혀 있어야 한다."

  • 구성영역인 AppConfig가 의존관계를 결정한다. 
  • 따라서 소프트웨어 요소를 새롭게 확장해도 사용영역(클라이언트 코드)을 변경하지 않아도 된다. 

다음 강의에서는 스프링의 IoC, DI 그리고 컨테이너를 배운다. 

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

728x90

+ Recent posts