본문 바로가기
Spring 이해하기

Ioc(Inversion of Controller)

by simplify-len 2020. 8. 26.

Ioc(Inversion of Controller) 란 무엇인가?

"제어권을 역전시킨다." 라는 말의 의미

0. 들어가며

스프링프레임워크를 비교적 손쉽게 사용하고, Service layer에 퍼사드로서 다양한 객체들을 합쳐 코딩할때쯤 IoC에 대해서 조금더 명확하게 알아야 겠다는 생각이 들었습니다.

Inversion of Controller 이라는 말은 간단히 생각하면 제어권을 역전시킨다. 의 의미로 쉽게 받아들여질 수 있습니다.

그러나, IoC에 대해서 다른 사람들은 어떻게 정의내리는지 확인하고, 좀 더 깊은 고찰로 이끌어내보자.

1. Ioc에 대한 간략한 설명

인프런의 백기선님의 강의에서 IoC의 설명을 빌리면,

"내가 쓸 놈은 내가 만들어 쓸게" 라는 식의 일반적인 의존성이 뒤집혀진다는 것을 의미합니다.

여기서 대표적으로, DI의 같은 의존성 주입도 될 수 있지만, 더 나아가 Bean, Container에 대한 부분도 IoC에 해당된다고 말할 수 있습니다.

더욱 중요한 것은 나 이외의 밖에서 누군가 의존성을 주입해주는 것을 의미합니다. 즉, 의존성이 나 이외에 다른 이가 제어하게 해주는 것을 의미합니다.

class OwnerController { 
  private OwnerRepository repo;

public OwnerController(OwnerRepository repo) { 
  this.repo = repo; 
}

// repo를 사용합니다.

}

class OwnerControllerTest { 
  @Test 
  public void create() { 
    OwnerRepository repo = new OwnerRepository(); 
    OwnerController controller = new OwnerController(repo); 
  } 
}

위 코드에서 보다시피, OwnerController 는 누군가 외부의 주입을 기다리고 있습니다. 즉, 의존성이 OwnerRepository로 역전되었음을 확인할 수 있습니다.

그럼 스프링 프레임워크에서는 IoC가 어떻게 사용되었는지 확인해보자.

2. Spring Framework 에서 IoC 는 어디일까?

해당 내용은 토비의 스프링 책의 "Ioc 컨테이너와 DI" 라는 파트에 내용을 인용했습니다.

IoC 컨테이너와 DI

IoC 컨테이너 - Bean Factory와 ApplicationContext

  • 스프링 애플리케이션에서는 오브젝트의 생성과 관계 설정, 사용, 제거 등의 작업을 애플리케이션 코드 대신 독립된 컨테이너가 담당한다. 이를 컨테이너가 코드 대신 오브젝트에 대한 제어권을 갖고 있다고 해서 IoC 라고 부른다. 그래서 스프링 컨테이너를 IoC 컨테이너라고도 한다.
  • 스프링 컨테이너는 단순한 DI 작업보다 더 많은 일을 한다. DI를 위한 빈 팩토리에 엔터프라이즈 애플리케이션을 개발하는 데 필요한 여러 가지 컨테이너 기능을 추가한 것을 애플리게이션 컨텍스트라고 부른다.
  • 스프링의 빈 팩토리와 애플리케이션 컨텍스트는 각각 기능을 대표하는 BeanFactory와 ApplicationContext라는 두 개의 인터페이스로 정의되어 있다. ApplicationContext 인터페이스는 BeanFactory 인터페이스를 상속한 서브 인터페이스이다.

애플리케이션에 대한 구성을 제공하는 중앙 인터페이스. 응용 프로그램이 실행되는 동안에는 읽기 전용이지만 구현에서 지원하는 경우 다시로드 될 수 있습니다.

ApplicationContext는 다음을 제공합니다.
애플리케이션 컴포넌트에 액세스하기위한 Bean 팩토리 메소드. ListableBeanFactory에서 상속됩니다.
일반적인 방식으로 파일 리소스를로드하는 기능. org.springframework.core.io.ResourceLoader 인터페이스에서 상속됩니다.
등록 된 리스너에 이벤트를 게시하는 기능. ApplicationEventPublisher 인터페이스에서 상속됩니다.
국제화를 지원하는 메시지를 해결하는 기능. MessageSource 인터페이스에서 상속됩니다.
상위 컨텍스트에서 상속. 하위 컨텍스트의 정의가 항상 우선합니다.

예를 들어, 전체 웹 애플리케이션에서 단일 상위 컨텍스트를 사용할 수있는 반면 각 서블릿에는 다른 서블릿과 독립적인 자체 하위 컨텍스트가 있습니다.

public interface ApplicationContext extends EnvironmentCapable, ListableBeanFactory, HierarchicalBeanFactory,
        MessageSource, ApplicationEventPublisher, ResourcePatternResolver {

    /**
     * Return the unique id of this application context.
     * @return the unique id of the context, or {@code null} if none
     */
    @Nullable
    String getId();

    /**
     * Return a name for the deployed application that this context belongs to.
     * @return a name for the deployed application, or the empty String by default
     */
    String getApplicationName();

    /**
     * Return a friendly name for this context.
     * @return a display name for this context (never {@code null})
     */
    String getDisplayName();

    /**
     * Return the timestamp when this context was first loaded.
     * @return the timestamp (ms) when this context was first loaded
     */
    long getStartupDate();

    /**
     * Return the parent context, or {@code null} if there is no parent
     * and this is the root of the context hierarchy.
     * @return the parent context, or {@code null} if there is no parent
     */
    @Nullable
    ApplicationContext getParent();

    /**
     * Expose AutowireCapableBeanFactory functionality for this context.
     * <p>This is not typically used by application code, except for the purpose of
     * initializing bean instances that live outside of the application context,
     * applying the Spring bean lifecycle (fully or partly) to them.
     * <p>Alternatively, the internal BeanFactory exposed by the
     * {@link ConfigurableApplicationContext} interface offers access to the
     * {@link AutowireCapableBeanFactory} interface too. The present method mainly
     * serves as a convenient, specific facility on the ApplicationContext interface.
     * <p><b>NOTE: As of 4.2, this method will consistently throw IllegalStateException
     * after the application context has been closed.</b> In current Spring Framework
     * versions, only refreshable application contexts behave that way; as of 4.2,
     * all application context implementations will be required to comply.
     * @return the AutowireCapableBeanFactory for this context
     * @throws IllegalStateException if the context does not support the
     * {@link AutowireCapableBeanFactory} interface, or does not hold an
     * autowire-capable bean factory yet (e.g. if {@code refresh()} has
     * never been called), or if the context has been closed already
     * @see ConfigurableApplicationContext#refresh()
     * @see ConfigurableApplicationContext#getBeanFactory()
     */
    AutowireCapableBeanFactory getAutowireCapableBeanFactory() throws IllegalStateException;

}
  • 실제로 스프링 컨테이너 또는 IoC 컨테이너라고 말하는 것은 바로 이 ApplicationContext인터페이스를 구현한 클래스의 오브젝트
  • 스프링 애플리케이션은 최소한 하나 이상의 IoC컨테이너, 즉 애플리케이션 컨텍스트 오브젝트를 갖고 있다. 하나 이상이라고 하는 이유는 한 개 이상의 애플리케이션 오브젝트를 갖고 있는 경우가 많기 때문이다.
  • 자세한 ApplicationContext 설명 링크
    https://docs.spring.io/spring-framework/docs/5.2.8.RELEASE/javadoc-api/org/springframework/context/ApplicationContext.html

용어의 어려움이 존재한다. ApplicationContext를 상속받은, 다양한 클래스가 클래스가 존재하고, ApplicationContext를 활용해 BeanFactory를 만들고, 이는 Bean을 등록시킬 수 있는 키라고 생각할 수 있겠다.

2. IoC 컨테이너의 종류와 사용 방법

ApplicationContext 를 구현하는 다양한 IoC 컨테이너가 존재한다. 그 중에서도 토비의 스프링에서 언급된 컨테이너만 살펴보겠다.

StaticApplicationContext

StaticApplicationContext는 코드를 통해 빈 메타정보를 등록하기 위해 사용한다. 스프링의 기능에 대한 학습 테스트를 만들 때를 제외하면 실제로 사용되지 않는다.

스태틱 애플리케이션 컨텍스트는 실전에서는 사용하면 안 된다. 테스트 목적으로 코드를 통해 빈을 등록하고 컨테이너가 어떻게 동작하는지 확인하고 싶을 때를 대비해 이런 컨테이너가 있다는 정도만 기억해두자.

GenericApplicationContext

  • GenericApplicationContext는 가장 일반적인 애플리케이션 컨텍스트의 구현 클래스다. 실전에서 사용될 수 있는 모든 기능을 갖추고 있는 애플리케이션 컨텍스트다. 컨테이너의 주요 기능을 DI를 통해 확장할 수 있도록 설계되어 있다.
  • GenericApplicationContext는 StaticApplicationContext외는 달리 XML 파일과 같은 외부의 리소스에 있는 빈 설정 메타정보를 리더를 통해 읽어들여서 메타정보로 전환해서 사용한다.
  • 특정 포맷의 빈 설정 메타정보를 읽어서 이를 애플리케이션 컨텍스트가 사용할 수있는 BeanDefinition 정보로 변환하는 기능을 가진 오브젝트는 BeanDefinitionReader 인터페이스를 구현해서 만들고, 빈 설정 정보 리더라고 불린다. XML로 작성된 빈 설정 정보를 읽어서 컨테이너에게 전달하는 대표적인 빈 설정정보 리더는 XmlBeanDefinitionReader다.
  • 스프링 IoC 컨테이너가 사용할 수 있는 BeanDefinition 오브젝트로 변환만 될 수 있다면 설정 메타정보는 어떤 포맷으로 만들어져도 상관없다. 스프링은 XML 말고도 프로퍼티 파일에서 빈 설정 메타정보를 가져오는 PropertiesBeanDefinitionReader도 제공한다. 이를 이용하면 프로퍼티 파일 안에 아래와 같이 빈 설정 메타정보를 작성할 수도 있다.
printer.(Class)=springbook.learningtest.spring.ioc.bean.StringPrinter

hello.(Class)=springbook.learningtest.spring.ioc.bean.Hello
hello.name=Spring
hello.printer(ref)=printer
  • GenericApplicationContext는 빈 설정 리더를 여러 개 사용해서 여러 리소스로부터 설정 메타정보를 읽어들이게도 할 수 있다. 모든 설정 메타정보를 가져온 후에 refresh( ) 메소드를 한 번 호출해서 애플리케이션 컨텍스트가 필요한 초기화 작업을 수행하게 해주면 된다.
  • 아래와 같이 테스트 클래스를 만들었다면 테스트가 실행되면서 GenericApplicationContext가 생성되고 @ContextConfiguration에 지정한 XML 파일로 초기화돼서 테스트 내에서 사용할 수 있도록 준비된다.
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "/test-applicationContext.xml")
public class UserServiceTest {
    @Autowired ApplicationContext applicationContext;
}

GenericXmlApplicationContext

  • 코드에서 GenericApplicationContext 를 사용하는 경우에는 번거롭게 XmlBeanDefinitionReader를 직접 만들지 말고, 이 두 개의 클래스가 결합된 GenericXmlApplicationContext를 사용하면 편리하다.
  • GenericXmlApplicationContext는 XmlBeanDefinitionReader를 내장하고 있기 때문에, XML 파일을 읽어들이고 refresh()를 통해 초기화하는 것까지 한 줄로 끝낼 수 있다.
GenericApplicatonContext ac = new GenericXmlApplicationContext(
    "springbook/learningtest/spring/ioc/genericApplicationContext.xml");
Hello hello = ac.getBean("hello", Hello.class)

WebApplicationContext

스프링 애플리케이션에서 가장 많이 사용되는 애플리케이션 컨텍스트는 바로 WebApplicationContext다. WebApplicationContext는 ApplicationContext를 확장한 인터페이스이므로 정확히는 WebApplicationContext를 구현한 클래스를 사용하는 셈이다. 이름 그대로 웹 환경에서 사용할 때 필요한 기능이 추가된 애플리케이션 컨텍스트다. 스프링 애플리케이션은 대부분 서블릿 기반의 독립 웹 애플리케이션(WAR)으로 만들어지기 때문이다.

public interface WebApplicationContext extends ApplicationContext {

    // skip...

    /**
     * Return the standard Servlet API ServletContext for this application.
     */
    @Nullable
    ServletContext getServletContext();
}
  • 가장 많이 사용되는건, XML 설정 파일을 사용하도록 만들어진 XmlWebApplicatoinContext다. 애노테이션을 이용한 설정 리소스만 사용한다면 AnnotationConfigWebApplicationContext를 쓰면 된다.

  • 스프링 IoC 컨테이너는 빈 설정 메타정보를 이용해 빈 오브젝트를 만들고 DI 작업을 수행한다. 하지만 그것만으로는 애플리케이션이 동작하지 않는다. 마치 자바 애플리케이션의 main() 메소드처럼 어디에선가 특정 빈 오브젝트의 메소드를 호출함으로써 애플리케이션을 동작시켜야 한다. IoC 컨테이너의 역할은 초기에 빈 오브젝트를 생성하고 DI 한 후에 최초로 애플리케이션을 기동할 빈 하나를 제공해주는 것까지다.

  • 웹 환경에서는 main() 메소드 대신 서블릿 컨테이너가 브라우저로부터 오는 HTTP 요청을 받아서 해당 요청에 매핑되어 있는 서블릿을 실행해주는 방식으로 동작한다. 서블릿이 일종의 main() 메소드와 같은 역할을 하는 셈이다.

  • 그렇다면 웹 애플리케이션에서 스프링 애플리케이션을 기동시키는 방법은 무엇일까?

    일단 main() 메소드 역할을 하는 서블릿을 만들어두고, 미리 애플리케이션 컨텍스트를 생성해둔 다음, 요청이 서블릿으로 들어올 때마다 getBean()으로 필요한 빈을 가져와 정해진 메소드를 실행해주면 된다.

  • 위의 그림은 웹 환경에서 스프링 빈으로 이뤄진 애플리케이션이 동작하는 구조다.

    • 서블릿 컨테이너는 브라우저와 같은 클라이언트로부터 들어오는 요청을 받아서 서블릿을 동작시켜주는 일을 맡는다.
    • 서블릿은 웹 애플리케이션이 시작될 때 미리 만들어둔 웹 애플리케이션 컨텍스트에게 빈 오브젝트로 구성된 애플리케이션의 기동 역할을 해줄 빈을 요청해서 받아둔다.
    • 그리고 미리 지정된 메소드를 호출함으로써 스프링 컨테이너가 DI 방식으로 구성해둔 애플리케이션의 기능이 시작되는 것이다.
  • 스프링은 이런 웹 환경에서 애플리케이션 컨텍스트를 생성하고 설정 메타 정보로 초기화해주고, 클라이언트로부터 들어오는 요청마다 적절한 빈을 찾아서 이를 실행해주는 기능을 가진 DispatcherServlet이라는 이름의 서블릿을 제공한다.

  • WebApplicationContext의 특징은 자신이 만들어지고 동작하는 환경인 웹 모률에 대한 정보에 접근할 수 있다는 점이다. 이를 이용해 웹 환경으로부터 필요한 정보를 가져오거나, 웹 환경에 스프링 컨테이너 자신을 노출할 수 있다. 컨테이너가 웹 환경에 노출되면 같은 웹 모듈에 들어 있는 스프링 빈이 아닌 일반 오브젝트와 연동될 수 있다.

참고 자료 https://gunju-ko.github.io/toby-spring/2019/03/25/IoC-%EC%BB%A8%ED%85%8C%EC%9D%B4%EB%84%88%EC%99%80-DI.html

댓글