[스프링 핵심 원리 - 기본편] 스프링 핵심 원리 이해
새로운 할인 정책 개발
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를 위반
- DIP : 클라이언트
기대했던 의존관계 실제 의존관계
- 클라이언트인
OrderServiceImpl
이 인터페이스 뿐만 아니라 인 구체 클래스도 함께 의존하고 있음 -> DIP 위반 - 그래서
FixDiscountPolicy
를RateDiscountPolicy
로 변경하는 순간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는 생성한 객체 인스턴스의 참조(레퍼런스)를 생성자를 통해서 주입(연결)
MemberServiceImpl
→MemoryMemberRepository
OrderServiceImpl
→MemoryMemberRepository
FixDiscountPolicy
MemberServiceImpl - 생성자 주입
public class MemberServiceImpl implements MemberService {
private final MemberRepository memberRepository;
public MemberServiceImpl(MemberRepository memberRepository) {
this.memberRepository = memberRepository;
}
...
- 설계 변경으로
MemberServiceImpl
은MemoryMemberRepository
를 의존하지 않고 단지MemberRepository 인터페이스
만 의존 MemberServiceImpl
클라이언트 입장에서 생성자를 통해 어떤 구현 객체가 들어올지는 알 수 없음MemberServiceImpl
의 생성자를 통해서 어떤 구현 객체를 주입할지는 오직 외부AppConfig
에서 결정MemberServiceImpl
은 의존관계에 대한 고민은 외부에 맡기고 실행에만 집중
- 객체의 생성과 연결은
AppConfig
가 담당 - DIP 완성 :
MemberServiceImpl
은MemberRepository
인 추상 인터페이스에만 의존, 구체클래스 몰라도 됨 - 관심사의 분리 : 객체를 생성하고 연결하는 역할과 실행하는 역할이 명확히 분리
- 클라이언트인
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
에서 할인 정책 역할을 담당하는 구현을FixDiscountPolicy
→RateDiscountPolicy
객체로 변경- 애플리케이션이 크게 사용 영역과, 객체를 생성하고 구성(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()
메서드를 사용해서 스프링 컨테이너를 통해 필요한 스프링 빈을 찾을 수 있음- 기존에는 개발자가 직접 자바코드로 모든 것을 했다면 이제부터는 스프링 컨테이너에 객체를 스프링 빈으로 등록하고, 스프링 컨테이너에서 스프링 빈을 찾아서 사용하도록 변경되었음
Author And Source
이 문제에 관하여([스프링 핵심 원리 - 기본편] 스프링 핵심 원리 이해), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://velog.io/@jihyeonee/스프링-핵심-원리-기본편-스프링-핵심-원리-이해2저자 귀속: 원작자 정보가 원작자 URL에 포함되어 있으며 저작권은 원작자 소유입니다.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)