ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Spring IoC Container 알아보기
    Language/Spring 2023. 10. 30. 10:19
    728x90
    반응형

    - 목차

     

     

    관련된 글

    https://westlife0615.tistory.com/7

    Spring Bean 알아보기

    - 목차 Bean 이란? Bean 은 스프링에서 관리하는 Java Object 입니다. 일반적인 스프링 웹 환경에서 Controller, Service, DataSource, ThreadPool 등이 Bean 으로써 사용됩니다. 직접 생성하는 Java Object 와 Bean 의 차

    westlife0615.tistory.com

     

     

    소개.

    IoC Container 는 Bean Container 또는 Spring Container 라고 불립니다.
    IoC Container 는 Spring Bean 을 관리하는 Spring 의 요소로써, Bean 의 라이프사이클을 관리하며 책임집니다.
    흔히 Bean 을 managed Java object 라고 부르죠.
    여기서 말하는 managed 가 바로 IoC Container 에 의해 관리됨을 뜻합니다.

    IoC 는 Inversion of Control 의 줄임말입니다.
    IoC 는 제어권을 뒤바뀐다는 의미인데,
    java object 의 생성을 프로그래밍 방식으로 행하던 방식과 달리 설정을 통해서 java object 가 생성됩니다.
    그러니까 new Object() 처럼 매뉴얼하게 생성하는 것이 아닌 XML 이나 어노테이션을 통한 설정으로 객체들이 생성됩니다.
    이렇게 객체를 생성하는 것은 다른 표현으로 Dependency Injection 이라고 합니다.
    Spring 의 Bean 들은 사용자의 코드가 아닌 설정에 의해 생성되므로 Dependency Injection 방식이 달라집니다.
    이러한 역전 현상을 IoC 라고 합니다.

    과거에서 현재까지 Dependency Injection 방식은 변화를 거듭합니다.
    XML 기반의 설정부터 현재는 Annotation 을 통한 방식으로 변해왔습니다.

    이어지는 내용에서 IoC Container 에 대해서 상세히 알아보도록 하겠습니다.

    BeanFactory & ApplicationContext.

    저는 처음에 IoC Container 에 대해서 들었을 때,
    Bean 을 관리하는 역할을 하는 Spring 의 컴포넌트라고 알고 있었습니다.
    그래서 무언가 엄청 거창한 존재라고 느꼈었고,
    그래서 Linux Process 또는 어떤 특별한 자료구조라고 추측하고 있었습니다.
    사실, IoC Container 는 BeanFactory 또는 ApplicationContext 라는 interface 를 구현한 java class 이자
    그 class 를 객체화시킨 인스턴스입니다.
    그러니깐 new BeanFactory() 또는 new ApplicationContext() 와 같은 형식으로 생성하는 java object 인거죠.
     
    그럼 BeanFactory 와 ApplicationContext 는 무엇일까요 ?
    이들은 java 의 interface 인데요.
    먼저 어떻게 생긴 interface 인지 알아보겠습니다.
     
    <BeanFactory Interface>

    public interface BeanFactory {
    
        Object getBean(String name) throws BeansException;
    
        Object getBean(String name, Class<?> requiredType, Object... args) throws BeansException;
    
        String[] getBeanNames();
    
        boolean containsBean(String name);
    
        boolean isSingleton(String name) throws NoSuchBeanDefinitionException;
    
        boolean isPrototype(String name) throws NoSuchBeanDefinitionException;
    
        Class<?> getType(String name) throws NoSuchBeanDefinitionException;
    
        String[] getAliases(String name);
    
        boolean isCurrentlyInCreation(String name);
    
        void destroySingletons();
    
    }

     
    <ApplicationContext Interface>

    public interface ApplicationContext extends BeanFactory {
    
        ResourceBundle getResourceBundle();
        
        MessageSource getMessageSource(Locale locale);
    
        ApplicationEventPublisher getApplicationEventPublisher();
    
        ResourceLoader getResourceLoader();
    
        ConfigurableEnvironment getEnvironment();
    
        ApplicationContext getParent();
    
        URL getWebServerRootUrl();
    
        void close() throws BeansException;
    
        void refresh() throws BeansException;
    
    }

     
    interface 가 대략적으로 어떻게 생겼는지 살펴보았다면, 하나씩 어떤 의미를 가지는지 알아보겠습니다.
     

    BeanFactory.

    BeanFactory 는 이름 그대로 Bean 을 생성하는 공장입니다.
    예를 들어,
    BeanFactory 를 구현하는 클래스의 getBean 메소드 내부에 Bean 을 생성하여 반환하는 코드가 채워질 것입니다.
    이러한 방식으로 BeanFactory 를 통해서 IoC Container, Bean Container 가 생성됩니다.
    BeanFactory 를 구현하는 클래스들은
    - XmlBeanFactory
    - DefaultListableBeanFactory
    등이 있습니다.
    즉, Spring Application 이 구동될 때 XML 또는 Annotation 의 설정을 통해서 모든 Bean 이 생성되구요.
    생성되는 과정에서 BeanFactory 가 사용됩니다.
     

    ApplicationContext.

     
    ApplicationContext 는 BeanFactory 를 상속하는 인터페이스입니다.
    ApplicationContext 가 BeanFactory 를 상속한다는 의미는
    BeanFactory 의 Bean 을 관리하는 역할 뿐만 아니라 다른 역할도 수행할 수 있다는 의미입니다.
    ApplicationContext 가 수행하는 다른 역할들은 아래와 같습니다.
     
    - internationalization and localization
    - resource management
    - event publishing and listening
     
    위의 기능들은 "음, 이런 기능들이 있군.", "ApplicationContext 는 Bean 관리 뿐만 아니라 다른 일도 하는군" 정도만 아셔도 될 것 같습니다.
     
    그럼 왜 ApplicationContext 라고 이름이 붙여졌을까요 ?
    Context 라는 의미는 여러 분야에서 사용됩니다.
     

    Context Switching.

    일반적인 OS 는 멀티프로세싱을 지원합니다.
    이는 Time-Shared 방식으로 여러 프로세스가 CPU 는 점유하게 되는데,
    각 Process 들은 자신의 Process Control Block 이라는 자료구조에 CPU 를 점유했던 상황을 기록합니다.
    - 레지스터에는 어떤 값들로 구성되고
    - Process 가 관리하던 쓰레드들은 어떤 Call Stack 을 가지며
    - Process 가 작업했던 File 과 Socket 등에 대한 정보
    등이 존재합니다.
    한 Process 가 다른 Process 에게 CPU 를 빼앗길 때, 위의 정보들을 PCB 에 저장하고,
    이러한 개념을 Context 라고 부릅니다.
     

    React Context.

    React 에 Context 라는 개념이 있습니다.
    여러 컴포넌트이 공유하는 데이터 또는 상태의 저장소인데요.
    일반적인 View 구조에서 이벤트의 전달을 통해서 부모 컴포넌트 (또는 뷰)와 자식 컴포넌트가 소통을 할 수 있습니다.
    하지만 Context 라는 데이터 또는 상태 저장소를 통해서 컴포넌트 (또는 뷰)들 사이의 소통이 가능해집니다.
    React 에서는 연관있는 컴포넌트 사이의 상태 저장소 및 소통을 위한 창구를 Context 라고 합니다.
     

    Flink Context.

    Flink 는 Runtime Context 하는 개념이 존재합니다.
    말 그래로 Runtime 의 문맥 또는 Runtime 의 환경 정보를 의미하는데요.
    실제 Flink Application 이 구동되는 상태에서 RuntimeContext 를 통해
    어떤 Subtask 또는 어떤 TaskManager 에서 자신이 실행 중인지에 대한 정보를 얻을 수 있습니다.
     
     

    다시 돌아와서...

    Spring 의 ApplicationContext 는 Bean 를 관리하는 역할을 넘어서
    Spring Application 의 시스템적인 기능을 수행할 수 있도록 여러 역할에 관여합니다.
    그래서 ApplicationContext 라고 불린다고 생각하시면 됩니다.
     
    ApplicationContext 를 구현하는 클래스들은 아래와 같습니다.
     
    - ClassPathXmlApplicationContext
    - FileSystemXmlApplicationContext
    - WebXmlApplicationContext
    - AnnotationConfigApplicationContext
    - GenericApplicationContext
    등등
     
     
     
     

    @Autowired.

    @Autowired 를 통해서 손쉽게 Dependency Injection 을 구현할 수 있습니다.
    Autowired 의 종류는 Field Injection 와 Constructor Injection 이 있습니다.
     

    Field Injection.

    Field Injection 은 @Autowired 어노테이션을 Field 에 마킹합니다.
    이를 통해서 해당 Field 에 Bean 에 세팅됩니다.
    자세한 예시는 아래와 같습니다.
    TestBean 는 TestChildBean 을 Dependency 로써 가집니다.
    TestChildBean 는 @Autowired 를 통해서 간편하게 Injection 됩니다.
     
    <예시 코드>

    package org.example;
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.context.ConfigurableApplicationContext;
    import org.springframework.context.annotation.ComponentScan;
    import org.springframework.stereotype.Component;
    
    @SpringBootApplication
    public class Main {
      public static void main(String[] args) {
        SpringApplication application = new SpringApplication(AppConfig.class);
        ConfigurableApplicationContext context = application.run(args);
        TestBean testBean = context.getBean(TestBean.class);
        System.out.printf("childBean is %s", testBean.getChildBean().hashCode());
      }
    }
    
    @ComponentScan("org.example")
    class AppConfig {
    }
    
    @Component
    class TestBean {
      public String name;
      @Autowired
      private TestChildBean childBean;
    
      public TestChildBean getChildBean() {
        return childBean;
      }
    }
    
    @Component
    class TestChildBean {
      public String name;
    }

     
    Field Injection 은 내부적으로 Reflection 을 사용합니다.
    @Autowired 어노테이션이 부착된 Field 에 한해서 IoC Container 로부터 적절한 Bean 을 조회하여
    해당 Bean 을 Reflection 을 통해서 설정합니다.
     
    아래는 Reflection 을 통해서 Autowired 를 구현한 예시입니다.
     
    <Reflection 예시 코드>

    package org.example;
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.context.ConfigurableApplicationContext;
    import org.springframework.context.annotation.ComponentScan;
    import org.springframework.stereotype.Component;
    
    import java.lang.reflect.Field;
    
    @SpringBootApplication
    public class Main {
      public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
        SpringApplication application = new SpringApplication(AppConfig.class);
        ConfigurableApplicationContext context = application.run(args);
        TestBean testBean = context.getBean(TestBean.class);
        System.out.printf("Autowired childBean is %s \n", testBean.getChildBean().hashCode());
    
        TestBean newTestBean = new TestBean();
        Field childBean = TestBean.class.getDeclaredField("childBean");
        childBean.setAccessible(true);
        TestChildBean testChildBean = context.getBean(TestChildBean.class);
        childBean.set(newTestBean, testChildBean);
        System.out.printf("Reflection childBean is %s \n", newTestBean.getChildBean().hashCode());
      }
    }
    
    @ComponentScan("org.example")
    class AppConfig {
    }
    
    @Component
    class TestBean {
      public String name;
      @Autowired
      private TestChildBean childBean;
    
      public TestChildBean getChildBean() {
        return childBean;
      }
    }
    
    @Component
    class TestChildBean {
      public String name;
    }

     
    <실행 결과>
    Autowired 와 Reflection 두가지 케이스의 결과는 동일합니다.

    Autowired childBean is 1627781283
    Reflection childBean is 1627781283

     

     

    Constructor Injection.

    Constructor Injection 은 Dependency 를 인자로 가지는 생성자를 통한 Dependency Injection 입니다.
    Field Injection 시에 @Autowired 를 Field 에 설정했다면,
    Constructor Injection 은 @Autowired 를 생성자에 적용합니다.
     
    예시는 아래와 같습니다.
     

    package org.example;
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.context.ConfigurableApplicationContext;
    import org.springframework.context.annotation.ComponentScan;
    import org.springframework.stereotype.Component;
    
    import java.lang.reflect.Field;
    
    @SpringBootApplication
    public class Main {
      public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
        SpringApplication application = new SpringApplication(AppConfig.class);
        ConfigurableApplicationContext context = application.run(args);
        TestBean testBean = context.getBean(TestBean.class);
        System.out.printf("childBean is %s \n", testBean.getChildBean().hashCode());
    
        TestBean newTestBean = new TestBean(null);
        Field childBean = TestBean.class.getDeclaredField("childBean");
        childBean.setAccessible(true);
        TestChildBean testChildBean = context.getBean(TestChildBean.class);
        childBean.set(newTestBean, testChildBean);
        System.out.printf("childBean is %s \n", newTestBean.getChildBean().hashCode());
      }
    }
    
    @ComponentScan("org.example")
    class AppConfig {
    }
    
    @Component
    class TestBean {
      public String name;
      private TestChildBean childBean;
    
      @Autowired
      public TestBean(TestChildBean childBean) {
        this.childBean = childBean;
      }
    
      public TestChildBean getChildBean() {
        return childBean;
      }
    }
    
    @Component
    class TestChildBean {
      public String name;
    }

     
    <실행 결과>

    childBean is 1340051218 
    childBean is 1340051218

    반응형

    'Language > Spring' 카테고리의 다른 글

    Spring Batch Job 알아보기  (3) 2023.11.24
    Spring Bean 알아보기  (2) 2023.10.30
Designed by Tistory.