인프런 김영한 강사님의 <스프링 핵심 원리-기본편> 강의를 정리한 글입니다.
지난 프로그램에서 새로운 할인 정책을 확장해보자.

객체 지향에 따라서 개발을 진행하였기 때문에, DiscountPoicy 인터페이스를 따르는 RateDiscountPolicy 클래스를 새로 만들면 된다.
근데 과연 클래스를 새로 생성하기만 하면 해결될까?
정답은 아니다. 다음 코드를 확인해보자.

RateDiscountPolicy 클래스를 생성한 뒤,
OrderSeviceImpl에서 FixDiscountPolicy 객체를 RateDiscountPolicy 객체로 코드 수정을 해주어야 한다.
이는 앞서 언급되었던 SOLID 원칙 중 OCP, DIP를 위배한다.


우리는 인터페이스에만 의존하도록 의존 관계를 변경하면 된다.
단순히 그냥 관계를 바꿔버리자!

아무런 값도 할당하지 않았으니
당연히 NullpPoingException이 뜨겠지...
누군가가 클라이언트(OrderServiceImpl)에 구현객체(DiscountPolicy)를 대신 생성하고 주입해주어야 한다.
클라이언트(OrderServiceImpl)에 구현객체(DiscountPolicy)를 직접 생성하는 것은 로미오 배역을 맡은 배우가 줄리엣 배역을 맡은 배우를 직접 데려오는 것과 똑같다!
AppConfig는 애플리케이션의 전체 동작 방식을 구성(config)하기 위해, 구현 객체를 생성하고 연결하는 책임을 가지는 별도의 설정 클래스이다.


AppConfig.java
// AppConfig는 애플리케이션의 실제 동작에 필요한 구현 객체를 생성한다.
public class AppConfig {
public MemberService memberService() {
return new MemberServiceImpl(new MemoryMemberRepository());
}
public OrderService orderService() {
return new OrderServiceImpl(new MemoryMemberRepository(), new FixDoiscountPolicy());
}
MemberServiceImpl.java, OrderServiceImpl.java
public class MemberServiceImpl implements MemberService{
private final MemberRepository memberRepository;
public MemberServiceImpl(MemberRepository memberRepository) {
this.memberRepository = memberRepository;
}
.
.
.
}
public class OrderServiceImpl implements OrderService{
private final MemberRepository memberRepository;
private final DiscountPolicy discountPolicy;
public OrderServiceImpl(MemberRepository memberRepository, DiscountPolicy discountPolicy) {
this.memberRepository = memberRepository;
this.discountPolicy = discountPolicy;
}
.
.
.
}
MemberApp, OrderApp
public class MemberApp {
public static void main(String[] args) {
AppConfig appConfig = new AppConfig();
MemberService memberService = appConfig.memberService();
.
.
.
}
}
public class OrderApp {
public static void main(String[] args) {
AppConfig appConfig = new AppConfig();
MemberService memberService = appConfig.memberService();
OrderService orderService = appConfig.orderService();
.
.
.
}
}
public class AppConfig {
public MemberService memberService() {
return new MemberServiceImpl(memberRepository());
}
// repository 역할 분리
private static MemoryMemberRepository memberRepository() {
return new MemoryMemberRepository();
}
public OrderService orderService() {
return new OrderServiceImpl(memberRepository(), discountPolicy());
}
// repository 역할 분리
private static FixDoiscountPolicy discountPolicy() {
return new FixDoiscountPolicy();
}
}
역할을 나누게 되면
나중에 MemoryMemberRepository에서 JDBCMemberRepository로 바뀌었을 때
memberRepository만 변경하면 된다.
나중에 FixDoiscountPolicy에서 RateDoiscountPolicy로 바뀌었을 때
discountPolicy만 변경하면 된다.
기능이 한 눈에 들어온다는 장점도 있다.
역할을 쭉죽 나눠놨기 때문에
AppConfig만 변경하면 할인 정책을 손쉽게 수정할 수 있다.


사용 영역이 아닌 구성 영역만 변경하면 된다!!
다음 세 가지 원칙이 적용되어 있다.
[제어의 역전 IoC(Inversion of Control)]
[의존 관계 주입 DI(Dependency Injection)]
"정적인 클래스 의존관계와 실행 시점에저 결정되는 동적인 객체 인스턴스 의존 관계"를 분리하여 생각해야 한다.
정적인 의존관계
동적인 의존관계

[IoC 컨테이너, DI 컨테이너]
@Configuration
public class AppConfig {
@Bean
public MemberService memberService() {
return new MemberServiceImpl(memberRository());
}
@Bean
public static MemoryMemberRepository memberRository() {
return new MemoryMemberRepository();
}
@Bean
public OrderService orderService() {
return new OrderServiceImpl(memberRository(), discountPolicy());
}
@Bean
public static DiscountPolicy discountPolicy() {
return new FixDoiscountPolicy();
// return new RateDiscountPolicy();
}
}
public class MemberApp {
public static void main(String[] args) {
// AppConfig appConfig = new AppConfig();
// MemberService memberService = appConfig.memberService();
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);
MemberService memberService = xc
.
.
.
}
}
public class OrderApp {
public static void main(String[] args) {
// AppConfig appConfig = new AppConfig();
//
// MemberService memberService = appConfig.memberService();
// OrderService orderService = appConfig.orderService();
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);
MemberService memberService = applicationContext.getBean("memberService", MemberService.class);
OrderService orderService = applicationContext.getBean("orderService", OrderService.class);
.
.
.
}
}

스프링 컨테이너
- ApplicationContext 를 스프링 컨테이너라 한다.
- 기존에는 개발자가 AppConfig 를 사용해서 직접 객체를 생성하고 DI를 했지만, 이제부터는 스프링 컨테이너를 통해서 사용한다.
- 스프링 컨테이너는 @Configuration 이 붙은 AppConfig 를 설정(구성) 정보로 사용한다. 여기서 @Bean 이라 적힌 메서드를 모두 호출해서 반환된 객체를 스프링 컨테이너에 등록한다. 이렇게 스프링 컨테이너에 등록된 객체를 스프링 빈이라 한다.
스프링 빈은 @Bean 이 붙은 메서드의 명을 스프링 빈의 이름으로 사용한다. ( memberService , orderService )- 이전에는 개발자가 필요한 객체를 AppConfig 를 사용해서 직접 조회했지만, 이제부터는 스프링 컨테이너 를 통해서 필요한 스프링 빈(객체)를 찾아야 한다. 스프링 빈은 applicationContext.getBean() 메서드 를 사용해서 찾을 수 있다.
- 기존에는 개발자가 직접 자바코드로 모든 것을 했다면 이제부터는 스프링 컨테이너에 객체를 스프링 빈으로 등록하고, 스프링 컨테이너에서 스프링 빈을 찾아서 사용하도록 변경되었다.