- 해당 게시물은 인프런 - "스프링 핵심 원리 - 기본편" 강의를 참고하여 작성한 글 입니다
- 유료강의이므로 자세한 내용은 없고, 간단한 설명 위주로 정리했습니다
강의 링크
다음과 같이 주문과 할인을 할 수 있는 예제를 만든다
역할과 구현을 분리해서 자유롭게 구현 객체를 조립할 수 있게 설계했다
회원 저장소, 할인 정책은 유연하게 변경할 수 있다
주문 서비스 역할에 해당하는 OrderServiceImpl
은 다음과 같이 구현할 수 있다
public interface OrderService {
Order createOrder(Long memberId, String itemName, int itemPrice);
}
public class OrderServiceImpl implements OrderService {
private final MemberRepository memberRepository = new MemoryMemberRepository();
private final DiscountPolicy discountPolicy = new FixDiscountPolicy();
@Override
public Order createOrder(Long memberId, String itemName, int itemPrice) {
Member member = memberRepository.findById(memberId);
int discountPrice = discountPolicy.discount(member, itemPrice);
return new Order(memberId, itemName, itemPrice, discountPrice);
}
}
만약 여기서 할인 정책을 변경하려면 위 OrderServiceImpl
코드를 다음과 같이 바꾸면 된다
public class OrderServiceImpl implements OrderService {
// private final DiscountPolicy discountPolicy = new FixDiscountPolicy();
private final DiscountPolicy discountPolicy = new RateDiscountPolicy();
}
여기서 문제가 발생한다
OCP, DIP 같은 객체지향 설계 원칙을 준수하지 않았다
OrderServiceImpl
은 DiscountPolicy
인터페이스에 의존하면서 추가로 구체(구현) 클래스도 같이 의존하고 있다
DiscountPolicy
FixDiscountPolicy
, RateDiscountPolicy
지금 코드는 기능을 확장해서 변경하면, 클라이언트 코드에 영향을 준다
실제로 new RateDiscountPolicy()
로 코드가 바뀌었다
따라서 OCP를 위반한다
현재 의존관계를 그림으로 나타내면 다음과 같다
다음과 같이 인터페이스에만 의존하도록 바꿔야 한다
다음과 같이 인터페이스에만 의존하도록 코드를 변경한다
public class OrderServiceImpl implements OrderService {
//private final DiscountPolicy discountPolicy = new RateDiscountPolicy();
private DiscountPolicy discountPolicy;
}
이러면 지금은 구현체가 없어서 코드를 실행할 수 없다
실행하면 null pointer exception
이 발생한다
따라서 OrderServiceImpl
에 DiscountPolicy
의 구현체를 밖에서 대신 생성하고 주입해줘야 한다
애플리케이션의 전체 동작 방식을 구성(config)하기 위해, 구현 객체를 생성하고 연결하는 책임을 가지는 별도의 설정 클래스를 만든다
public class AppConfig {
public MemberService memberService() {
return new MemberServiceImpl(new MemoryMemberRepository());
}
public OrderService orderService() {
return new OrderServiceImpl(new MemoryMemberRepository(), new FixDiscountPolicy());
}
}
AppConfig
는 애플리케이션의 실제 동작에 필요한 구현 객체를 생성한다
AppConfig
는 생성한 객체 인스턴스의 참조(레퍼런스)를 생성자를 통해서 주입(연결)한다
MemberServiceImpl
은 MemberRepository
인 추상(인터페이스)에만 의존하면 된다
구체 클래스를 몰라도 된다
프로그램 제어 흐름을 직접 제어하는 것이 아니라 외부에서 관리하는 것을 제어의 역전(IoC)이라 한다
프로그램에 대한 제어 흐름 권한은 모두 AppConfig
가 가지고 있다
AppConfig
를 만들기 전에는 클라이언트 구현 객체에서 직접 필요한 객체를 생성하고, 연결하고, 실행했다
AppConfig
가 등장한 이후에는 구현 객체는 자신의 로직을 실행하는 역할만 담당한다
프로그램 제어 흐름은 이제 AppConfig
가 가져간다
애플리케이션 실행 시점(런타임)에 외부에서 실제 구현 객체를 생성하고 클라이언트에 전달해서 클라이언트와 서버의 실제 의존관계가 연결 되는 것을 의존관계 주입이라 한다
객체 인스턴스를 생성하고, 그 참조값을 전달해서 연결된다
의존관계 주입을 사용하면 클라이언트 코드를 변경하지 않고, 클라이언트가 호출하는 대상의 타입 인스턴스를 변경할 수 있다
의존관계 주입을 사용하면 정적인 클래스 의존관계를 변경하지 않고, 동적인 객체 인스턴스 의존관계를 쉽게 변경할 수 있다
OrderServiceImpl
은 DiscountPolicy
인터페이스에 의존한다
실제 어떤 구현 객체가 사용될지는 모른다
애플리케이션 실행 시점에 참조가 연결된 후 알 수 있다
AppConfig
처럼 객체를 생성하고 관리하면서 의존객체를 연결해 주는 것을 IoC 컨테이너 또는 DI 컨테이너라 한다
의존관계 주입에 초점을 맞춰서 주로 DI 컨테이너라 한다
AppConfig
클래스 내용을 다음과 같이 바꾼다
@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();
}
}
@Configuration
어노테이션을 붙인다
이는 스프링 설정 클래스를 나타낸다
각 메소드에 @Bean
을 붙여준다
이렇게 하면 스프링 컨테이너에 스프링 빈으로 등록한다
다음은 스프링 컨테이너를 사용하는 예제다
public class MemberApp {
public static void main(String[] args) {
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);
MemberService memberService = applicationContext.getBean("memberService", MemberService.class);
Member member = new Member(1L, "memberA", Grade.VIP);
memberService.join(member);
Member findMember = memberService.findMember(1L);
System.out.println("new member = " + member.getName());
System.out.println("find Member = " + findMember.getName());
}
}
ApplicationContext
를 스프링 컨테이너라 한다
스프링 컨테이너는 @Configuration
이 붙은 AppConfig
클래스를 설정(구성) 정보로 사용한다
여기서 @Bean
이라 적힌 메소드를 모두 호출해서 반환된 객체를 스프링 컨테이너에 등록한다
스프링 컨테이너에 있는 스프링 빈은 application.getBean()
메소드를 사용해서 찾을 수 있다