스프링 핵심 원리 (기본편) CH02~03
단축키 정리(인텔리J)
1 | command+shift + T // 테스트 케이스 만들기 |
Core 프로젝트
- start.io를 통해 java 11버전 gradle로 인텔리제이로 세팅완료
- 인텔리제이에서 preference에서 gradle에서 test환경과 실행환경 둘다 인텔리제이로 하는것이 훨씬 원활한 실행가능..(변경권장)
요구사항
- 회원 데이터, 할인 정책같은 부분은 인터페이스를 만들어서 연동해야함>> 추후 구현체 갈아끼우기가능해야함
설계방식(!!!)
- 도메인
(역할별로나누기==기획자) - 클래스 다이어그램
(인터페이스 및 클래스 의존관계 나눠보기)(정적) - 객체 다이어그램
(인터페이스 및 클래스별로 나눠보기==동적결정이므로, 실제로 쓰는 객체는 객체 다이어그램보기)(메모리간의 참조를 그려보기)(동적)
클래스대로 세팅완료
- 입문과정과 거의 비슷하여 생략
테스트시도
- java로만
1 | package hello.core; |
- jtest사용
1 | package hello.core.member; |
주문과 할인 도메인 설계
- 기능으로 일단 도메인 분리
- 역할과 구현 분리해보기(클래스, 객체 다이그램그리기)
구현
테스트
- java
1 | package hello.core; |
- j test
1 | package hello.core.order; |
새로운 할인 정책 구현(OCP, DIP규칙)
요구 조건
- 현재 OCP와 DIP를 지킬수가 없다. 지키지 못함
- 따라서 스프링이 나타나기까지 이유 알 수 있다
구현
- DiscountPolicy 인터페이스 구현한 파일 추가
1 | package hello.core.discount; |
테스트
- VIP일때, 아닐떄 구별하여 TEST둘다 구현
1 | package hello.core.discount; |
새로운 할인 정책 적용과 문제점
- 할인 정책을 변경하려면 클라이언트인 OrderServiceImpl 코드를 고쳐야 한다.
- 방금 추가한 할인 정책은 분명, 인터페이스를 따르고있지만, 의존하는 곳들에서는 인터페이스 = new 구현체 를 따르고있기에 new 구현체 부분을 결국 전부다 바꿔줘야만 하는 한계를 가진다.
문제점
- DIP(항상추상화에만 의존해라), OCP(코드변경X하면서 확장O) 규칙 위반
해결해야할 부분
1 | package hello.core.order; |
- DIP문제를 해결하려면, 구현체를 모두 제거하고 추상화에만 남긴다.. 근데 구현체가없는데? 실행은 불가(NullPointEx)
- 이문제를 해결하려면 누군가가 클라이언트인 OrderServiceImpl에 DiscountPolicy의 구현 객체를 대신 생성하고 주입해줘야함!!!!
관심사의 분리
- 애플리케이션을 하나의 공연이라 생각해보자
- 배우가 직접 상대방 배우를 직접선택하는것이 아닌 상대방 배역을 의존하고있어야한다!
- 그리고 모든 배역에 대해서 공연 기획자를 따로 만들어서 공연 기획자에게 책임지게함
AppConfig등장(!!!)
- 애플리케이션의 전체 동작 방식을 구성(config)하기 위해, 구현 객체를 생성하고, 연결하는 책임을 가지는
별도의 설정 클래스를 만들자. - AppConfig.java
1 | public class AppConfig { |
- 의존하는 쪽에서는 반드시 생성자로 설정해줘야한다.(의존하는쪽에서는 어떤 구현 객체가 들어올지 알수없다
- 어떤 객체 주입되는것은 오직 외부 AppConfig에서 결정된다
- 클래스다이어그램
- 회원 객체 인스턴스 다이어그램
- 마지막줄 중요!!! 클라이언트로써 의존관계있던것을 주입해줌
- test 코드시 앞으로는 AppConfig를 사용하여, 통일하고, 초기화
1 |
|
정리
- AppConfig를 통해서 관심사를 확실하게 분리했다.
- 배역, 배우를 생각해보자.
- AppConfig는 공연 기획자다.
- AppConfig는 구체 클래스를 선택한다. 배역에 맞는 담당 배우를 선택한다. 애플리케이션이 어떻게
동작해야 할지 전체 구성을 책임진다. - 이제 각 배우들은 담당 기능을 실행하는 책임만 지면 된다.
OrderServiceImpl 은 기능을 실행하는 책임만 지면 된다.
AppConfig 리팩터링
현재 AppConfig를 보면 중복이 있고, 역할에 따른 구현이 잘 안보인다.
한눈에 역할이 보이는게 중요
Before
- 중복제거, 역할에 따른 구현 보이도록 리팩터링하자
1 | public class AppConfig { |
After
- 역할과 구현이 정확히 구별되어있음
- 구성이보이며, 중복도제거, 구현체 교체시 리턴값만 교체 편리
1 | public class AppConfig { |
새로운 구조와 할인 정책 적용
사용영역과 구성영역이 구별됨.
실제로 할인 정책 적용시 AppConfig만 고치면 끝이다..
사용영역에 있는 코드는 변경없다!!!
구성 영역에서만 변경하면됨
정리
- 새로운 정률 할인 정책으로 적용시 DIP, OCP모두 위반
- 관심사 분리하여 구성 기획자를 만듬
- 클라이언트 객체는 자신의 역할을 실행하는 것만 집중, 권한줄어듬(책임명확)
- 구성 정보에서 역할 구현 명확하게 분리!!
- 새로운 구조 다시 적용할때는 구성 영역만 하나만 고치면 모든것이 적용완료됨(실제 사용 영역은 코드변경X)
좋은 객체 지향 설계의 5가지 원칙적용여부판단
SRP 단일 책임 원칙
- 한 클래스는 하나의 책임만 가져야 한다.
- 클라이언트에서 다양한 책임을 없애고, 관심사 분리>> AppConfig가 구현 객체 생성 연결담당시킴
- 클라이언트 객체는 실행 책임만 담당
DIP 의존관계 역전 원칙
- 프로그래머는 추상화에 의존해야지, 구체화에 의존하면 안된다
- 기존코드는 추상화와 구현체를 모두 갖고 의존하고있었다.
- AppConfig로 클라이언트 코드에 의존관계를 주입했다. 추상화로만 의존되니, DIP원칙 따르게됨
OCP
- 소프트웨어 요소는 확장에는 열려 있으나 변경에는 닫혀 있어야한다.
- 다형성 사용하고 클라이언트가 DIP가 지켜야한다
- 사용영역과 구성 영역으로 나눠서, 클라이언트코드는 어떤 코드도 변경안해도 주입해서 확장가능
- 소프트웨어 요소는 확장가능해도 사용 영역의 변경은 닫혀있다
IoC, DI, 컨테이터
제어의 역전 IoC(Inversion of Control)
- 기존 프로그램은 클라이언트 구현 객체가 스스로 필요한 서버 구현 객체를 생성하고, 연결하고, 실행했다.
한마디로 구현 객체가 프로그램의 제어 흐름을 스스로 조종했다. 개발자 입장에서는 자연스러운 흐름이다. - 반면에 AppConfig가 등장한 이후에 구현 객체는 자신의 로직을 실행하는 역할만 담당한다. 프로그램의
제어 흐름은 이제 AppConfig가 가져간다. 예를 들어서 OrderServiceImpl 은 필요한 인터페이스들을 호출하지만 어떤 구현 객체들이 실행될지 모른다.(제어권없음) - 프로그램에 대한 제어 흐름에 대한 권한은 모두 AppConfig가 가지고 있다. 심지어 OrderServiceImpl도 AppConfig가 생성한다. 그리고 AppConfig는 OrderServiceImpl 이 아닌 OrderService인터페이스의 다른 구현 객체를 생성하고 실행할 수 도 있다. 그런 사실도 모른체 OrderServiceImpl 은 묵묵히 자신의 로직을 실행할 뿐이다.
- 이렇듯 프로그램의 제어 흐름을 직접 제어하는 것이 아니라 외부에서 관리하는 것을 제어의 역전(IoC)이라한다.
프레임워크 vs 라이브러리
- 프레임워크가 내가 작성한 코드를 제어하고, 대신 실행하면 그것은 프레임워크가 맞다. (JUnit)(제어의 역전일어남)(프레임워크가 내가짠코드를 실행해줌)
- 반면에 내가 작성한 코드가 직접 제어의 흐름을 담당한다면 그것은 프레임워크가 아니라 라이브러리다.(제어의 역전X)
의존 관계 주입 DI(Dependency Injection)
- OrderServiceImpl 은 DiscountPolicy 인터페이스에 의존한다. 실제 어떤 구현 객체가 사용될지는 모른다.
- 의존관계는 정적인 클래스 의존 관계와, 실행 시점에 결정되는 동적인 객체(인스턴스) 의존 관계 둘을 분리해서 생각해야 한다.
정적인 클래스 의존관계
- 클래스가 사용하는 import 코드만 보고 의존관계를 쉽게 판단할 수 있다. 정적인 의존관계는 애플리케이션을 실행하지 않아도 분석할 수 있다.
- 클래스 다이어그램을 보자OrderServiceImpl 은 MemberRepository , DiscountPolicy 에 의존한다는 것을 알 수 있다.
- 그런데 이러한 클래스 의존관계만으로는 실제 어떤 객체가 OrderServiceImpl 에 주입 될지 알 수 없다.
동적인 객체 인스턴스 의존 관계(!!!)
- 애플리케이션 실행 시점에 실제 생성된 객체 인스턴스의 참조가 연결된 의존 관계다.
- AppConfig에 실제로실행시 생성된 객체 인스턴의 참조가연결 (동적임)
- 애플리케이션 실행 시점(런타임)에 외부에서 실제 구현 객체를 생성하고 클라이언트에 전달해서 클라이언트와 서버의 실제 의존관계가 연결 되는 것을 의존관계 주입이라고한다(실제로 참조값이 연결되는것)
- 의존관계 주입을 사용하면 정적인 클래스 의존관계를 변경하지않고, 동적인 객체 인스턴스 의존관계를 쉽게 변경할수있다
DI 컨테이너(=IoC 컨테이너)
- AppConfig 처럼 객체를 생성하고 관리하면서 의존관계를 연결해 주는 것을 IoC 컨테이너 또는 DI 컨테이너라 한다.
- 의존관계 주입에 초점을 맞추어 최근에는 주로 DI 컨테이너라 한다. 또는 어샘블러, 오브젝트 팩토리 등으로 불리기도 한다.
스프링으로 전환하기
- 지금까지 순수한 자바코드만으로 DI를 적용했다.
- AppConfig에 설정을 구성한다는 뜻으로 @Configuration
- 각 메서드앞에는 @Bean 사용하여, 스프링 빈으로 등록
스프링 컨테이너
- ApplicationContext 를 스프링 컨테이너라 한다.
- 기존에는 개발자가 AppConfig 를 사용해서 직접 객체를 생성하고 DI를 했지만, 이제부터는 스프링 컨테이너를 통해서 사용한다.
- 스프링 컨테이너는 @Configuration 이 붙은 AppConfig 를 설정(구성) 정보로 사용한다. 여기서 @Bean 이라 적힌 메서드를 모두 호출해서 반환된 객체를 스프링 컨테이너에 등록한다. 이렇게 스프링 컨테이너에 등록된 객체를 스프링 빈이라 한다.
- 스프링 빈은 @Bean 이 붙은 메서드의 명을 스프링 빈의 이름으로 사용한다. ( memberService , orderService )
- 이전에는 개발자가 필요한 객체를 AppConfig 를 사용해서 직접 조회했지만, 이제부터는 스프링 컨테이너를 통해서 필요한 스프링 빈(객체)를 찾아야 한다. 스프링 빈은 applicationContext.getBean() 메서드를 사용해서 찾을 수 있다.
- 기존에는 개발자가 직접 자바코드로 모든 것을 했다면 이제부터는 스프링 컨테이너에 객체를 스프링 빈으로 등록하고, 스프링 컨테이너에서 스프링 빈을 찾아서 사용하도록 변경되었다.
- 코드가 약간 더 복잡해진 것 같은데, 스프링 컨테이너를 사용하면 어떤 장점이 있을까?
테스트
1 | public class MemberApp { |