카테고리 없음
Bean 생명주기
JABHACK
2025. 1. 18. 16:02
생명주기와 콜백
📌 콜백 메서드는 Spring Bean의 생명주기에서 특정 시점에 실행되도록 설정된 메서드로 초기화 혹은 종료 시점에 필요한 작업(리소스 초기화, 정리)을 처리할 때 주로 사용된다.
- Bean 생명주기
- Spring은 Bean의 생성과 관리, 소멸까지 자동으로 처리한다.
- 개발자가 직접 객체의 생명주기를 관리할 필요가 없다.
- Spring은 Bean의 생성과 관리, 소멸까지 자동으로 처리한다.
Spring Container 생성
- 애플리케이션이 실행되면 Spring은 ApplicationContext 또는 BeanFactory와 같은 컨테이너를 생성한다.
- 컨테이너는 @Configuration, @ComponentScan 또는 XML 파일 등의 설정을 통해 Bean 정의 정보를 읽는다.
Bean 인스턴스 생성
- 컨테이너는 필요한 Bean의 인스턴스를 생성합니다.
- 일반적으로 기본 생성자가 호출되어 객체가 생성되며, 이 과정에서 싱글톤 Bean은 애플리케이션이 시작할 때 미리 생성된다.
- 프로토타입 Bean은 요청 시점에 생성된다.
의존성 주입 (Dependency Injection, DI)
- 생성된 Bean에 의존하는 다른 Bean들이 있을 경우, Spring은 지정된 의존성 주입을 수행한다.
- 생성자 주입, setter 주입, 또는 필드 주입 방식이 사용될 수 있지만 주로 생성자 주입을 사용한다.
초기화 콜백
- @PostConstruct 또는 InitializingBean의 afterPropertiesSet() 메서드 호출
- 주로 데이터베이스 연결, 리소스 준비, 설정 작업과 같은 준비를 수행한다.
Bean 사용
- Bean이 초기화되고 의존성이 주입된 후에는 애플리케이션에서 자유롭게 Bean을 사용할 수 있다.
- 컨테이너는 애플리케이션의 요청에 따라 필요한 Bean을 제공하고, Bean들은 비즈니스 로직을 수행한다.
소멸전 콜백
- @PreDestroy 또는 DisposableBean의 destroy() 메서드 호출
- Application이 종료되거나 컨테이너가 종료되기 직전에 Spring은 Bean의 소멸 메서드를 호출하여 리소스를 정리한다.
- 주로 파일 닫기, 데이터베이스 연결 해제 등 리소스 정리를 위해 사용된다.
Spring 종료(Bean 소멸)
- Bean의 소멸 메서드가 호출된 후, Spring은 Bean을 메모리에서 제거한다.
- 이로써 해당 Bean은 더 이상 사용할 수 없으며, 모든 리소스가 정리된다.
생명주기 요약
- Spring Container 초기화
- Bean 인스턴스 생성
- 의존성 주입 (DI)
- 초기화 메서드 호출 (@PostConstruct, afterPropertiesSet())
- Bean 생성 및 의존관계 설정이 완료된 후 호출
- Bean 사용
- 소멸 메서드 호출 (@PreDestroy, destroy())
- Bean이 소멸되기 직전에 호출
- Bean 소멸
Spring의 생명주기 콜백 지원
- InitalizingBean, DisposableBean Interface
- @Bean 속성
- @PostConstruct, @PreDestroy Annotation
InitializingBean, DisposableBean
📌 Bean이 생성되고 모든 의존성이 주입된 후 InitializingBean의 afterPropertiesSet() 메서드가 호출되고 초기화 작업을 수행할 수 있다. 컨테이너가 종료될 때는 DisposableBean의 destroy() 메서드가 호출되며, 리소스 해제나 정리 작업을 처리할 수 있다.
- 인터페이스를 사용하여 콜백 메서드를 사용하는 방법은 최근에는 잘 사용하지 않는 방법이지만 전체적인 동작 흐름을 코드로 이해하기 위해 만든 예시입니다.
public class MyBean implements InitializingBean, DisposableBean {
private String data;
public MyBean() {
System.out.println("Bean 생성자 호출");
System.out.println("data = " + data);
}
public void setData(String data) {
this.data = data;
}
public String getData() {
return data;
}
// InitializingBean 인터페이스의 초기화 메서드
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("MyBean 초기화 - afterPropertiesSet() 호출됨");
System.out.println("data = " + data);
}
// DisposableBean 인터페이스의 종료 메서드
@Override
public void destroy() throws Exception {
System.out.println("MyBean 종료 - destroy() 호출됨");
data = null;
}
public void doSomething() {
System.out.println("MyBean 작업 중...");
}
}
@Configuration
public class AppConfig {
@Bean
public MyBean myBean() {
MyBean myBean = new MyBean();
// 의존관계 설정
myBean.setData("Example");
return myBean;
}
}
public class InterfaceCallback {
public static void main(String[] args) {
// Spring 컨테이너 생성 및 설정 클래스 등록
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
// Bean 사용
MyBean myBean = context.getBean(MyBean.class);
myBean.doSomething();
// 종료 되기전 호출
System.out.println(myBean.getData());
// 컨테이너 종료 (DisposableBean 호출됨)
context.close();
System.out.println(myBean.getData());
}
}
출력결과
// 1. Bean 생성자 호출
Bean 생성자 호출
data = null
// 2. 의존관계 설정 setData()
// 3. 초기화
MyBean 초기화 - afterPropertiesSet() 호출됨
// 4. Bean 사용
data = Example
MyBean 작업 중...
// 5. Spring 종료 전 Data 출력
Example
// 6. destroy() -> data = null
MyBean 종료 - destroy() 호출됨
// 7. close 이후 Data 출력
null
- 인터페이스 사용의 단점
- Spring에 의존적이다.
- 외부 라이브러리 등의 코드에 적용할 수 없다.
- Method의 이름을 바꿀 수 없다.(오버라이딩)
- Spring에 의존적이다.
@Bean 속성
📌 Spring에서 생명주기 콜백을 지원하는 방법으로 설정 정보에 초기화 메서드와 종료 메서드를 지정하는 방법을 사용할 수 있다. 이 방법을 선택하면 외부 라이브러리에도 콜백 메서드를 사용할 수 있다.
- 설정 정보에 초기화 메서드, 종료 메서드 지정
- *@Bean*(initMethod = "초기화 메서드명", destroyMethod = "소멸 메서드명")
public class MyBeanV2 {
private String data;
public MyBeanV2() {
System.out.println("Bean 생성자 호출");
System.out.println("data = " + data);
}
public void setData(String data) {
this.data = data;
}
public String getData() {
return data;
}
public void init() {
System.out.println("MyBean 초기화 - init() 호출됨");
System.out.println("data = " + data);
}
public void close() {
System.out.println("MyBean 종료 - close() 호출됨");
data = null;
}
public void doSomething() {
System.out.println("MyBean 작업 중...");
}
}
@Configuration
public class AppConfigV2 {
@Bean(initMethod = "init", destroyMethod = "close")
public MyBeanV2 myBeanV2() {
MyBeanV2 myBeanV2 = new MyBeanV2();
// 의존관계 설정
myBeanV2.setData("Example");
return myBeanV2;
}
}
public class InterfaceCallbackV2 {
public static void main(String[] args) {
// Spring 컨테이너 생성 및 설정 클래스 등록
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfigV2.class);
// Bean 사용
MyBeanV2 myBeanV2 = context.getBean(MyBeanV2.class);
myBeanV2.doSomething();
// Bean 소멸 전 필드
System.out.println(myBeanV2.getData());
// 컨테이너 종료 (destroy 호출)
context.close();
// Bean 소멸 후 필드
System.out.println(myBeanV2.getData());
}
}
실행결과
- 설정정보 사용
- Bean이 Spring 내부적으로 구현된 코드에 의존하지 않는다.
- 메서드 이름을 자유롭게 설정할 수 있다.
- 외부 라이브러리에도 초기화, 종료 메서드를 적용할 수 있다.
@PostConstruct, @PreDestroy
📌 Bean의 생명주기 동안 특정 작업을 실행하기 위해 사용하는 Annotation으로 각각 Spring Bean의 초기화와 소멸 시점에 호출되는 메서드를 지정할 때 사용된다.
public class MyBeanV3 {
private String data;
public MyBeanV3() {
System.out.println("Bean 생성자 호출");
System.out.println("data = " + data);
}
public void setData(String data) {
this.data = data;
}
public String getData() {
return data;
}
// 초기화 메서드
@PostConstruct
public void init() {
System.out.println("MyBean 초기화 - init() 호출됨");
System.out.println("data = " + data);
}
// 소멸 메서드
@PreDestroy
public void destroy() throws Exception {
System.out.println("MyBean 종료 - destroy() 호출됨");
data = null;
}
public void doSomething() {
System.out.println("MyBean 작업 중...");
}
}
@Configuration
public class AppConfigV3 {
@Bean
public MyBeanV3 myBeanV3() {
MyBeanV3 myBeanV3 = new MyBeanV3();
// 의존관계 설정
myBeanV3.setData("Example");
return myBeanV3;
}
}
public class InterfaceCallbackV3 {
public static void main(String[] args) {
// Spring 컨테이너 생성 및 설정 클래스 등록
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfigV3.class);
// Bean 사용
MyBeanV3 myBeanV3 = context.getBean(MyBeanV3.class);
myBeanV3.doSomething();
// Bean 소멸 전 필드
System.out.println(myBeanV3.getData());
// 컨테이너 종료 (destroy 호출)
context.close();
// Bean 소멸 후 필드
System.out.println(myBeanV3.getData());
}
}
실행결과
- 표준 Annotation 방식의 단점
- 외부 라이브러리에 적용이 불가능하다.
Bean Scope
📌 Spring 컨테이너에서 Bean이 어떻게 생성되고 관리되는지를 정의하는 개념으로 Spring은 다양한 범위(스코프)를 제공하여 Bean의 생명주기를 설정할 수 있는데, 각 스코프는 Bean이 얼마나 오래 유지되는지, 여러 번 사용할 수 있는지 등을 결정합니다.
Bean Scope 등록 방법
// 자동 등록
@Scope("singleton") // 생략 가능(기본 값)
@Component // @Service 사용 가능
public class MemberServiceImpl implements MemberService { ... }
// 수동 등록
@Configuration
public class AppConfig {
@Scope("singleton") // 생략 가능
@Bean
public MemberService memberService() {
return new MemberServiceImpl();
}
}
코드 예시
더보기
public class SingletonBean {
public SingletonBean() {
System.out.println("Bean 생성자 호출");
}
// 초기화 메서드
@PostConstruct
public void init() {
System.out.println("init() 호출");
}
// 소멸 메서드
@PreDestroy
public void destroy() throws Exception {
System.out.println("destroy() 호출");
}
public void doSomething() {
System.out.println("SingletonBean 작업 중...");
}
}
@Configuration
public class SingletonAppConfig {
@Scope("singleton")
@Bean
public SingletonBean singletonBean() {
SingletonBean singletonBean = new SingletonBean();
return singletonBean;
}
}
public class Singleton {
public static void main(String[] args) {
// Spring 컨테이너 생성 및 설정 클래스 등록
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(SingletonAppConfig.class);
// Bean 사용
SingletonBean singletonBean = context.getBean(SingletonBean.class);
SingletonBean singletonBean2 = context.getBean(SingletonBean.class);
System.out.println("singletonBean = " + singletonBean);
System.out.println("singletonBean2 = " + singletonBean2);
singletonBean.doSomething();
// 컨테이너 종료
context.close();
}
}
출력결과(정상 호출)
Bean 생성자 호출
init() 호출
singletonBean = com.example.springcallback.scope.SingletonBean@76ed1b7c
singletonBean2 = com.example.springcallback.scope.SingletonBean@76ed1b7c
SingletonBean 작업 중...
destroy() 호출
- 같은 Bean 객체가 조회된다.
- 초기화, 종료 메서드 정상 실행
프로토타입 스코프 사용
public class ProtoTypeBean {
public ProtoTypeBean() {
System.out.println("Bean 생성자 호출");
}
// 초기화 메서드
@PostConstruct
public void init() {
System.out.println("init() 호출");
}
// 소멸 메서드
@PreDestroy
public void destroy() throws Exception {
System.out.println("destroy() 호출");
}
public void doSomething() {
System.out.println("ProtoTypeBean 작업 중...");
}
}
@Configuration
public class ProtoTypeAppConfig {
@Scope("prototype")
@Bean
public ProtoTypeBean protoTypeBean() {
ProtoTypeBean protoTypeBean = new ProtoTypeBean();
return protoTypeBean;
}
}
public class ProtoType {
public static void main(String[] args) {
// Spring 컨테이너 생성 및 설정 클래스 등록
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ProtoTypeAppConfig.class);
// Bean 사용
System.out.println("protoTypeBean 조회");
ProtoTypeBean protoTypeBean = context.getBean(ProtoTypeBean.class);
System.out.println("protoTypeBean2 조회");
ProtoTypeBean protoTypeBean2 = context.getBean(ProtoTypeBean.class);
System.out.println("singletonBean = " + protoTypeBean);
System.out.println("singletonBean2 = " + protoTypeBean2);
protoTypeBean.doSomething();
// 컨테이너 종료
context.close();
}
}
출력결과
protoTypeBean 조회
Bean 생성자 호출
init() 호출
protoTypeBean2 조회
Bean 생성자 호출
init() 호출
singletonBean = com.example.springcallback.prototype.ProtoTypeBean@7c1e2a9e
singletonBean2 = com.example.springcallback.prototype.ProtoTypeBean@fa36558
ProtoTypeBean 작업 중...
- 조회할 때 Bean이 생성되고 초기화된다.
- close가 되어도 클라이언트가 직접 소멸하지 않아서 소멸되지 않는다.
- @PreDestroy 실행안됨
- 사용처
- Singleton(Default)
- 대부분의 Sevice, Repository 등 Application 전체에서 공유되는 Bean
- 상태를 가지면 안된다.
- Prototype
- 매번 새로운 인스턴스가 필요한 경우
- 상태를 가지는 객체(특정 설정값이 다른 임시 작업 객체)
- Request
- Web Application에서 요청별로 별도의 Bean이 필요한 경우
- 요청 데이터를 처리하는 객체
- Singleton(Default)