`Spring Bean Life Cycle`

오늘은 Spring Bean Life Cycle을 살펴보겠습니다. Spring Beans은 모든 Spring 애플리케이션의 가장 중요한 부분입니다. Spring ApplicationContext는 spring bean 구성 파일에 정의된 Spring Beans를 초기화하는 역할을 담당합니다.

Spring Bean Life Cycle

Spring Context는 빈에 대한 의존성을 setter 또는 생성자 메서드를 통해 주입하거나 spring autowiring을 통해 처리하는 역할도 담당합니다. 때로는 클라이언트 요청 전에 초기화 시간에 데이터베이스 연결 생성 또는 타사 서비스 유효성 검사와 같은 작업을 빈 클래스에서 수행하고 싶을 때가 있습니다. Spring 프레임워크는 spring bean life cycle에서 post-initialization 및 pre-destroy 메서드를 제공하기 위해 다양한 방법을 제공합니다.

  1. InitializingBeanDisposableBean 인터페이스를 구현함으로써 – 이 두 인터페이스는 빈에서 리소스를 초기화/닫을 수 있는 단일 메서드를 선언합니다. 후 초기화를 위해 InitializingBean 인터페이스를 구현하고 afterPropertiesSet() 메서드의 구현을 제공할 수 있습니다. 전 소멸을 위해 DisposableBean 인터페이스를 구현하고 destroy() 메서드의 구현을 제공할 수 있습니다. 이러한 메서드는 콜백 메서드이며 서블릿 리스너 구현과 유사합니다. 이 접근 방식은 사용하기 쉽지만 빈 구현에 Spring 프레임워크와 강한 결합이 생성되므로 권장되지 않습니다.
  2. 스프링 빈 구성 파일에서 빈에 대한 init-methoddestroy-method 속성 값을 제공합니다. 이는 직접적인 스프링 프레임워크 종속성이 없기 때문에 권장되는 접근 방식이며 우리 자신의 메서드를 만들 수 있습니다.

또한 post-initpre-destroy 메서드는 인수가 없어야 하지만 예외를 던질 수 있습니다. 이러한 메서드 호출을 위해 스프링 애플리케이션 컨텍스트에서 빈 인스턴스를 가져와야 합니다.

스프링 빈 생명주기 – @PostConstruct, @PreDestroy 애노테이션

Spring 프레임워크는 또한 후처리 및 전처리 메서드를 정의하기 위한 @PostConstruct@PreDestroy 주석도 지원합니다. 이러한 주석들은 javax.annotation 패키지의 일부입니다. 그러나 이러한 주석이 작동하려면 스프링 애플리케이션을 주석으로 찾도록 구성해야 합니다. 이를 위해 org.springframework.context.annotation.CommonAnnotationBeanPostProcessor 유형의 빈을 정의하거나 스프링 빈 구성 파일에서 context:annotation-config 요소를 사용할 수 있습니다. 위의 구성을 사용하여 스프링 빈 라이프사이클 관리의 예를 보여주는 간단한 Spring 애플리케이션을 작성해 보겠습니다. Spring Tool Suite에서 Spring Maven 프로젝트를 생성하고 최종 프로젝트는 아래 이미지와 같을 것입니다.

Spring Bean 라이프사이클 – Maven 종속성

스프링 빈 라이프사이클 메서드를 구성하기 위해 추가적인 종속성을 포함할 필요는 없습니다. pom.xml 파일은 일반적인 Spring Maven 프로젝트와 같습니다.

<project xmlns="https://maven.apache.org/POM/4.0.0" xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="https://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>org.springframework.samples</groupId>
  <artifactId>SpringBeanLifeCycle</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  
  <properties>

		<!-- Generic properties -->
		<java.version>1.7</java.version>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
		<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>

		<!-- Spring -->
		<spring-framework.version>4.0.2.RELEASE</spring-framework.version>

		<!-- Logging -->
		<logback.version>1.0.13</logback.version>
		<slf4j.version>1.7.5</slf4j.version>

	</properties>
	
	<dependencies>
		<!-- Spring and Transactions -->
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-context</artifactId>
			<version>${spring-framework.version}</version>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-tx</artifactId>
			<version>${spring-framework.version}</version>
		</dependency>

		<!-- Logging with SLF4J & LogBack -->
		<dependency>
			<groupId>org.slf4j</groupId>
			<artifactId>slf4j-api</artifactId>
			<version>${slf4j.version}</version>
			<scope>compile</scope>
		</dependency>
		<dependency>
			<groupId>ch.qos.logback</groupId>
			<artifactId>logback-classic</artifactId>
			<version>${logback.version}</version>
			<scope>runtime</scope>
		</dependency>

	</dependencies>	
</project>

Spring Bean 라이프사이클 – 모델 클래스

서비스 클래스에서 사용할 간단한 Java 빈 클래스를 만들어 봅시다.

package com.journaldev.spring.bean;

public class Employee {

	private String name;

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}
	
}

Spring 빈 라이프 사이클 – InitializingBean, DisposableBean

post-init 및 pre-destroy 메서드를 구현할 인터페이스를 두 개 모두 구현할 서비스 클래스를 만들어 봅시다.

package com.journaldev.spring.service;

import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;

import com.journaldev.spring.bean.Employee;

public class EmployeeService implements InitializingBean, DisposableBean{

	private Employee employee;

	public Employee getEmployee() {
		return employee;
	}

	public void setEmployee(Employee employee) {
		this.employee = employee;
	}
	
	public EmployeeService(){
		System.out.println("EmployeeService no-args constructor called");
	}

	@Override
	public void destroy() throws Exception {
		System.out.println("EmployeeService Closing resources");
	}

	@Override
	public void afterPropertiesSet() throws Exception {
		System.out.println("EmployeeService initializing to dummy value");
		if(employee.getName() == null){
			employee.setName("Pankaj");
		}
	}
}

Spring 빈 라이프 사이클 – 사용자 정의 post-init, pre-destroy

서비스에 직접적인 Spring Framework 종속성을 가지지 않기 위해, post-init 및 pre-destroy Spring 라이프 사이클 메서드가 있는 Employee Service 클래스의 다른 형태를 만들어 보겠습니다. 이를 Spring 빈 구성 파일에서 구성할 것입니다.

package com.journaldev.spring.service;

import com.journaldev.spring.bean.Employee;

public class MyEmployeeService{

	private Employee employee;

	public Employee getEmployee() {
		return employee;
	}

	public void setEmployee(Employee employee) {
		this.employee = employee;
	}
	
	public MyEmployeeService(){
		System.out.println("MyEmployeeService no-args constructor called");
	}

	//pre-destroy 메서드
	public void destroy() throws Exception {
		System.out.println("MyEmployeeService Closing resources");
	}

	//post-init 메서드
	public void init() throws Exception {
		System.out.println("MyEmployeeService initializing to dummy value");
		if(employee.getName() == null){
			employee.setName("Pankaj");
		}
	}
}

이제 조금 후에 Spring 빈 구성 파일을 살펴보겠습니다. 그 전에, @PostConstruct 및 @PreDestroy 주석을 사용하는 다른 서비스 클래스를 만들어 봅시다.

Spring Bean Life Cycle – @PostConstruct, @PreDestroy

다음은 스프링 빈으로 구성된 간단한 클래스입니다. 포스트-초기화 및 프리-소멸 메서드에는 @PostConstruct@PreDestroy 주석을 사용합니다.

package com.journaldev.spring.service;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;

public class MyService {

	@PostConstruct
	public void init(){
		System.out.println("MyService init method called");
	}
	
	public MyService(){
		System.out.println("MyService no-args constructor called");
	}
	
	@PreDestroy
	public void destory(){
		System.out.println("MyService destroy method called");
	}
}

스프링 빈 라이프 사이클 – 구성 파일

스프링 컨텍스트 파일에서 빈을 구성하는 방법을 알아보겠습니다.

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="https://www.springframework.org/schema/beans"
	xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="https://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd">

<!-- Not initializing employee name variable-->
<bean name="employee" class="com.journaldev.spring.bean.Employee" />

<bean name="employeeService" class="com.journaldev.spring.service.EmployeeService">
	<property name="employee" ref="employee"></property>
</bean>

<bean name="myEmployeeService" class="com.journaldev.spring.service.MyEmployeeService"
		init-method="init" destroy-method="destroy">
	<property name="employee" ref="employee"></property>
</bean>

<!-- initializing CommonAnnotationBeanPostProcessor is same as context:annotation-config -->
<bean class="org.springframework.context.annotation.CommonAnnotationBeanPostProcessor" />
<bean name="myService" class="com.journaldev.spring.service.MyService" />
</beans>

빈 정의에서 직원 이름을 초기화하지 않았음에 유의하십시오. EmployeeService는 인터페이스를 사용하기 때문에 여기에서 특별한 구성이 필요하지 않습니다. MyEmployeeService 빈에는 init-method 및 destroy-method 속성을 사용하여 사용자 정의 메서드를 스프링 프레임워크가 실행할 수 있도록합니다. MyService 빈 구성에는 특별한 내용이 없지만, 주석 기반 구성을 사용하도록 설정한 것을 볼 수 있습니다. 우리의 응용 프로그램이 준비되었습니다. 다양한 메서드가 어떻게 실행되는지 확인하기 위해 테스트 프로그램을 작성해 봅시다.

스프링 빈 생명주기 – 테스트 프로그램

package com.journaldev.spring.main;

import org.springframework.context.support.ClassPathXmlApplicationContext;

import com.journaldev.spring.service.EmployeeService;
import com.journaldev.spring.service.MyEmployeeService;

public class SpringMain {

	public static void main(String[] args) {
		ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("spring.xml");

		System.out.println("Spring Context initialized");
		
		//MyEmployeeService service = ctx.getBean("myEmployeeService", MyEmployeeService.class);
		EmployeeService service = ctx.getBean("employeeService", EmployeeService.class);

		System.out.println("Bean retrieved from Spring Context");
		
		System.out.println("Employee Name="+service.getEmployee().getName());
		
		ctx.close();
		System.out.println("Spring Context Closed");
	}

}

위의 테스트 프로그램을 실행하면 다음과 같은 결과를 얻을 수 있습니다.

Apr 01, 2014 10:50:50 PM org.springframework.context.support.ClassPathXmlApplicationContext prepareRefresh
INFO: Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@c1b9b03: startup date [Tue Apr 01 22:50:50 PDT 2014]; root of context hierarchy
Apr 01, 2014 10:50:50 PM org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
INFO: Loading XML bean definitions from class path resource [spring.xml]
EmployeeService no-args constructor called
EmployeeService initializing to dummy value
MyEmployeeService no-args constructor called
MyEmployeeService initializing to dummy value
MyService no-args constructor called
MyService init method called
Spring Context initialized
Bean retrieved from Spring Context
Employee Name=Pankaj
Apr 01, 2014 10:50:50 PM org.springframework.context.support.ClassPathXmlApplicationContext doClose
INFO: Closing org.springframework.context.support.ClassPathXmlApplicationContext@c1b9b03: startup date [Tue Apr 01 22:50:50 PDT 2014]; root of context hierarchy
MyService destroy method called
MyEmployeeService Closing resources
EmployeeService Closing resources
Spring Context Closed

스프링 빈 생명주기의 중요한 포인트:

  • 콘솔 출력에서 알 수 있듯이, 스프링 컨텍스트는 빈 객체를 초기화하기 위해 먼저 인자가 없는 생성자를 사용하고, 그 후에 post-init 메소드를 호출합니다.
  • 빈 초기화의 순서는 스프링 빈 구성 파일에서 정의한 순서와 동일합니다.
  • 모든 스프링 빈이 post-init 메소드 실행으로 제대로 초기화된 경우에만 컨텍스트가 반환됩니다.
  • 직원 이름은 “Pankaj”로 인쇄되는데, 이는 post-init 메소드에서 초기화되었기 때문입니다.
  • 컨텍스트가 닫힐 때, 빈은 초기화된 순서와 반대로 제거됩니다. 즉, LIFO (Last-In-First-Out) 순서로 제거됩니다.

MyEmployeeService 유형의 빈을 얻기 위해 코드의 주석을 해제하고, 출력이 유사하며 위에서 언급한 모든 포인트를 따르는지 확인할 수 있습니다.

스프링 Aware 인터페이스

가끔은 우리가 작업을 수행하기 위해 빈에 Spring Framework 객체가 필요합니다. 예를 들어, ServletConfig 및 ServletContext 매개변수를 읽거나 ApplicationContext에 로드된 빈 정의를 알기 위해 필요한 경우가 있습니다. 이를 위해 Spring Framework는 빈 클래스에서 구현할 수 있는 여러 *Aware 인터페이스를 제공합니다. org.springframework.beans.factory.Aware는 이러한 Aware 인터페이스들의 루트 마커 인터페이스입니다. *Aware 인터페이스들은 모두 Aware의 하위 인터페이스이며 빈에서 구현해야 할 단일 setter 메서드를 선언합니다. 그런 다음 Spring 컨텍스트는 setter 기반 의존성 주입을 사용하여 해당 객체를 빈에 주입하고 우리가 사용할 수 있게 만듭니다. Spring Aware 인터페이스는 콜백 메서드가 있는 servlet 리스너옵저버 디자인 패턴을 구현하는 것과 유사합니다. 중요한 Aware 인터페이스 중 일부는 다음과 같습니다:

  • ApplicationContextAware – ApplicationContext 객체를 주입하기 위한 것으로, 예를 들어 빈 정의 이름의 배열을 가져오는 데 사용됩니다.
  • BeanFactoryAware – BeanFactory 객체를 주입하기 위한 것으로, 예를 들어 빈의 범위를 확인하는 데 사용됩니다.
  • BeanNameAware – 구성 파일에서 정의된 빈 이름을 알기 위한 것입니다.
  • ResourceLoaderAware – 리소스로더 객체를 주입하기 위한 것으로, 클래스 패스의 파일에 대한 입력 스트림을 얻는 예시 사용법입니다.
  • ServletContextAware – MVC 애플리케이션에서 ServletContext 객체를 주입하기 위한 것으로, 컨텍스트 매개변수와 속성을 읽는 예시 사용법입니다.
  • ServletConfigAware – MVC 애플리케이션에서 ServletConfig 객체를 주입하기 위한 것으로, 서블릿 구성 매개변수를 얻는 예시 사용법입니다.

이제 이러한 Aware 인터페이스의 사용법을 실제로 구현된 클래스에서 살펴보겠습니다. 이 클래스는 스프링 빈으로 구성할 것입니다.

package com.journaldev.spring.service;

import java.util.Arrays;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanClassLoaderAware;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.BeanNameAware;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.context.EnvironmentAware;
import org.springframework.context.ResourceLoaderAware;
import org.springframework.context.annotation.ImportAware;
import org.springframework.core.env.Environment;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.type.AnnotationMetadata;

public class MyAwareService implements ApplicationContextAware,
		ApplicationEventPublisherAware, BeanClassLoaderAware, BeanFactoryAware,
		BeanNameAware, EnvironmentAware, ImportAware, ResourceLoaderAware {

	@Override
	public void setApplicationContext(ApplicationContext ctx)
			throws BeansException {
		System.out.println("setApplicationContext called");
		System.out.println("setApplicationContext:: Bean Definition Names="
				+ Arrays.toString(ctx.getBeanDefinitionNames()));
	}

	@Override
	public void setBeanName(String beanName) {
		System.out.println("setBeanName called");
		System.out.println("setBeanName:: Bean Name defined in context="
				+ beanName);
	}

	@Override
	public void setBeanClassLoader(ClassLoader classLoader) {
		System.out.println("setBeanClassLoader called");
		System.out.println("setBeanClassLoader:: ClassLoader Name="
				+ classLoader.getClass().getName());
	}

	@Override
	public void setResourceLoader(ResourceLoader resourceLoader) {
		System.out.println("setResourceLoader called");
		Resource resource = resourceLoader.getResource("classpath:spring.xml");
		System.out.println("setResourceLoader:: Resource File Name="
				+ resource.getFilename());
	}

	@Override
	public void setImportMetadata(AnnotationMetadata annotationMetadata) {
		System.out.println("setImportMetadata called");
	}

	@Override
	public void setEnvironment(Environment env) {
		System.out.println("setEnvironment called");
	}

	@Override
	public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
		System.out.println("setBeanFactory called");
		System.out.println("setBeanFactory:: employee bean singleton="
				+ beanFactory.isSingleton("employee"));
	}

	@Override
	public void setApplicationEventPublisher(
			ApplicationEventPublisher applicationEventPublisher) {
		System.out.println("setApplicationEventPublisher called");
	}

}

스프링 *Aware 예제 구성 파일

아주 간단한 스프링 빈 구성 파일입니다.

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="https://www.springframework.org/schema/beans"
	xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="https://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd">

<bean name="employee" class="com.journaldev.spring.bean.Employee" />

<bean name="myAwareService" class="com.journaldev.spring.service.MyAwareService" />

</beans>

스프링 *Aware 테스트 프로그램

package com.journaldev.spring.main;

import org.springframework.context.support.ClassPathXmlApplicationContext;

import com.journaldev.spring.service.MyAwareService;

public class SpringAwareMain {

	public static void main(String[] args) {
		ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("spring-aware.xml");

		ctx.getBean("myAwareService", MyAwareService.class);
		
		ctx.close();
	}

}

이제 위의 클래스를 실행하면 다음과 같은 출력이 나옵니다.

Apr 01, 2014 11:27:05 PM org.springframework.context.support.ClassPathXmlApplicationContext prepareRefresh
INFO: Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@60a2f435: startup date [Tue Apr 01 23:27:05 PDT 2014]; root of context hierarchy
Apr 01, 2014 11:27:05 PM org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
INFO: Loading XML bean definitions from class path resource [spring-aware.xml]
setBeanName called
setBeanName:: Bean Name defined in context=myAwareService
setBeanClassLoader called
setBeanClassLoader:: ClassLoader Name=sun.misc.Launcher$AppClassLoader
setBeanFactory called
setBeanFactory:: employee bean singleton=true
setEnvironment called
setResourceLoader called
setResourceLoader:: Resource File Name=spring.xml
setApplicationEventPublisher called
setApplicationContext called
setApplicationContext:: Bean Definition Names=[employee, myAwareService]
Apr 01, 2014 11:27:05 PM org.springframework.context.support.ClassPathXmlApplicationContext doClose
INFO: Closing org.springframework.context.support.ClassPathXmlApplicationContext@60a2f435: startup date [Tue Apr 01 23:27:05 PDT 2014]; root of context hierarchy

테스트 프로그램의 콘솔 출력은 이해하기 쉽습니다. 그에 대해서는 자세히 설명하지 않겠습니다. 이로써 스프링 빈 라이프사이클 메서드와 스프링 빈에 프레임워크 특정 객체를 주입하는 방법에 대한 설명이 끝났습니다. 더 자세한 내용은 아래 링크에서 샘플 프로젝트를 다운로드하여 분석해보세요.

스프링 빈 라이프사이클 프로젝트 다운로드

Source:
https://www.digitalocean.com/community/tutorials/spring-bean-life-cycle