-
Spring Bean 알아보기Language/Spring 2023. 10. 30. 10:19728x90반응형
- 목차
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예시 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