해당 강의는 김영한 강사님의 유료 강의로, 아주 간략하게 배운 부분들을 짚고 넘어가는 식으로 작성하였습니다.
생략된 부분이 많습니다. 전체 소스코드 공개도 금지이므로 블로그에 부분적으로만 올릴 생각입니다.
강의를 보며 포스트잇을 붙이는 느낌으로 제가 보기 위해 작성하는 글이니
학습을 위해서라면 아래 링크의 강의를 직접 들으시는 것을 추천합니다!
스프링 핵심 원리 - 기본편 - 인프런 | 강의
스프링 입문자가 예제를 만들어가면서 스프링의 핵심 원리를 이해하고, 스프링 기본기를 확실히 다질 수 있습니다., - 강의 소개 | 인프런...
www.inflearn.com
MemberApp과 OrderApp 코드 수정 및 실행
이제 실행을 위해 MemberApp.java를 살펴보자
// MemberApp.java
MemberService memberService = new MemberServiceImpl();
이렇게 기존에는 MemberApp에서 memberService 객체를 직접 만들었다.
하지만 이젠 앞서 만든 AppConfig를 이용해서 개발할 것이다.
// MemberApp.java
AppConfig appConfig = new AppConfig();
MemberService memberService = appConfig.memberService();
이렇게 AppConfig의 memberService()를 호출하면
// AppConfig.java
public MemberService memberService() {
return new MemberServiceImpl(new MemoryMemberRepository());
}
MemberServiceImpl을 만들고 내가 만든 MemberServiceImpl은 MemberServiceImpl를 사용할 것이라고
딱 주입해주는 것이다.
OrderApp에서도 마찬가지로 코드를 작성하면 된다.
// OrderApp.java
AppConfig appConfig = new AppConfig();
MemberService memberService = appConfig.memberService();
OrderService orderService = appConfig.orderService();
// ConfigApp.java
public OrderService orderService() {
return new OrderServiceImpl(new MemoryMemberRepository(), new FixDiscountPolicy());
}
OrderServiceImpl이 MemoryMemberRepository 와 FixDiscountPolicy 객체를 참조하도록 그림을 완성시키고
완성된 OrderServiceImpl 객체를 반환하는 것이다.
이제는 AppConfig에서 객체 생성과 인터페이스에 어떤 객체가 할당되어야 할지를 다 정하게 되는 것이다.


성공적으로 실행되는 것을 확인할 수 있다.
FixDiscountPolicy가 적용되었으므로 itemPrice에 상관없이 discountPrice는 1000원으로 고정된 모습이다.
테스트 코드 역시 AppConfig를 사용하도록 수정해서 실행해보면 된다.

현재 이렇게 작성되어 있는데, 이를 수정해보자

@BeforeEach는 각 테스트를 실행하기 전에 호출된다.

테스트 결과도 성공적으로 나오는 것을 확인할 수 있다.
AppConfig 리팩터링
리팩터링(refactoring)은 소프트웨어 공학에서 결과의 변경 없이 코드의 구조를 재조정함을 뜻한다.
// AppConfig.java
public MemberService memberService() {
return new MemberServiceImpl(new MemoryMemberRepository());
}
public OrderService orderService() {
return new OrderServiceImpl(new MemoryMemberRepository(), new FixDiscountPolicy());
}
현재 AppConfig를 보면 역할과 구현이 분리가 한그림에 보여지지 않는다.
역할이 있고 그 역할에 따른 구현을 어떤 것이 한다 한눈에 보여야 한다는 것이다.
위 코드를 잘 보면 MemberRepository라는 역할이 전혀 보이지 않는다.
또한 new MemoryMemberRepository()를 보면 중복이 있는 것도 확인할 수 있다.
AppConfig를 수정해보자
우선, new MemoryMemberRepository()를 드래그해서 우클릭 - Refactor - Extract Method를 클릭한다.

// AppConfig.java
public MemberService memberService() {
return new MemberServiceImpl(memberRepository());
}
public MemberRepository memberRepository() {
return new MemoryMemberRepository();
}
public OrderService orderService() {
return new OrderServiceImpl(memberRepository(), new FixDiscountPolicy());
}
memberRepository 인터페이스가 드러났고, 중복이였던 new MemoryMemberRepository()가 정리되었다.
이제 DiscountPolicy 인터페이스도 드러나게 해보자
// AppConfig.java
public MemberService memberService() {
return new MemberServiceImpl(memberRepository());
}
public MemberRepository memberRepository() {
return new MemoryMemberRepository();
}
public OrderService orderService() {
return new OrderServiceImpl(memberRepository(), discountPolicy());
}
public DiscountPolicy discountPolicy() {
return new FixDiscountPolicy();
}
이렇게하면 모든 역할과 구현을 볼 수가 있다.
애플리케이션 전체 구성이 어떻게 되어있는지 빠르게 파악할 수 있게 된 것이다!
할인 정책 변경하기
지금까지 배웠던 개념을 토대로 현재의 정액 할인 정책을 정률 할인 정책으로 변경해보자
FixDicountPolicy를 RateDiscountPolicy로 바꾸면 되는 것이다.
원래라면 OrderServiceImpl에서 new FixDicountPolicy 부분을 new RateDiscountPolicy로 코드를 수정하였겠지만
(클라이언트 코드를 직접 수정)
이젠 그렇게 하지 않아도 된다. 즉, 사용 영역의 어떤 코드도 변경할 필요가 없다는 것이다.
구성 영역 (AppConfig)을 변경하면 된다.
// AppConfig.java
private DiscountPolicy discountPolicy() {
//return new FixDiscountPolicy();
return new RateDiscountPolicy();
}
이렇게 AppConfig에서 FixDicountPolicy를 RateDiscountPolicy로 변경해주면 된다.
테스트 결과 )

정률 할인 정책이므로 2000원이 나와야 한다.

1000원이 아니라 2000원이 나와야한다고 알려준다.
성공적으로 정액 할인 정책이 정률 할인 정책으로 변경된 것을 확인할 수 있다.
정리를 해보자.
이렇게하면 코드가 추상에만 의존하기 때문에 DIP를 잘 지키고 있고,
[ DIP : 구체(구현)에 의존하지 말고 항상 추상(인터페이스)에 의존하라 ]
OCP도 잘 지키고 있다.
[ OCP : 확장에는 열려있고, 변경에는 닫혀있다. ]
좋은 객체 지향 설계의 5가지 원칙 적용
여기서 3가지인 SRP, DIP, OCP를 적용하였다.
SRP 단일 책임 원칙 : 한 클래스는 하나의 책임만 가져야 한다.
> AppConfig를 통해 구현 객체를 생성하고 연결하는 책임을 담당하게 했고,
클라이언트 객체는 실행하는 책임만 담당하게 했다.
DIP 의존관계 역전 원칙 : 프로그러머는 추상화에 의존해야지, 구체화에 의존해서는 안된다.
(의존성 주입은 이 원칙을 따르는 방법 중 하나다.)
> AppConfig에서 객체 인스턴스를 클라이언트 코드 대신 생성해 클라이언트 코드 의존관계를 주입했다.
OCP : 소프트웨어 요소는 확장에는 열려있으나 변경에는 닫혀있어야 한다.
AppConfig (구성영역)가 의존관계를 예를 들어 FixDiscountPolicy -> RateDiscountPolicy로 변경해
클라이언트에 주입하므로 클라이언트 코드를 변경하지 않아도 된다.
소프트웨어 요소를 새롭게 확장해도 사용 영역의 변경은 닫혀있다.
IoC, DI, 컨테이너에 대해
제어의 역전 IoC
AppConfig가 등장한 이후 구현 객체는 자신의 로직을 실행하는 역할만 담당한다.
프로그램의 제어 흐름은 이제 AppConfig가 가져가는 것이다.
이렇게 프로그램의 제어 흐름을 직접 제어하는 것이 아니라 외부에서 관리하는 것을 제어의 역전(IoC)라고 한다.
프레임워크 vs 라이브러리
프레임워크가 내가 작성한 코드를 제어하고 대신 실행하면 그것은 프레임워크가 맞다. (JUnit)
내가 작성한 코드가 직접 제어의 흐름을 담당한다면 그것은 프레임워크가 아니라 라이브러리이다.
의존관계 주입 DI (Dependency Injection)
의존관계는 둘을 분리해서 생각해야 한다.
- 정적인 클래스 의존 관계
- 실행 시점에 결정되는 동적인 객체(인스턴스) 의존 관계
정적인 클래스 의존 관계
~~~
> 이러한 클래스 의존관계 만으로는 실제 어떤 객체가 OrderServiceImpl에 주입될지 알 수 없다.
실제로 실행 시켜봐야 한다.
동적인 객체 인스턴스 의존 관계 : 애플리케이션 실행 시점에 실제 생성된 객체 인스턴스의 참조가 연결된 의존 관계
> 애플리케이션 실행 시점에 외부에서 실제 구현 객체를 생성하고 클라이언트에 전달해
클라이언트와 서버의 실제 의존관계가 연결되는 것을 의존관계 주입이라고 한다.
IoC 컨테이너, DI 컨테이너
말 그대로 IoC를 해주는 컨테이너, DI를 해주는 컨테이너라고 보면 된다.
AppConfig처럼 객체를 생성하고 관리하면서 의존관계를 연결해주는 것을 IoC 컨테이너 또는 DI 컨테이너라고 한다.
최근에는 DI 컨테이너라고 부른다고 한다.
스프링으로 전환하기
지금까지 순수하게 자바 코드만으로 DI를 적용했었는데,
이제 스프링을 사용해보자!
기존에는 개발자가 AppConfig 사용해 직접 객체를 생성하고 DI를 했지만,
이제부터는 스프링 컨테이너를 통해 사용할 것이다.
작성했던 AppConfig를 스프링 기반으로 변경해보자

우선 AppConfig에다가 구성정보를 뜻하는 @Configuration 을 붙여준다.
스프링 컨테이너는 @Configuration 이 붙은 AppConfig를 구성(설정)정보로 사용한다.

그리고 각 메서드에 이와 같이 @Bean을 붙여준다. 스프링 컨테이너에 스프링 빈으로 등록하는 것이다.
여기에 이렇게 @Bean이라 적힌 메서드를 모두 호출해 반환된 객체를 스프링 컨테이너에 등록한다.
이렇게 스프링 컨테이너에 등록된 객체를 스프링 빈이라고 한다.
이제 MemberApp.java로 가보자

ApplicationContext 는 스프링 컨테이너라고 보면 된다.
AnnotationConfigApplicationContext는 어노테이션 기반으로 Config를 한다는 것이다.
여기의 파라미터로 AppConfig.class를 넣어준다.
이렇게하면 AppConfig에 있는 환경설정 정보를 가지고 스프링이 AppConfig안에 있는 @Bean이 붙은 것들을
스프링 컨테이너에 객체 생성한 것을 집어넣고 관리해준다.
기존에는 MemberService memberService = appConfig.memberService(); 와 같이
AppConfig에서 직접 찾아왔다.
이제부터는 스프링 컨테이너를 통해 필요한 스프링 빈(객체)를 찾아야 한다.

스프링 빈은 이와 같이 ApplicationContext.getBean() 메서드를 사용해 찾을 수 있다.
왼쪽에는 이름, 오른쪽에는 반환 타입을 적어준다.
기본적으로 스프링 빈은 @Bean이 붙은 메서드의 명을 스프링 빈의 이름으로 사용한다.
실행결과 )

잘 실행되는 것을 확인할 수 있다.
이렇게 스프링 컨테이너를 사용할 경우 어떤 장점이 있는지는
스프링 컨테이너의 핵심 기능들을 공부하다보면 확실히 알게 될 것이라고 한다.
다음 시간에는 스프링 컨테이너와 스프링 빈에 대해 정리하도록 하겠다.
'김영한님의 스프링 강의 학습 > 스프링 핵심 원리' 카테고리의 다른 글
#5 싱글톤 컨테이너, #6 컴포넌트 스캔 (0) | 2022.12.22 |
---|---|
#4 스프링 컨테이너와 스프링 빈 (0) | 2022.11.10 |
#3 스프링 핵심 원리 이해2 - 객체지향 원리 적용1 (1) | 2022.09.30 |
#2 스프링 핵심 원리 이해 - 예제 만들기2 (0) | 2022.09.29 |
#2 스프링 핵심 원리 이해 - 예제 만들기1 (0) | 2022.09.28 |
해당 강의는 김영한 강사님의 유료 강의로, 아주 간략하게 배운 부분들을 짚고 넘어가는 식으로 작성하였습니다.
생략된 부분이 많습니다. 전체 소스코드 공개도 금지이므로 블로그에 부분적으로만 올릴 생각입니다.
강의를 보며 포스트잇을 붙이는 느낌으로 제가 보기 위해 작성하는 글이니
학습을 위해서라면 아래 링크의 강의를 직접 들으시는 것을 추천합니다!
스프링 핵심 원리 - 기본편 - 인프런 | 강의
스프링 입문자가 예제를 만들어가면서 스프링의 핵심 원리를 이해하고, 스프링 기본기를 확실히 다질 수 있습니다., - 강의 소개 | 인프런...
www.inflearn.com
MemberApp과 OrderApp 코드 수정 및 실행
이제 실행을 위해 MemberApp.java를 살펴보자
// MemberApp.java
MemberService memberService = new MemberServiceImpl();
이렇게 기존에는 MemberApp에서 memberService 객체를 직접 만들었다.
하지만 이젠 앞서 만든 AppConfig를 이용해서 개발할 것이다.
// MemberApp.java
AppConfig appConfig = new AppConfig();
MemberService memberService = appConfig.memberService();
이렇게 AppConfig의 memberService()를 호출하면
// AppConfig.java
public MemberService memberService() {
return new MemberServiceImpl(new MemoryMemberRepository());
}
MemberServiceImpl을 만들고 내가 만든 MemberServiceImpl은 MemberServiceImpl를 사용할 것이라고
딱 주입해주는 것이다.
OrderApp에서도 마찬가지로 코드를 작성하면 된다.
// OrderApp.java
AppConfig appConfig = new AppConfig();
MemberService memberService = appConfig.memberService();
OrderService orderService = appConfig.orderService();
// ConfigApp.java
public OrderService orderService() {
return new OrderServiceImpl(new MemoryMemberRepository(), new FixDiscountPolicy());
}
OrderServiceImpl이 MemoryMemberRepository 와 FixDiscountPolicy 객체를 참조하도록 그림을 완성시키고
완성된 OrderServiceImpl 객체를 반환하는 것이다.
이제는 AppConfig에서 객체 생성과 인터페이스에 어떤 객체가 할당되어야 할지를 다 정하게 되는 것이다.


성공적으로 실행되는 것을 확인할 수 있다.
FixDiscountPolicy가 적용되었으므로 itemPrice에 상관없이 discountPrice는 1000원으로 고정된 모습이다.
테스트 코드 역시 AppConfig를 사용하도록 수정해서 실행해보면 된다.

현재 이렇게 작성되어 있는데, 이를 수정해보자

@BeforeEach는 각 테스트를 실행하기 전에 호출된다.

테스트 결과도 성공적으로 나오는 것을 확인할 수 있다.
AppConfig 리팩터링
리팩터링(refactoring)은 소프트웨어 공학에서 결과의 변경 없이 코드의 구조를 재조정함을 뜻한다.
// AppConfig.java
public MemberService memberService() {
return new MemberServiceImpl(new MemoryMemberRepository());
}
public OrderService orderService() {
return new OrderServiceImpl(new MemoryMemberRepository(), new FixDiscountPolicy());
}
현재 AppConfig를 보면 역할과 구현이 분리가 한그림에 보여지지 않는다.
역할이 있고 그 역할에 따른 구현을 어떤 것이 한다 한눈에 보여야 한다는 것이다.
위 코드를 잘 보면 MemberRepository라는 역할이 전혀 보이지 않는다.
또한 new MemoryMemberRepository()를 보면 중복이 있는 것도 확인할 수 있다.
AppConfig를 수정해보자
우선, new MemoryMemberRepository()를 드래그해서 우클릭 - Refactor - Extract Method를 클릭한다.

// AppConfig.java
public MemberService memberService() {
return new MemberServiceImpl(memberRepository());
}
public MemberRepository memberRepository() {
return new MemoryMemberRepository();
}
public OrderService orderService() {
return new OrderServiceImpl(memberRepository(), new FixDiscountPolicy());
}
memberRepository 인터페이스가 드러났고, 중복이였던 new MemoryMemberRepository()가 정리되었다.
이제 DiscountPolicy 인터페이스도 드러나게 해보자
// AppConfig.java
public MemberService memberService() {
return new MemberServiceImpl(memberRepository());
}
public MemberRepository memberRepository() {
return new MemoryMemberRepository();
}
public OrderService orderService() {
return new OrderServiceImpl(memberRepository(), discountPolicy());
}
public DiscountPolicy discountPolicy() {
return new FixDiscountPolicy();
}
이렇게하면 모든 역할과 구현을 볼 수가 있다.
애플리케이션 전체 구성이 어떻게 되어있는지 빠르게 파악할 수 있게 된 것이다!
할인 정책 변경하기
지금까지 배웠던 개념을 토대로 현재의 정액 할인 정책을 정률 할인 정책으로 변경해보자
FixDicountPolicy를 RateDiscountPolicy로 바꾸면 되는 것이다.
원래라면 OrderServiceImpl에서 new FixDicountPolicy 부분을 new RateDiscountPolicy로 코드를 수정하였겠지만
(클라이언트 코드를 직접 수정)
이젠 그렇게 하지 않아도 된다. 즉, 사용 영역의 어떤 코드도 변경할 필요가 없다는 것이다.
구성 영역 (AppConfig)을 변경하면 된다.
// AppConfig.java
private DiscountPolicy discountPolicy() {
//return new FixDiscountPolicy();
return new RateDiscountPolicy();
}
이렇게 AppConfig에서 FixDicountPolicy를 RateDiscountPolicy로 변경해주면 된다.
테스트 결과 )

정률 할인 정책이므로 2000원이 나와야 한다.

1000원이 아니라 2000원이 나와야한다고 알려준다.
성공적으로 정액 할인 정책이 정률 할인 정책으로 변경된 것을 확인할 수 있다.
정리를 해보자.
이렇게하면 코드가 추상에만 의존하기 때문에 DIP를 잘 지키고 있고,
[ DIP : 구체(구현)에 의존하지 말고 항상 추상(인터페이스)에 의존하라 ]
OCP도 잘 지키고 있다.
[ OCP : 확장에는 열려있고, 변경에는 닫혀있다. ]
좋은 객체 지향 설계의 5가지 원칙 적용
여기서 3가지인 SRP, DIP, OCP를 적용하였다.
SRP 단일 책임 원칙 : 한 클래스는 하나의 책임만 가져야 한다.
> AppConfig를 통해 구현 객체를 생성하고 연결하는 책임을 담당하게 했고,
클라이언트 객체는 실행하는 책임만 담당하게 했다.
DIP 의존관계 역전 원칙 : 프로그러머는 추상화에 의존해야지, 구체화에 의존해서는 안된다.
(의존성 주입은 이 원칙을 따르는 방법 중 하나다.)
> AppConfig에서 객체 인스턴스를 클라이언트 코드 대신 생성해 클라이언트 코드 의존관계를 주입했다.
OCP : 소프트웨어 요소는 확장에는 열려있으나 변경에는 닫혀있어야 한다.
AppConfig (구성영역)가 의존관계를 예를 들어 FixDiscountPolicy -> RateDiscountPolicy로 변경해
클라이언트에 주입하므로 클라이언트 코드를 변경하지 않아도 된다.
소프트웨어 요소를 새롭게 확장해도 사용 영역의 변경은 닫혀있다.
IoC, DI, 컨테이너에 대해
제어의 역전 IoC
AppConfig가 등장한 이후 구현 객체는 자신의 로직을 실행하는 역할만 담당한다.
프로그램의 제어 흐름은 이제 AppConfig가 가져가는 것이다.
이렇게 프로그램의 제어 흐름을 직접 제어하는 것이 아니라 외부에서 관리하는 것을 제어의 역전(IoC)라고 한다.
프레임워크 vs 라이브러리
프레임워크가 내가 작성한 코드를 제어하고 대신 실행하면 그것은 프레임워크가 맞다. (JUnit)
내가 작성한 코드가 직접 제어의 흐름을 담당한다면 그것은 프레임워크가 아니라 라이브러리이다.
의존관계 주입 DI (Dependency Injection)
의존관계는 둘을 분리해서 생각해야 한다.
- 정적인 클래스 의존 관계
- 실행 시점에 결정되는 동적인 객체(인스턴스) 의존 관계
정적인 클래스 의존 관계
~~~
> 이러한 클래스 의존관계 만으로는 실제 어떤 객체가 OrderServiceImpl에 주입될지 알 수 없다.
실제로 실행 시켜봐야 한다.
동적인 객체 인스턴스 의존 관계 : 애플리케이션 실행 시점에 실제 생성된 객체 인스턴스의 참조가 연결된 의존 관계
> 애플리케이션 실행 시점에 외부에서 실제 구현 객체를 생성하고 클라이언트에 전달해
클라이언트와 서버의 실제 의존관계가 연결되는 것을 의존관계 주입이라고 한다.
IoC 컨테이너, DI 컨테이너
말 그대로 IoC를 해주는 컨테이너, DI를 해주는 컨테이너라고 보면 된다.
AppConfig처럼 객체를 생성하고 관리하면서 의존관계를 연결해주는 것을 IoC 컨테이너 또는 DI 컨테이너라고 한다.
최근에는 DI 컨테이너라고 부른다고 한다.
스프링으로 전환하기
지금까지 순수하게 자바 코드만으로 DI를 적용했었는데,
이제 스프링을 사용해보자!
기존에는 개발자가 AppConfig 사용해 직접 객체를 생성하고 DI를 했지만,
이제부터는 스프링 컨테이너를 통해 사용할 것이다.
작성했던 AppConfig를 스프링 기반으로 변경해보자

우선 AppConfig에다가 구성정보를 뜻하는 @Configuration 을 붙여준다.
스프링 컨테이너는 @Configuration 이 붙은 AppConfig를 구성(설정)정보로 사용한다.

그리고 각 메서드에 이와 같이 @Bean을 붙여준다. 스프링 컨테이너에 스프링 빈으로 등록하는 것이다.
여기에 이렇게 @Bean이라 적힌 메서드를 모두 호출해 반환된 객체를 스프링 컨테이너에 등록한다.
이렇게 스프링 컨테이너에 등록된 객체를 스프링 빈이라고 한다.
이제 MemberApp.java로 가보자

ApplicationContext 는 스프링 컨테이너라고 보면 된다.
AnnotationConfigApplicationContext는 어노테이션 기반으로 Config를 한다는 것이다.
여기의 파라미터로 AppConfig.class를 넣어준다.
이렇게하면 AppConfig에 있는 환경설정 정보를 가지고 스프링이 AppConfig안에 있는 @Bean이 붙은 것들을
스프링 컨테이너에 객체 생성한 것을 집어넣고 관리해준다.
기존에는 MemberService memberService = appConfig.memberService(); 와 같이
AppConfig에서 직접 찾아왔다.
이제부터는 스프링 컨테이너를 통해 필요한 스프링 빈(객체)를 찾아야 한다.

스프링 빈은 이와 같이 ApplicationContext.getBean() 메서드를 사용해 찾을 수 있다.
왼쪽에는 이름, 오른쪽에는 반환 타입을 적어준다.
기본적으로 스프링 빈은 @Bean이 붙은 메서드의 명을 스프링 빈의 이름으로 사용한다.
실행결과 )

잘 실행되는 것을 확인할 수 있다.
이렇게 스프링 컨테이너를 사용할 경우 어떤 장점이 있는지는
스프링 컨테이너의 핵심 기능들을 공부하다보면 확실히 알게 될 것이라고 한다.
다음 시간에는 스프링 컨테이너와 스프링 빈에 대해 정리하도록 하겠다.
'김영한님의 스프링 강의 학습 > 스프링 핵심 원리' 카테고리의 다른 글
#5 싱글톤 컨테이너, #6 컴포넌트 스캔 (0) | 2022.12.22 |
---|---|
#4 스프링 컨테이너와 스프링 빈 (0) | 2022.11.10 |
#3 스프링 핵심 원리 이해2 - 객체지향 원리 적용1 (1) | 2022.09.30 |
#2 스프링 핵심 원리 이해 - 예제 만들기2 (0) | 2022.09.29 |
#2 스프링 핵심 원리 이해 - 예제 만들기1 (0) | 2022.09.28 |