스프링 핵심 원리 -2
본 내용은 해당 강의를 듣고 필요에 맞게 요약한 글입니다.
스프링 핵심 원리 - 기본편 - 인프런 | 강의
스프링 입문자가 예제를 만들어가면서 스프링의 핵심 원리를 이해하고, 스프링 기본기를 확실히 다질 수 있습니다., 스프링 핵심 원리를 이해하고, 성장하는 백엔드 개발자가 되어보세요! 📢
www.inflearn.com
스프링
객체 지향만으로는 OCP(개방 폐쇄 원칙), DIP(의존관계 역전 원칙)을 지킬 수 없다.
스프링은 앞에서 말한 다형성 + OCP(개방 폐쇄 원칙), DIP(의존관계 역전 원칙)을 가능하게 여러가지 기능을 제공한다.
- Dependency Injection(의존관계 주입)
- DI 컨테이너 제공
- 클라이언트의 코드의 변경없이 기능 확장
먼저 간단한 예시 비즈니스 도메인을 만들었다.
역할과 구현, 책임을 잘 분리해서 설계하는 것이 중요하다.
유연하게 변경이 가능하도록 설계하는 것 또한 중요하다.
클래스 다이어그램은 클래스 간의 관계를 표현한 정적인 다이어그램이다.
객체 다이어그램은 실제 메모리가 할당되는 객체들의 관계를 정의한 동적인 다이어그램이다.
제어의 역전(IOC : Inversion of Control)
기존의 프로그램은 클라이언트 구현 객체가 스스로 필요한 서버 구현 객체를 생성하고, 연결하고 실행했다.
개발자가 원하는 대로 호출하고 생성하는 개념이 아닌 프레임워크가 내 코드를 대신 호출하여 제어권이 뒤바뀌는 것을 제어의 역전 이라고 한다. 다음 예시는 수동으로 빈을 등록하여 객체 인스턴스의 의존관계를 주입하는 예시이다.
@Configuration
public class AppConfig {
@Bean
public MemberService memberService(){
return new MemberServiceImpl(memberRepository());
}
@Bean
protected MemberRepository memberRepository() {
return new MemoryMemberRepository();
}
@Bean
public OrderService orderService(){
return new OrderServiceImpl(memberRepository(), discountPolicy());
}
@Bean
protected DiscountPolicy discountPolicy() {
return new RateDiscountPolicy();
}
}
AppConfig는 프로그램에 제어 흐름 권한을 가져가며, 수동으로 빈을 등록한다.
오더 서비스나 멤버 서비스는 어떠한 구현 객체가 생성되는지 전혀 알지 못한다.
이렇게 프로그램의 제어 흐름을 직접 제어하는 것이 아니라 외부에서 관리하는 것을 제어의 역전이라고 한다.
의존관계 주입 (DI : Dependency Injection)
위의 그림인 클래스 다이어그램을 보면 오더 서비스 구현 객체인 OrderServiceImpl은 할인 정책(Discount Policy) 인터페이스에 의존한다. OrderServiceImpl은 할인 정책에 어떤 구현 객체가 사용될지 모른다.
의존관계에는 정적인 클래스 의존 관계와 실행 시점에 결정되는 동적인 의존관계가 있다.
정적인 클래스 의존관계는 클래스가 사용되는 임포트 코드나 클래스 다이어그램만으로도 파악 가능하지만 실제 어떤 구현 객체가 주입될지는 알 수 없다.
동적인 객체 인스턴스 의존관계는 애플리케이션 실행 시점에 실제 생성된 객체의 인스턴스 참조가 연결된 의존 관계이다.
결론적으로 애플리케이션 실행 시점에 외부에서 실제 구현 객체를 생성하고, 클라이언트에 전달해서 클라이언트와 서버의 실제 의존관계가 연결되는 것을 의존관계 주입이라고 한다.
객체 인스턴스를 생성하고 그 참조값을 전달해서 연결된다. 의존관계 주입을 사용하면 정적인 클래스 의존관계를 변경하지 않고, 동적인 객체 인스턴스의 변경을 쉽게 할 수 있다.
그리고 스프링은 스프링 컨테이너로 이를 관리해준다.
스프링 컨테이너 ( DI 컨테이너 )
위의 예시인 AppConfig처럼 객체를 생성 및 관리하고 의존관계를 주입해주는 역할을 하는 것을 스프링 컨테이너 또는 DI 컨테이너라고 한다. 스프링 컨테이너는 AppConfig처럼 @Configuration이 붙은 컨피그 클래스를 설정 정보로 사용한다.
스프링 컨테이너를 만드는 방법
ApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);
ApplicationContext는 인터페이스이며, ApplicationContext를 스프링 컨테이너라고 한다.
스프링 컨테이너는 애노테이션으로 만들 수도 있고, XML 방식으로도 만들 수 있다.
AnnotationConfigApplicationContext는 ApplicationContext의 구현체이다.
스프링 컨테이너는 설정 정보를 참고해서, 의존관계를 주입한다. 위의 AppConfig를 보면 단순히 자바 코드를 호출하는 것 같지만 스프링 컨테이너는 기본적으로 싱글톤 방식으로 스프링 빈을 등록한다.
스프링 컨테이너에서 스프링 빈을 조회하는 방법
getBean(빈이름, 타입)을 활용하여 조회하면 된다. 다음은 멤버 서비스 빈을 조회하는 예시이다.
MemberService memberService = ac.getBean("memberService",MemberService.class);
타입이 둘 이상이면 다음과 같은 방식으로 조회 가능하다.
Map<String, MemberRepository> beansOfType = ac.getBeansOfType(MemberRepository.class);
스프링 빈을 조회 시, 부모 타입으로 조회하면 자식 타입도 함께 조회한다. 만약 부모의 자식이 둘 이상이면NoUniqueBeanDefinitionException이 발생하므로 빈 이름을 지정해주어야한다.
BeanFactory와 ApplicationContext
BeanFactory는 스프링 컨테이너의 최상위 인터페이스로, 스프링 빈을 관리하고 조회하는 역할을 한다.
ApplicationContext는 BeanFactory의 기능을 모두 상속받아서 제공하며,추가로 여러가지 부가 기능을 제공한다.
- 메시지소스를 이용한 국제화 기능
- 예를 들면 한국에서 들어오면 한국어로, 영어권에서 들어오면 영어로 출력하는 기능 - 환경 변수
- 로컬, 운영, 개발 등을 구분해서 처리 - 애플리케이션 이벤트
- 이벤트를 발행하고 구독하는 모델을 편리하게 지원 - 편리한 리소스 조회
- 파일, 클래스패스, 외부 등에서 리소스를 편리하게 조회
정리하면 ApplicationContext는 BeanFactory의 기능을 상속받아 사용하며, 빈 관리 기능 외에도 추가적인 기능을 제공한다.
BeanFactory나 ApplicationContext을 스프링 컨테이너라고 한다.
ApplicationContext는 또한 애노테이션, xml, Groovy 등 다양한 설정 정보를 지원하게끔 구현 객체를 가지고 있다.
스프링 빈 설정 메타정보
스프링이 이렇게 다양한 설정 정보를 지원할 수 있는 이유는 BeanDefinition이라는 추상화 덕분이다.
BeanDefinition을 빈 설정 메타정보라고 하며, 스프링 컨테이너는 이 메타정보를 기반으로 스프링 빈을 생성한다.
@Bean 애노테이션 하나에 각각 하나씩 메타 정보가 생성된다.
더 자세히 들어가자면
1. AnnotationConfigApplicationContext 는 AnnotatedBeanDefinitionReader 를 사용해서 AppConfig.class 를 읽고 BeanDefinition을 생성한다.
2. GenericXmlApplicationContext는 XmlBeanDefinitionReader를 사용해서 appConfig.xml 설정 정보를 읽고 BeanDefinition을 생성한다.
3.새로운 형식의 설정 정보가 추가되면, XxxBeanDefinitionReader를 만들어서 BeanDefinition을 생성하면 된다.
BeanDefinition의 정보
- BeanClassName: 생성할 빈의 클래스 명(자바 설정 처럼 팩토리 역할의 빈을 사용하면 없음)
- factoryBeanName: 팩토리 역할의 빈을 사용할 경우 이름, 예) appConfig
- factoryMethodName: 빈을 생성할 팩토리 메서드 지정, 예) memberService
- Scope: 싱글톤(기본값)
- lazyInit: 스프링 컨테이너를 생성할 때 빈을 생성하는 것이 아니라, 실제 빈을 사용할 때 까지 최대한 생성을 지연 처리 하는지 여부
- InitMethodName: 빈을 생성하고, 의존관계를 적용한 뒤에 호출되는 초기화 메서드 명
- DestroyMethodName: 빈의 생명주기가 끝나서 제거하기 직전에 호출되는 메서드 명
- Constructor arguments, Properties: 의존관계 주입에서 사용한다. (자바 설정 처럼 팩토리 역할의 빈을 사용하면 없음)
프레임워크 VS 라이브러리
프레임워크가 내가 작성한 코드를 제어하고, 대신 실행하면 그것은 프레임워크이다.
ex) 자바 테스트로 활용하는 Junit이 대표적인 예시이다. 나는 테스트 코드만 짤뿐 실제 실행이 어떤 프로세스로 흘러가는 지 알 수 없다.
반면에, 내가 작성한 코드가 직접 제어의 흐름을 담당한다면 그것은 라이브러리이다.