[Spring] IoC와 DI (1/2)
Inversion of Control
- 제어의 역전이라고 한다.
- 스프링 애플리케이션에서 스프링 빈의 생성과 의존관계 설정, 사용 및 제거 등의 작업을 애플리케이션 코드가 아닌 스프링 컨테이너가 담당한다.
- 이를 스프링 컨테이너가 코드 대신 빈에 대한 제어권을 갖고 있다고 해서 IoC라고 부른다.
- 스프링 컨테이너를 IoC 컨테이너라고 부르기도 한다.
IoC 컨테이너
- 스프링에서 IoC를 담당하는 컨테이너를 빈 팩토리, DI 컨테이너, Application Context라고 부른다.
- 빈(오브젝트)의 생성과 빈 사이의 런타임 관계를 설정하는 DI의 관점에서 컨테이너를 빈 팩토리 또는 DI 컨테이너라고 부른다.
- 그러나 스프링 컨테이너는 단순히 DI작업보다 더 많은 작업을 하는데, DI를 위한 빈 팩토리에 여러가지 기능을 추가한 것을 Application Context라고 한다.
- 정리하면, Application Context는 그 자체로 IoC와 DI 기능 뿐만 아니라 그 이상의 기능을 가지고 있다.
빈 팩토리와 Application Context
빈 팩토리
- 스프링 컨테이너의 최상위 인터페이스
- 스프링 빈을 관리하고 조회하는 역할을 한다.
- getBean() 메서드가 대표적이다.
ApplicationContext
public interface ApplicationContext extends EnvironmentCapable, ListableBeanFactory,
HierarchicalBeanFactory, MessageSource, ApplicationEventPublisher,
ResourcePatternResolver {}
- 빈 팩토리를 상속한 만큼, 빈 팩토리의 기능을 모두 제공한다.
- 위에서 extends한 인터페이스는 모두 빈 팩토리 인터페이스의 서브 인터페이스이며, 빈 팩토리에 없는 추가 기능을 제공한다. 즉, 애플리케이션은 이를 혼합해서 다음과 같은 기능을 제공한다.
- 메시지 소스를 활용한 국제화 기능
- 한국에서 들어오면 한국어로, 영미권에서 들어오면 영어로
- 환경 변수
- 로컬, 개발, 운영 등을 구분해서 처리한다.
- 애플리케이션 이벤트
- 이벤트를 발행하고 구독하는 모델을 편리하게 지원
- 편리한 리소스 조회
- 파일, 클래스패스, 외부 등에서 리소스를 편리하게 조회한다.
- 메시지 소스를 활용한 국제화 기능
설정 메타 정보
IoC 컨테이너의 가장 기초적인 역할은 오브젝트를 생성하고, 이를 관리하는 것이다.
이 때, 스프링 컨테이너가 관리하는 오브젝트를 빈이라 부른다.
설정 메타 정보는 이 빈을 어떻게 만들고 어떻게 동작하게 할 것인가에 관한 정보이다.
스프링 컨테이너는 자바 코드, XML, Groovy 등 다양한 형식의 설정 정보를 받아들일 수 있도록 유연하게 설계되어있다.
어노테이션 기반의 자바 설정
@Configuration
public class AppConfig {
@Bean
public MemberService memberService() {
return new MemberServiceImpl(memberRepository());
}
}
- @Configuration: 1개 이상의 빈을 제공하는 클래스의 경우 반드시 명시해야함
- @Bean: 클래스를 빈으로 등록할 때 사용한다.
XML 기반의 스프링 빈 설정
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://www.springframework.org/schema/beans http://
www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="memberService" class="hello.core.member.MemberServiceImpl">
<constructor-arg name="memberRepository" ref="memberRepository"/>
</bean>
</beans>
- 형식이 다를 뿐, 자바 코드로 된 설정 파일과 비슷하다.
- 최근에는 잘 사용하지 않는다.
스프링 빈 설정 메타 정보 - BeanDefinition
- 스프링은 이런 다양한 형식을 지원하기 위해 BeanDefinition이라는 추상화가 존재한다.
- 자바 코드를 읽어 BeanDefinition을, XML을 읽어 BeanDefinition을 만든다. 그렇기 때문에 스프링 컨테이너는 자바 코드, XML을 구분할 필요 없이 BeanDefinition만 알면 된다.
- 이를 빈 설정 메타 정보라고 하는데, @Bean 과 <bean> 당 각각 하나씩 메타 정보가 생성된다.
- AnnotationConfigApplicationContext는 AnnotatedBeanDefinitionReader를 사용해서 AppConfig.class를 읽고 BeanDefinition을 생성한다.
- GenericXmlApplicationContext는 XmlBeanDefinitionReader를 사용해서 appConfig.xml 설정 정보를 읽고 BeanDefinition을 생성한다.
Dependency Injection - 의존 관계 주입
의존관계란?
"A가 B를 의존한다" 는 추상적인 표현이지만, 토비의 스프링에서는 "의존 대상 B가 변하면, 그것이 A에 영향을 미친다" 라고 한다.
즉, B의 기능이 추가되거나 변경되면 그 영향이 A에 미치는 것이다.
class BurgerChef {
private HamBurgerRecipe hamBurgerRecipe;
public BurgerChef() {
hamBurgerRecipe = new HamBurgerRecipe();
}
}
햄버거 레시피가 변하게 되었을때, 변화된 레시피에 따라서 BurgerChef 클래스를 수정해야한다.
레시피의 변화가 요리사의 행위에 영향을 미쳤기 때문에 요리사는 레시피에 의존한다고 할 수 있다.
Dependency를 인터페이스로 추상화
위 예제에서, BurgerChef는 HamburgerRecipe만 의존할 수 있는 구조로 되어있다.
더 다양한 햄버거 레시피를 의존할 수 있게 구현하려면, 인터페이스로 추상화해야 한다.
class BurgerChef {
private BurgerRecipe burgerRecipe;
public BurgerChef() {
burgerRecipe = new HamBurgerRecipe();
//burgerRecipe = new CheeseBurgerRecipe();
//burgerRecipe = new ChickenBurgerRecipe();
}
}
interface BugerRecipe {
newBurger();
}
class HamBurgerRecipe implements BurgerRecipe {
public Burger newBurger() {
return new HamBerger();
}
}
이제 더 다양한 버거 레시피에 의존할 수 있는 BurgerChef가 되었다.
이처럼 의존 관계를 인터페이스로 추상화하게 되면, 더 다양한 의존 관계를 맺을 수 있고, 실제 구현 클래스와의 관계가 느슨해지면 결합도가 낮아진다.
그래서 결국 DI는?
위 코드에서는 BurgerChef 내부적으로 의존관계인 BurgerRecipe가 어떤 값을 가질지 직접 정하고 있다.
이때 DI는 어떤 햄버거 레시피를 만들 지는 버거 가게 사장이 정하는 상황이라고 할 수 있다. 즉, BurgerChef가 의존하고 있는 BurgerRecipe를 외부에서 결정하고 주입하는 것이다.
class BurgerChef {
private BurgerRecipe burgerRecipe;
public BurgerChef(BurgerRecipe bugerRecipe) {
this.burgerRecipe = bugerRecipe;
}
}
//의존관계를 외부에서 주입 -> DI
new BurgerChef(new HamBurgerRecipe());
new BurgerChef(new CheeseBurgerRecipe());
new BurgerChef(new ChickenBurgerRecipe());
이처럼 의존관계를 외부에서 결정하는 것을 DI(의존 관계 주입)이라고 한다.
스프링에서는 외부의 대상이 IoC 컨테이너가 되어, 빈을 알아서 주입해준다.