웹사이트 검색

스프링 빈 라이프 사이클


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

스프링 빈 라이프 사이클

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

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

스프링 빈 수명 주기 - @PostConstruct, @PreDestroy 주석

Spring Bean 수명 주기 - Maven 종속성

우리는 spring bean 수명 주기 방법을 구성하기 위해 추가 의존성을 포함할 필요가 없습니다. 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 라이프사이클 - 모델 클래스

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

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 Bean 라이프 사이클 - 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 Bean 수명 주기 - 사용자 지정 사후 초기화, 사전 소멸

우리는 우리 서비스가 직접적인 스프링 프레임워크 의존성을 가지기를 원하지 않기 때문에 post-init 및 pre-destroy 스프링 라이프 사이클 메소드를 가질 다른 형태의 Employee Service 클래스를 생성하고 스프링 빈 구성 파일에서 구성할 것입니다.

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 method
	public void destroy() throws Exception {
		System.out.println("MyEmployeeService Closing resources");
	}

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

스프링 빈 구성 파일을 잠시 살펴보겠습니다. 그 전에 @PreDestroy 주석을 사용할 또 다른 서비스 클래스를 생성해 보겠습니다.

스프링 빈 라이프 사이클 - @PostConstruct, @PreDestroy

아래는 spring bean으로 구성될 간단한 클래스이며 post-init 및 pre-destroy 메서드에 대해 @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");
	}
}

Spring Bean 라이프사이클 - 구성 파일

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

<?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>

Bean 정의에서 직원 이름을 초기화하지 않는다는 점에 유의하십시오. EmployeeService는 인터페이스를 사용하므로 여기서는 특별한 구성이 필요하지 않습니다. MyEmployeeService 빈의 경우 init-method 및 destroy-method 속성을 사용하여 스프링 프레임워크에 실행할 사용자 지정 메서드를 알립니다. MyService 빈 구성에는 특별한 것이 없지만 보시다시피 이를 위해 주석 기반 구성을 활성화하고 있습니다. 애플리케이션이 준비되었습니다. 테스트 프로그램을 작성하여 다양한 메서드가 실행되는 방식을 살펴보겠습니다.

Spring Bean 라이프사이클 - 테스트 프로그램

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

Spring Bean 라이프사이클 중요 사항:

  • 콘솔 출력에서 Spring Context가 빈 객체를 초기화하기 위해 먼저 no-args 생성자를 사용한 다음 post-init 메소드를 호출하는 것이 분명합니다.
  • Bean 초기화 순서는 Spring Bean 구성 파일에 정의된 것과 동일합니다.
  • 컨텍스트는 post-init 메서드 실행으로 모든 스프링 빈이 제대로 초기화된 경우에만 반환됩니다.
  • 직원 이름은 post-init 메소드에서 초기화되었기 때문에 "Pankaj\로 출력됩니다.
  • 컨텍스트가 닫히면 Bean은 초기화된 순서의 역순, 즉 LIFO(후입선출) 순서로 파괴됩니다.

MyEmployeeService 유형의 bean을 얻기 위해 코드의 주석을 제거하고 출력이 유사하다는 것을 확인하고 위에서 언급한 모든 사항을 따를 수 있습니다.

스프링 인식 인터페이스

때로는 ServletConfig 및 ServletContext 매개변수를 읽거나 ApplicationContext에 의해 로드된 빈 정의를 아는 것과 같은 일부 작업을 수행하기 위해 빈에 Spring 프레임워크 개체가 필요합니다. 이것이 스프링 프레임워크가 빈 클래스에서 구현할 수 있는 많은 *Aware 인터페이스를 제공하는 이유입니다. org.springframework.beans.factory.Aware는 이러한 모든 Aware 인터페이스의 루트 마커 인터페이스입니다. 모든 *Aware 인터페이스는 Aware의 하위 인터페이스이며 bean에 의해 구현될 단일 setter 메소드를 선언합니다. 그런 다음 스프링 컨텍스트는 세터 기반 종속성 주입을 사용하여 bean에 해당 개체를 주입하고 사용할 수 있도록 합니다. Spring Aware 인터페이스는 관찰자 디자인 패턴과 유사합니다. 중요한 Aware 인터페이스 중 일부는 다음과 같습니다.

  • ApplicationContextAware - ApplicationContext 객체를 삽입하기 위해 사용 예는 bean 정의 이름의 배열을 얻는 것입니다.
  • BeanFactoryAware - BeanFactory 객체를 주입하기 위해 사용 예는 bean의 범위를 확인하는 것입니다.
  • BeanNameAware - 구성 파일에 정의된 bean 이름을 알기 위해.
  • ResourceLoaderAware - ResourceLoader 개체를 주입하기 위해 사용 예는 클래스 경로에서 파일에 대한 입력 스트림을 가져오는 것입니다.
  • 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");
	}

}

Spring *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>

Spring *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

테스트 프로그램의 콘솔 출력은 이해하기 쉬우므로 자세히 설명하지 않겠습니다. Spring Bean Life Cycle 메소드와 프레임워크 특정 객체를 Spring Bean에 주입하는 것이 전부입니다. 아래 링크에서 샘플 프로젝트를 다운로드하고 분석하여 자세히 알아보세요.

Spring Bean Lifycycle 프로젝트 다운로드