[스프링 핵심 원리 - 기본편] 스프링 핵심 원리 이해

21044 단어 스프링스프링

새로운 할인 정책 개발

public class OrderServiceImpl implements OrderService {
// private final DiscountPolicy discountPolicy = new FixDiscountPolicy();
 private final DiscountPolicy discountPolicy = new RateDiscountPolicy();
}
  • 역할과 구현을 분리하고, 다형성을 활용하여 인터페이스와 구현 객체를 분리 하였음
  • 하지만 DIP와 OCP 설계 원칙을 준수하지 않아 직접 클라이언트 코드를 변경해야하는 문제점 발생
    • DIP : 클라이언트OrderServiceImpl는 추상(인터페이스)DiscountPolicy 뿐만 아니라 구체(구현)클래스 FixDiscountPolicy RateDiscountPolicy에도 의존하고 있음
    • OCP : 지금 코드는 기능을 확장해서 변경하면, 클라이언트 코드에 영향을 주기 때문에 OCP를 위반

기대했던 의존관계 실제 의존관계

  • 클라이언트인 OrderServiceImpl 이 인터페이스 뿐만 아니라 인 구체 클래스도 함께 의존하고 있음 -> DIP 위반
  • 그래서 FixDiscountPolicyRateDiscountPolicy 로 변경하는 순간 OrderServiceImpl 의 소스 코드도 함께 변경해야 함 -> OCP 위반
  • DIP를 위반하지 않도록 인터페이스에만 의존하게 의존관계를 변경해야함

관심사의 분리

클라이언트는 인터페이스에만 의존하도록 코드 변경

public class OrderServiceImpl implements OrderService {
 //private final DiscountPolicy discountPolicy = new RateDiscountPolicy();
 private DiscountPolicy discountPolicy;
}

AppConfig

애플리케이션의 전체 동작 방식을 구성(config)하기 위해, 클라이언트인 OrderServiceImpl대신 구현 객체를 생성하고, 연결하는 책임을 가지는 별도의 설정 클래스를 생성

AppConfig.java

public class AppConfig {

 public MemberService memberService() {
 return new MemberServiceImpl(new MemoryMemberRepository());
 }
 
 public OrderService orderService() {
 return new OrderServiceImpl( new MemoryMemberRepository(), new FixDiscountPolicy());
 }
}
  • AppConfig는 애플리케이션의 실제 동작에 필요한 구현 객체를 생성
    MemberServiceImpl MemoryMemberRepository OrderServiceImpl FixDiscountPolicy
  • AppConfig는 생성한 객체 인스턴스의 참조(레퍼런스)를 생성자를 통해서 주입(연결)
    MemberServiceImplMemoryMemberRepository
    OrderServiceImplMemoryMemberRepository FixDiscountPolicy

MemberServiceImpl - 생성자 주입

public class MemberServiceImpl implements MemberService {
 
 private final MemberRepository memberRepository;
 
 public MemberServiceImpl(MemberRepository memberRepository) {
 this.memberRepository = memberRepository;
 }
 
 ...
  • 설계 변경으로 MemberServiceImplMemoryMemberRepository를 의존하지 않고 단지 MemberRepository 인터페이스만 의존
  • MemberServiceImpl클라이언트 입장에서 생성자를 통해 어떤 구현 객체가 들어올지는 알 수 없음
  • MemberServiceImpl 의 생성자를 통해서 어떤 구현 객체를 주입할지는 오직 외부AppConfig에서 결정
  • MemberServiceImpl은 의존관계에 대한 고민은 외부에 맡기고 실행에만 집중
  • 객체의 생성과 연결은 AppConfig가 담당
  • DIP 완성 : MemberServiceImplMemberRepository인 추상 인터페이스에만 의존, 구체클래스 몰라도 됨
  • 관심사의 분리 : 객체를 생성하고 연결하는 역할과 실행하는 역할이 명확히 분리
  • 클라이언트인 MemberServiceImpl입장에서 보면 의존관계를 마치 외부에서 주입해주는 것 같다고 해서 의존관계 주입 또는 의존성 주입 DI(Dependency Injection) 이라고 함

AppConfig 리팩터링

AppConfig 코드에서 역할과 구현을 분리 해야함

AppConfig.java 리팩터링 후

public class AppConfig {

 public MemberService memberService() {
 return new MemberServiceImpl(memberRepository());
 }
 
 public OrderService orderService() {
 return new OrderServiceImpl(memberRepository(),discountPolicy()); 
 }
 
 public MemberRepository memberRepository() {
 return new MemoryMemberRepository();
 }
 
 public DiscountPolicy discountPolicy() {
 return new FixDiscountPolicy();
 }
}
  • 중복이 제거되었음
  • MemoryMemberRepository를 다른 구현체로 변경할 때 한 부분만 변경하면 됨
  • 역할구현 클래스로 나뉨

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

AppConfig.java

public class AppConfig {

 ...
 
 public DiscountPolicy discountPolicy() {
// return new FixDiscountPolicy();
 return new RateDiscountPolicy();
 }
}
  • AppConfig 에서 할인 정책 역할을 담당하는 구현을 FixDiscountPolicyRateDiscountPolicy 객체로 변경
  • 애플리케이션이 크게 사용 영역과, 객체를 생성하고 구성(Configuration)하는 영역으로 분리되었기 때문에 애플리케이션의 구성 역할을 담당하는 AppConfig만 변경
  • 클라이언트 코드인 OrderServiceImpl 를 포함해서 사용 영역의 어떤 코드도 변경할 필요가 없음

IoC, DI, 그리고 컨테이너

제어의 역전 IoC(Inversion of Control)

  • 프로그램의 제어 흐름을 직접 제어하는 것이 아니라 외부에서 관리하는 것
    내가 뭔가 호출하는것이 아니라 프레임워크 같은 것이 내 코드를 대신 호출해주는 것
  • 제어권이 뒤바뀐다 -> 제어의 역전
  • 기존 프로그램은 구현 객체가 프로그램의 제어 흐름을 스스로 조종했음
  • 반면에 AppConfig가 등장한 이후에 구현 객체는 자신의 로직을 실행하는 역할만 담당
  • 프로그램에 대한 제어 흐름에 대한 권한은 모두 AppConfig가 가지고 있음

프레임워크 vs 라이브러리

  • 내가 작성한 코드를 제어하고, 대신 실행하면 프레임워크 (Ex. JUnit)
  • 내가 작성한 코드가 직접 제어의 흐름을 담당한다면 그것은 라이브러리

의존관계 주입 DI(Dependency Injection)

  • 클라이언트가 인터페이스에 의존하기 때문에 실제 어떤 구현 객체가 사용될지 모름
  • 의존관계는 정적인 클래스 의존 관계와, 실행 시점에 결정되는 동적인 객체(인스턴스) 의존 관계 둘을 분리해서 생각해야 함

정적인 클래스 의존 관계

  • 클래스가 사용하는 import 코드만 보고 의존관계를 쉽게 판단할 수 있음
  • 애플리케이션을 실행하지 않아도 분석할 수 있음
  • 하지만 이러한 클래스 의존관계 만으로는 실제 어떤 객체가 OrderServiceImpl에 주입 될지 알 수 없음

동적인 클래스 의존 관계

  • 애플리케이션 실행 시점에 실제 생성된 객체 인스턴스의 참조가 연결된 의존 관계
  • 애플리케이션 실행 시점(런타임)에 외부에서 실제 구현 객체를 생성하고 클라이언트에 전달해서 클라이언트와 서버의 실제 의존관계가 연결 되는 것이 의존관계 주입
  • 객체 인스턴스를 생성하고, 그 참조값을 전달해서 연결됨
  • 의존관계 주입을 사용하면 클라이언트 코드를 변경하지 않고, 클라이언트가 호출하는 대상의 타입 인스턴스를 변경 가능
  • 의존관계 주입을 사용하면 정적인 클래스 의존관계를 변경하지 않고, 동적인 객체 인스턴스 의존관계를 쉽게 변경할 수 있음

IoC 컨테이너, DI 컨테이너

  • AppConfig 처럼 객체를 생성하고 관리하면서 의존관계를 연결해 주는 것을 IoC 컨테이너 또는 DI 컨테이너 라고 함
  • 의존관계 주입에 초점을 맞추어 최근에는 주로 DI 컨테이너라 함

스프링으로 전환하기

AppConfig.java

@Configuration
public class AppConfig {

 @Bean
 public MemberService memberService() {
 return new MemberServiceImpl(memberRepository());
 }
 
 @Bean
 public OrderService orderService() {
 return new OrderServiceImpl( memberRepository(), discountPolicy());
 }
 
 @Bean
 public MemberRepository memberRepository() {
 return new MemoryMemberRepository();
 }
 
 @Bean public DiscountPolicy discountPolicy() {
 return new RateDiscountPolicy();
 }
}
  • AppConfig에 설정을 구성한다는 뜻의 @Configuration을 작성
  • 각 메서드에 @Bean 작성 > 스프링 컨테이너에 스프링 빈으로 등록

OrderApp.java에 스프링 컨테이너 적용

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);
//getBean("메서드 이름",반환 타입)
OrderService orderService = applicationContext.getBean("orderService", OrderService.class);

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


출처
스프링 핵심 원리 - 기본편

좋은 웹페이지 즐겨찾기