ABOUT ME

-

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

    - 목차

     

    Bean 이란?

    Bean 은 스프링에서 관리하는 Java Object 입니다.
    일반적인 스프링 웹 환경에서 Controller, Service, DataSource, ThreadPool 등이 Bean 으로써 사용됩니다.
    직접 생성하는 Java Object 와 Bean 의 차이를 먼저 설명드리면 좋을 것 같은데요.
    다른 개발환경의 케이스를 통해 비유를 들어볼려고 합니다.
     

    예시 1: Thread vs ThreadPool .

    Thread 와 ThreadPool 을 예시로 들 수 있을 것 같습니다.
    멀티쓰레딩을 구현하기 위해서 여러 Thread 를 생성해야하는데요.
    필요한 Thread 수량만큼, new Thread 와 같은 형식으로 생성해서 사용해도 무방합니다.
    다만 효율적인 관리를 위해서 필요한 사이즈의 ThreadPool 을 생성하고,
    ThreadPool 로부터 Thread 를 제공받아도 됩니다.
     
    결과는 동일하지만 제가 사용할 Thread 를 직접 관리하는 Thread Pool 로부터 공급받고 제어되는 차이가 있습니다.
     
    https://westlife0615.tistory.com/319

    Java Thread Pool 알아보기

    - 목차 관련된 글https://westlife0615.tistory.com/318 Java Future 알아보기- 목차 관련된 글 https://westlife0615.tistory.com/319 Java Thread Pool 알아보기 - 목차 소개. Thread Pool 은 Worker Thread 들을 관리하는 자료구조입

    westlife0615.tistory.com

     

    예시 2: Android, IOS 의 View.

    Android Activity 라는 화면 단위 컴포넌트가 있고,
    IOS 에서도 ViewController, View 라는 UI 를 위한 요소가 있습니다.
    Android 는 XML 으로 설정된 View 구조 정보를 토대로 동적인 방식으로 Activity 와 View 가 생성됩니다.
     
    <Android Manifest.xml>

    <manifest>
        <application >
            <activity android:name="com.example.app.MainActivity" >
            </activity>
        </application>
    </manifest>

     
    IOS 의 경우에도 스토리보드라는 툴을 통해서 뷰를 구성할 수 있구요.
    해당 내용 또한 XML 로 변환되어 관리됩니다.
     
    그래서 두 케이스 모두 아래와 같은 방식으로 Dependency Injection 이 발생합니다.

    // Android
    // Manifect.XML 을 통해서 미리 생성된 View 를 조회
    View view = findViewById(R.id.rootView);
    // swift
    @IBOutlet weak var tableView: UITableView!

     
    즉, Android 와 IOS 의 View 는 XML 의 View 정보를 기반으로 미리 생성되고,
    이는 new View(), new TableView 와 같은 manual 한 방식이 아니라 시스템에 의해서 동적으로 생성됩니다.
    그리고 시스템에 의해서 관리됩니다.
     
     

    다시 돌아와서...

    Thread vs ThreadPool, Android-IOS 의 view 케이스를 예로 들어보았습니다.
    Thread 가 Thread Pool 에서 관리되고, 미리 생성되어 존재합니다.
    Android-IOS 의 view 또한 XML 설정 정보를 기반으로 미리 생성되어, 시스템에 의해서 관리됩니다.
     
    이러한 방식처럼,
    Spring 의 Bean 또한 XML 또는 Annotation 방식을 통해서 Spring IoC Container 에 의해서 생성되고 관리됩니다.
    모든 Bean 들은 IoC Container 에 보관되고 관리되어서
    IoC Container 를 Bean Container, Spring Container 라고도 부릅니다.
    참고로 IoC 는 Inversion of Control 의 약자입니다.
     
    요약하자면, Bean 은 XML 이나 Annotation 을 활용한 설정으로 인해서 동적 생성됩니다.
    그리고 Application 은 Bean 을 직접 생성하지 않고, (이미 생성되어 있음.)
    IoC Container 의 인터페이스를 통해서 Bean 요청을 통해서 사용해야합니다.

     
     

    Bean Lifecycle.

    Bean 의 라이프사이클에 대해서 알아보려고 합니다.
    Bean 은 IoC Container 에 의해서 관리됩니다.
    Bean 은 특별한 경우가 아닌한 IoC Container 에 의해서 어플리케이션이 종료될 때까지 관리되는데요.
    따라서 Bean 은 생성부터 삭제까지 IoC Container 의 통제를 받게 됩니다.
    여기서 말하는 특별한 케이스는 아래와 같습니다.
     

    Lifecycle 관련 케이스들.

     

    Bean 의 Scope 가 Singleton 이 아닌 경우.

    Bean 의 Scope 가 Prototype 과 같이 Singleton 이 아닌 경우에는
    IoC Container 에 의해서 생성은 되겠지만, Prototype Bean 의 삭제는 IoC Container 가 아닌 가비지 콜렉터에 의해서 제거될 수도 있습니다.
     

    비정상 종료되는 경우.

    모종의 이유로 java process 자체가 종료될 수 있습니다.
    종료 signal (SIGINT, SIGTERM) 을 받게 되는 경우라든지, 리소스 문제로 종료되던지
    여러가지 이유로 종료된 경우에 Bean 은 정상적인 라이프사이클을 유지할 수 없습니다.
     

    Bean 의 Scope 가 Singleton 인 경우.

    Singleton Bean 의 경우에는 IoC Container 에 의해서 시스템이 종료될 때까지 계속 참조상태를 유지합니다.
    (IoC Container 는 Singleton Bean 을 계속 Reference 합니다. IoC Container --> Singleton Bean)
    그래서 해당 Bean 은 가비지 콜렉터에 의해서 제거되는 대상이 아닙니다.
     
     

    Lifecycle.

    Bean 의 라이프사이클은 아래의 흐름을 가집니다.
    - Instantiation
    - Initialization
    - Destruction
     

    Instantiation & Initialization.

    생성자 또는 @PostConstruct 어노테이션으로 초기 설정을 진행할 수 있습니다.
    생성자의 경우에는 해당 Bean 의 Field 들에 값을 채우는 정도의 작업을 진행할 수 있습니다.
    그저 단순히 생성자의 역할을 수행하죠.
     
    @PostConstruct 는 생성자 이후에 호출되는 라이프사이클인데요.
    이 단계에서 Bean 의 중요한 초기 설정을 진행할 수 있습니다.
    이 단계에서 Dependency Injection 이 완료되기 때문에 Dependency 를 활용한 작업이 가능해집니다.
     
     

    Destruction.

    @PreDestroy 어노테이션을 통해서 Bean 객체가 삭제되기 이전에 수행되어야 할 작업들을 선언할 수 있습니다.
    대체로 자원의 릴리즈에 대한 케이스가 해당 라이프사이클에 적용됩니다.
     
     
    간단한 Configuration 과 Bean 들을 설정해보았습니다.
     

    package org.example;
    
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.context.ConfigurableApplicationContext;
    import org.springframework.context.annotation.Bean;
    
    import javax.annotation.PostConstruct;
    import javax.annotation.PreDestroy;
    
    @SpringBootApplication
    public class Main {
      public static void main(String[] args) {
        SpringApplication application = new SpringApplication(AppConfig.class);
        ConfigurableApplicationContext context = application.run(args);
        context.start();
        TestBean testBean = context.getBean(TestBean.class);
        System.out.printf("***** Usage Step \n name is %s, hashcode is %s \n", testBean.name, testBean.hashCode());
      }
    }
    
    class AppConfig {
    
      @Bean
      public TestBean test() {
        TestChildBean childBean = new TestChildBean();
        childBean.name = "testChild";
        TestBean testBean = new TestBean(childBean);
        testBean.name = "test";
        return testBean;
      }
    
    }
    
    class TestBean {
      public String name;
      public TestChildBean childBean;
    
      public TestBean () {
        System.out.printf("***** Construct Step \n hashcode is %s \n", this.hashCode());
      }
    
      public TestBean (TestChildBean childBean) {
        this.childBean = childBean;
        System.out.printf("***** Construct Step \n hashcode is %s, childName is %s \n", this.hashCode(), this.childBean.name);
      }
    
      @PostConstruct
      public void init() {
        System.out.printf("***** PostConstruct Step \n hashcode is %s, childName is %s \n", this.hashCode(), this.childBean.name);
      }
    
      @PreDestroy
      public void destroy() {
        System.out.printf("***** PreDestroy Step \n hashcode is %s \n", this.hashCode());
      }
    }
    
    class TestChildBean {
      public String name;
    }

     
    <실행 결과>

    ***** Construct Step 
     hashcode is 1514214932, childName is testChild 
    ***** PostConstruct Step 
     hashcode is 1514214932, childName is testChild 
    ***** Usage Step 
     name is test, hashcode is 1514214932 
    ***** PreDestroy Step 
     hashcode is 1514214932

     

    Bean Scope.

    Bean 의 Scope 에 대해서 간단히 알아보겠습니다.
    크게 Singleton 과 Prototype 으로 나뉩니다.
    Singleton Bean 은 IoC Container 내에서 단 하나로 관리됩니다.
    Protytype Bean 은 getBean 인터페이스로 IoC Container 에 요청할 때마다 새로운 Bean 을 생성합니다.
     
    Prototype Bean 은 Stateful 한 Bean 을 생성하는 목적으로 사용됩니다.
    예를 들어,
    Client 와의 Network Connection 같이 짧은 생명주기를 가지면서 여러 개가 필요한 케이스가 Stateful Bean 의 예입니다.
     
    아래의 예시처럼 Client 수 만큼 새로운 Session 이 생성되어야하며
    Session 은 Bean 으로써 관리한다면 아래와 같은 구조가 됩니다.
    이러한 Bean 은 Prototype Bean 으로 관리합니다.

    Client 1 -> Session Bean 1
    Client 2 -> Session Bean 2
    Client 3 -> Session Bean 3
    Client 4 -> Session Bean 4
    Client 5 -> Session Bean 5




    그리고 Prototype Bean 은 다른 Bean 이나 Java 환경에 의해서 공유되는 자원이 아니기 때문에 Thread-Safe 합니다.
    반면 Singleton Bean 의 경우에는 Thread-Safety 에 유의해야합니다.
     
    아래 예시는 Prototype Scope Bean 을 생성하는 예시입니다.

    package org.example;
    
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.context.ConfigurableApplicationContext;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Scope;
    
    import javax.annotation.PostConstruct;
    import javax.annotation.PreDestroy;
    
    @SpringBootApplication
    public class Main {
      public static void main(String[] args) {
        SpringApplication application = new SpringApplication(AppConfig.class);
        ConfigurableApplicationContext context = application.run(args);
        context.start();
        TestBean testBean = context.getBean(TestBean.class);
        TestBean testBean2 = context.getBean(TestBean.class);
        TestBean testBean3 = context.getBean(TestBean.class);
        System.out.printf("***** Usage Step \n name is %s, hashcode is %s \n", testBean.name, testBean.hashCode());
        System.out.printf("***** Usage Step \n name is %s, hashcode is %s \n", testBean2.name, testBean2.hashCode());
        System.out.printf("***** Usage Step \n name is %s, hashcode is %s \n", testBean3.name, testBean3.hashCode());
      }
    }
    
    class AppConfig {
    
      @Bean
      @Scope("prototype")
      public TestBean test() {
        TestChildBean childBean = new TestChildBean();
        childBean.name = "testChild";
        TestBean testBean = new TestBean(childBean);
        testBean.name = "test";
        return testBean;
      }
    
    }
    
    class TestBean {
      public String name;
      public TestChildBean childBean;
    
      public TestBean () {
        System.out.printf("***** Construct Step \n hashcode is %s \n", this.hashCode());
      }
    
      public TestBean (TestChildBean childBean) {
        this.childBean = childBean;
        System.out.printf("***** Construct Step \n hashcode is %s, childName is %s \n", this.hashCode(), this.childBean.name);
      }
    
      @PostConstruct
      public void init() {
        System.out.printf("***** PostConstruct Step \n hashcode is %s, childName is %s \n", this.hashCode(), this.childBean.name);
      }
    
      @PreDestroy
      public void destroy() {
        System.out.printf("***** PreDestroy Step \n hashcode is %s \n", this.hashCode());
      }
    }
    
    class TestChildBean {
      public String name;
    }

     
    <실행 결과>
    각각의 bean 이 서로 다른 hashcode 를 가지며,
    bean 갯수만큼 생성자가 호출됨을 알 수 있습니다.

    ***** Construct Step 
     hashcode is 1770070706, childName is testChild 
    ***** PostConstruct Step 
     hashcode is 1770070706, childName is testChild 
    ***** Construct Step 
     hashcode is 977160959, childName is testChild 
    ***** PostConstruct Step 
     hashcode is 977160959, childName is testChild 
    ***** Construct Step 
     hashcode is 1563053805, childName is testChild 
    ***** PostConstruct Step 
     hashcode is 1563053805, childName is testChild 
    ***** Usage Step 
     name is test, hashcode is 1770070706 
    ***** Usage Step 
     name is test, hashcode is 977160959 
    ***** Usage Step 
     name is test, hashcode is 1563053805

     
    반면 singleton 의 경우에는
     

    package org.example;
    
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.context.ConfigurableApplicationContext;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Scope;
    
    import javax.annotation.PostConstruct;
    import javax.annotation.PreDestroy;
    
    @SpringBootApplication
    public class Main {
      public static void main(String[] args) {
        SpringApplication application = new SpringApplication(AppConfig.class);
        ConfigurableApplicationContext context = application.run(args);
        context.start();
        TestBean testBean = context.getBean(TestBean.class);
        TestBean testBean2 = context.getBean(TestBean.class);
        TestBean testBean3 = context.getBean(TestBean.class);
        System.out.printf("***** Usage Step \n name is %s, hashcode is %s \n", testBean.name, testBean.hashCode());
        System.out.printf("***** Usage Step \n name is %s, hashcode is %s \n", testBean2.name, testBean2.hashCode());
        System.out.printf("***** Usage Step \n name is %s, hashcode is %s \n", testBean3.name, testBean3.hashCode());
      }
    }
    
    class AppConfig {
    
      @Bean
      @Scope("singleton")
      public TestBean test() {
        TestChildBean childBean = new TestChildBean();
        childBean.name = "testChild";
        TestBean testBean = new TestBean(childBean);
        testBean.name = "test";
        return testBean;
      }
    
    }
    
    class TestBean {
      public String name;
      public TestChildBean childBean;
    
      public TestBean () {
        System.out.printf("***** Construct Step \n hashcode is %s \n", this.hashCode());
      }
    
      public TestBean (TestChildBean childBean) {
        this.childBean = childBean;
        System.out.printf("***** Construct Step \n hashcode is %s, childName is %s \n", this.hashCode(), this.childBean.name);
      }
    
      @PostConstruct
      public void init() {
        System.out.printf("***** PostConstruct Step \n hashcode is %s, childName is %s \n", this.hashCode(), this.childBean.name);
      }
    
      @PreDestroy
      public void destroy() {
        System.out.printf("***** PreDestroy Step \n hashcode is %s \n", this.hashCode());
      }
    }
    
    class TestChildBean {
      public String name;
    }

     
    <실행 결과>
    singleton 의 경우에는 하나의 Bean 이 공유됩니다.

    ***** Usage Step 
     name is test, hashcode is 1384563514 
    ***** Usage Step 
     name is test, hashcode is 1384563514 
    ***** Usage Step 
     name is test, hashcode is 1384563514 
    ***** PreDestroy Step 
     hashcode is 1384563514

    반응형

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

    Spring Batch Job 알아보기  (3) 2023.11.24
    Spring IoC Container 알아보기  (0) 2023.10.30
Designed by Tistory.