본문 바로가기
개발자 일지/Spring

[인프런 김영한 로드맵2]스프링 핵심 원리 정리(3)

by 네빌링 2022. 2. 4.
반응형

-인프런 김영한 강사님의 스프링 핵심 원리를 정리한다.
-모든 소스는 깃허브에서 관리한다.(https://github.com/coderahn/Spring-Lecture2)


3.스프링 핵심 원리 이해2 - 객체 지향 원리 적용

새로운 할인 정책을 개발해본다. 기존 고정 할인 정책을 변경할 때 OCP, DIP 원칙을 위반하게 되는 문제점을 의존관계주입 방식을 통해 해결해본다.

 

1)새로운 할인 정책 개발

 

새로운 할인 정책은 주문 금액에 10%를 할인해주는 정책이다. 할인 정책 인터페이스 DIscountPolicy를 구현한 RateDiscountPolicy를 만든다. 그리고 OrderServiceImpl은 새로운 할인 정책을 사용하도록 변경한다.

 

FixDiscountPolicy를 RateDiscountPolicy로 전환

 

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

 

위의 할인정책코드를 보면 역할과 구현을 충실하게 분리하였고 다형성을 활용하였다. 그러나 OCP, DIP를 준수하지 못했다. 확장이 발생할 때, 해당 객체를 사용하는 OrderServiceImpl 코드가 변경되기 때문에 OCP를 위반하였다. 그리고 인터페이스에 의존하면서 동시에 구현체에도 의존하고 있기 때문에 DIP도 위반한 코드다.

 

3)관심사의 분리

 

애플리케이션을 공연에 비유해보면 OCP와 DIP원칙을 지키는 설계에 가까워진다. 로미오와 줄리엣 공연에서 로미오 역할이 줄리엣 역할을 정하는 것이 아니다. 그 반대도 마찬가지이다. 로미오와 줄리엣은 공연 무대에서 연기만 하면 되는 것이고 캐스팅이라는 책임은 공연기획자가 한다.

 

이것을 애플리케이션으로 다시 이해해보면 OrderService는 DiscountPolicy를 어떤 구현체로 쓸지 정할 수 없다. MemberService도 어떤 MemberRepository를 구현체로 쓸지 정할 수 없다. 구현객체를 생성하고 연결하는 것은 공연기획자 역할이 필요하다. 그런 역할을 해줄 AppConfig를 다음과 같이 만든다.

 

공연기획자 역할을 해준다.

 

그리고 MemberServiceImpl의 구현체 생성 코드를 다음과 같이 변경한다.(OrderServiceImpl도 같은 방식으로 변경)

 

인터페이스에만 의존하도록 변경(DIP)

 

위 코드에서 memberRepository 구현체 생성코드는 제거하고 인터페이스에만 의존하게 변경하였다. 그러나 이 상태로 실행하면 NullPointerException이 발생할 것이다. 클라이언트 쪽에서 AppConfig를 생성하여 memberRepository 구현체를 주입해준다.

 

beforeEach는 각 테스트 실행 전에 실행되는 어노테이션이다.

 

AppConfig를 통해 관심사를 확실하게 분리하였고 MemberService와 OrderService는 실행에만 관심을 갖도록 변경하였다.

 

4)AppConfig 리펙토링

 

AppConfig의 중복을 제거하고 역할에 따른 구현을 한눈에 볼 수 있게 수정한다. 애플리케이션 전체 구성이 어떻게 되어있는지 빠르게 파악할 수 있다.

 

MemberRepository구현체를 수정하려면 한 군데만 수정하면 된다.

인텔리제이의 중복 제거 리팩토링 단축키는 윈도우 기준 ctrl + alt + m이다.

 

 

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

 

고정할인정책(FixDiscountPolicy)을 변동할인정책(RateDiscountPolicy)로 변동하려면 AppConfig만 변경하면 된다. 클라이언트인 OrderService는 변동되는 코드가 없다. OCP 원칙을 지킬 수 있게 되었다.

 

6)전체 흐름 정리

 

  • 새로운 할인 정책을 개발하는데에 문제가 없었다. 인터페이스로 역할을 먼저 만들고 구현체들을 만들었기 때문.
  • 기존 새로운 할인 정책 적용과 문제점이 있었다. 주문 서비스 클라이언트가 할인정책 인터페이스 뿐만 아니라 구현체(FixDiscountPolicy)에도 의존하고 있던 것 -> DIP 위반
  • 이런 문제점 해결을 위해 관심사의 분리를 하였다. AppConfig라는 공연기획자 역할의 클래스를 만들어 구현객체를 생성하고 연결하는 '책임'을 맡았다.
  • AppConfig를 리펙토링하였다. 중복된 MemoryMemberRepository 생성 코드를 메소드 추출하였고 역할이 잘 드러나도록 변경 하였다.
  • AppConfig를 통해 새로운 구조 및 할인 정책을 적용하였다. 할인 정책 코드 구현체를 리턴하는 discountPolicy()만 변경해주면 된다.

 

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

 

  • SRP(단일책임원칙) 적용 : 클라이언트 객체는 실행하는 책임, AppConfig는 구현객체 생성 및 연결 책임으로 나눔
  • DIP(의존관계역전원칙) 적용 : 추상화에만 의존할 수 있도록 변경. 기존 OrderServiceImpl이 구현체에도 의존했었으나 AppConfig를 통해 구현체를 생성하도록 변경하였음
  • OCP(개방폐쇄원칙) 적용 : AppConfig를 사용하여 고정할인정책을 변동할인정책으로 변경하여도 DiscountPolicy를 사용하는 클라이언트(OrderServiceImpl)에는 변경이 안 일어나도록 적용

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

 

스프링에서 자주 사용되는 용어와 개념을 정리한다.

 

(1)제어의 역전(IoC, Inversion of Control)

 

기존에는 구현 객체(OrderServiceImpl)이 서버 구현 객체(MemberRepository)를 생성 및 연결까지 처리했다. 반면 AppConfig 등장 이후에 구현 객체는 자신의 로직만 실행하고 프로그램 제어 흐름은 AppConfig가 가져간다.

 

이렇게 프로그램 제어 흐름을 직접 제어하는 것이 아닌 외부에서 제어하는 것을 제어의 역전이라 한다.

 

(2)의존관계주입(DI, Dependency Injection)

 

OrderServiceImpl은 DiscountPolicy 인터페이스에 의존한다. 실제 DiscountPolicy의 구현 객체는 어떤 것이 사용될 것인지 모른다. 의존관계는 정적인 클래스 의존관계, 실행 시점에 결정되는 동적인 객체(인스턴스) 의존관계로 나눌 수 있다.

 

정적인 클래스 의존관계는 클래스가 사용하는 import 코드만 보고도 의존관계를 판단할 수 있다. 애플리케이션 실행과 상관없이 분석이 가능하며 클래스 다이어그램으로 판단 가능하다.

 

OrderServiceImpl이 인터페이스에 의존함을 판단 가능

 

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

 

실제 구현체(메모리 회원 저장소, 정액 할인 정책)에 의존함을 알 수 있다.

 

객체 다이어그램에서 보듯이 실행 시점(런타임)에 외부에서 실제 구현객체를 클라이언트에게 전달하여 클라이언트 서버의 실제 의존관계가 연결 되는 것을 의존관계 주입이라고 한다.

 

위와 같이 의존관계 주입을 활용하면 클라이언트 코드 변경 없이 클라이언트가 호출하는 대상 인스턴스를 변경할 수 있다. 예를들어 외부(AppConfig)에서 DiscountPolicy를 FixedDiscountPolicy에서 RateDiscountPolicy로 수정만 해주면 클라이언트는 RateDiscountPolicy의 참조객체를 사용할 수 있다.(정적인 클래스 의존관계를 변경하지 않고 동적인 객체 인스턴스 의존관계를 쉽게 변경 가능)

 

(3)IoC 컨테이너, DI 컨테이너

 

AppConfig와 같이 객체 생성 및 관리를 해주는 것을 IoC컨테이너, DI컨테이너라고 한다. 의존관계 주입 초점을 맞춰 주로 DI컨테이너로 부르며 오브젝트 팩토리라고도 부른다.

 

9)스프링으로 전환하기

 

지금까지 만든 순수 자바 코드를 스프링으로 전환한다. 우선 객체 생성 책임을 담당했던 AppConfig에 @Configuration, @Bean 어노테이션을 사용하여 다음과 같이 변경해준다.

 

어노테이션으로 스프링 컨테이너 설정파일로 변경

 

그리고 프로그램 실행을 담당했던 MemberApp, OrderApp에서 스프링 컨테이너 적용 코드로 다음과 같이 변경해준다.(그림은 MemberApp.java)

 

MemberApp.java / 방식3으로 스프링 컨테이너 방식 적용

ApplicationContext를 구현한 AnnotationConfigApplicationContext 구현체를 사용하면 스프링 컨테이너에 AppConfig를 등록해 사용할 수 있다. 위와 같이 getBean()을 사용하면 내부에 등록된 빈 객체를 가져올 수 있다. 

 

기존에 AppConfig만 사용하여 필요한 객체를 조회했다면 이제 스프링 컨테이너를 사용하여 객체를 찾는다.

 

4.스프링 컨테이너와 스프링 빈

1)스프링 컨테이너 생성

 

앞서 설명했듯이 ApplicationContext을 스프링 컨테이너라고 한다. 여기서 AppConfig.class를 파라미터로 넘기면 스프링 컨테이너 구성 정보로 AppConfig가 등록되고 구성 정보를 통해 스프링 빈이 등록된다. 빈 이름은 @Bean이 붙은 메소드의 메소드명이다.(@Bean(name="...")으로 빈네임 지정 가능)

 

스프링 컨테이너에 빈이 등록되면 등록된 빈들 사이의 의존관계가 설정된다. 이를 의존관계 주입(DI)라고 한다. 그러나 자바 코드로 스프링 빈을 등록하면 생성자 호출시 의존관계 주입도 한번에 처리된다. 이해를 돕기위해 나누어 이해한다.

 

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

 

다음과 같이 getBeanDefinitionNames()와 getRole() 등을 사용하여 등록된 빈 조회가 가능하다.

 

애플리케이션 빈만 조회시 getRole()을 사용

 

3)스프링 빈 조회 - 기본

 

스프링 컨테이너에서 빈을 찾는 가장 기본적인 방법은 다음과 같다.

  • ac.getBean(빈이름, 타입)
  • ac.getBean(타입)

조회 대상 스프링 빈이 없으면 다음과 같은 예외가 발생한다.

 

NoSuchBeanDefinitionException: No bean named '????' available 
//????는 찾는 빈 이름

 

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

 

등록된 빈에서 리턴할 타입이 같은 것이 2개 이상인 경우 오류가 발생한다. 이때는 빈 이름을 지정해야 한다. 아래의 그림은 동일한 타입이 2개 이상일 때 테스트 코드이다. static class로 내부에서만 사용할 스프링 컨테이너 구성 정보를 만든다. 구성 정보의 빈 2개(memberRepository1, memberRepository2)는 타입이 중복된다. 같은 타입이 중복되는 것이 있으면 아래 그림의 findBeanByName()과 같이 빈이름을 지정한다.

 

중복되는 타입이 있는 경우 테스트 코드

 

4)스프링 빈 조회 - 상속 관계

 

부모타입으로 조회시 자식타입도 함께 조회된다. Object로 조회하면 모든 스프링 빈을 조회한다.

 

static class 설정파일을 이너 클래스로 만든 후 사용.

 

"부모 타입으로 모두 조회하기 - Object" 테스트 결과는 다음과 같다.

 

DiscountPolicy 자식타입 Bean뿐만 아니라 자체 설정된 빈들도 모두 조회된다.

5)BeanFactory와 ApplicationContext

 

BeanFactory, ApplicationContext 모두 스프링빈 관리 및 조회 기능을 제공한다. ApplicationContext는 BeanFactory를 상속하며 추가적으로 메세지소스 다국어 기능, 환경변수, 애플리케이션 이벤트, 편리한 리소스 조회 기능도 제공한다. 

 

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

 

자바설정뿐만 아니라 XML을 통한 설정을 지원하여 스프링 빈 설정이 가능하다.

 

7)스프링 빈 설정 메타 정보 - BeanDefinition

 

스프링이 설정형식으로 java, xml 등 다양한 형식을 지원할 수 있는 이유는 BeanDefinition 추상화가 있기 때문이다. XML, 자바코드를 읽어 BeanDefinition을 만들면 된다. 스프링 컨테이너는 BeanDefinition만 알면 되는 것이다.

 

BeanDefinition을 빈 설정 메타정보라고하며 @Bean, <bean>당 각각 하나씩 메타정보가 생성된다.

 

 

반응형