Spring Bean Life Cycle

今日はSpring Beanのライフサイクルについて見ていきます。Spring Beansは、Springアプリケーションの最も重要な部分です。Spring ApplicationContextは、spring beanの設定ファイルで定義されたSpring Beansを初期化する責任を持ちます。

Spring Beanのライフサイクル

Spring Contextは、Beanに対して依存関係を注入する責任も持っています。これは、setterメソッドやコンストラクタメソッドを介して行われることもありますし、Springのオートワイヤリングによって行われることもあります。Beanクラスでリソースを初期化したい場合もあります。たとえば、クライアントのリクエスト前にデータベース接続を作成したり、サードパーティのサービスを検証したりする場合です。Springフレームワークは、Spring Beanのライフサイクルでのポスト初期化メソッドやプリデストロイメソッドを提供するさまざまな方法を提供しています。

  1. InitializingBeanおよびDisposableBeanインターフェースを実装することで、これらのインターフェースはビーン内でリソースを初期化/クローズできる単一のメソッドを宣言しています。ポスト初期化では、InitializingBeanインターフェースを実装し、afterPropertiesSet()メソッドの実装を提供できます。プレデストロイでは、DisposableBeanインターフェースを実装し、destroy()メソッドの実装を提供できます。これらのメソッドはコールバックメソッドであり、サーブレットリスナーの実装と類似しています。このアプローチは使用が簡単ですが、ビーンの実装でSpringフレームワークとの強い結合を作成するため、お勧めできません。
  2. 春のビーン構成ファイルでビーンに対してinit-methodおよびdestroy-method属性値を提供することもできます。これは直接的なSpringフレームワークへの依存がないため、お勧めの方法です。また、独自のメソッドを作成できます。

post-initおよびpre-destroyメソッドは引数を持たない必要がありますが、例外をスローすることができます。これらのメソッドを呼び出すためにSpringアプリケーションコンテキストからビーンインスタンスを取得する必要があります。

Spring Bean Life Cycle – @PostConstruct@PreDestroyアノテーション

Springフレームワークは、@PostConstructおよび@PreDestroy注釈もサポートしており、ポスト初期化およびプレ破棄メソッドを定義するために使用されます。これらの注釈は、javax.annotationパッケージの一部です。ただし、これらの注釈が機能するには、Springアプリケーションを注釈の検索に設定する必要があります。これは、org.springframework.context.annotation.CommonAnnotationBeanPostProcessorタイプのビーンを定義するか、Springビーン構成ファイル内のcontext:annotation-config要素を使用して行うことができます。上記の設定を使用してSpringビーンのライフサイクル管理を示すために、簡単なSpringアプリケーションを作成しましょう。Spring Tool SuiteでSpring Mavenプロジェクトを作成し、最終的なプロジェクトは以下の画像のようになります。

Spring Bean Life Cycle – Maven Dependencies

Springビーンのライフサイクルメソッドを構成するための追加の依存関係を含める必要はありません。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 Life Cycle – Model Class

サービスクラスで使用されるシンプルな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 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

サービスに直接的なSpringフレームワークの依存関係を持たせたくないため、他の形式のEmployee Serviceクラスを作成し、post-initおよびpre-destroyのSpringライフサイクルメソッドを持たせ、それらを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

以下は、Springビーンとして構成される単純なクラスです。ポスト初期化とプリデストロイメソッドでは、@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");
	}
}

Spring Bean Life Cycle – Configuration File

Springコンテキストファイルでビーンをどのように構成するかを見てみましょう。

<?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属性を使用して、Springフレームワークにカスタムメソッドを実行する方法を通知しています。MyServiceビーンの構成には特に何もありませんが、このために注釈ベースの構成を有効にしていることがわかります。アプリケーションは準備ができています。異なるメソッドがどのように実行されるかを確認するために、テストプログラムを記述しましょう。

Spring Bean Life Cycle – テストプログラム

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 Life Cycle の重要なポイント:

  • コンソール出力から明らかなように、Springコンテキストは最初に引数なしのコンストラクタを使用してビーンオブジェクトを初期化し、その後にポスト初期化メソッドを呼び出します。
  • ビーンの初期化の順序は、Springビーン構成ファイルで定義されている順序と同じです。
  • コンテキストは、すべてのSpringビーンが正しくポスト初期化メソッドの実行で初期化されたときにのみ返されます。
  • 従業員の名前が「Pankaj」と印刷されるのは、それがポスト初期化メソッドで初期化されたためです。
  • コンテキストが閉じられると、ビーンは初期化された順序の逆の順序で破棄されます。つまり、LIFO(Last-In-First-Out)の順序で破棄されます。

MyEmployeeServiceのタイプのビーンを取得して、出力が類似しており、上記のすべてのポイントに従っていることを確認するために、コードのコメントを解除できます。

Spring Aware インタフェース

私たちは、時にはSpring Frameworkのオブジェクトが必要な場合があります。たとえば、ServletConfigやServletContextのパラメータを読み取るため、またはApplicationContextによってロードされたBeanの定義を知るためです。そのため、Spring Frameworkは私たちが私たちのビーンクラスで実装できるいくつかの*Awareインターフェースを提供しています。 org.springframework.beans.factory.Awareは、これらのすべてのAwareインターフェースのルートマーカーインターフェースです。*AwareインターフェースのすべてはAwareのサブインターフェースであり、ビーンによって実装される単一のセッターメソッドを宣言します。それからSpringコンテキストは、セッターベースの依存関係のインジェクションを使用して、ビーンに対応するオブジェクトをインジェクトし、私たちの使用のために利用可能にします。Spring Awareインターフェースは、コールバックメソッドを持つサーブレットリスナーオブザーバーデザインパターンを実装するものと似ています。重要なAwareインターフェースのいくつかは次のとおりです:

  • ApplicationContextAware – ApplicationContextオブジェクトをインジェクトするためのもので、例としてビーン定義名の配列を取得する使用方法があります。
  • BeanFactoryAware – BeanFactoryオブジェクトをインジェクトするためのもので、例としてビーンのスコープをチェックする使用方法があります。
  • BeanNameAware – 設定ファイルで定義されたビーン名を知るためのものです。
  • ResourceLoaderAware – ResourceLoaderオブジェクトをインジェクトするためのもので、例えばクラスパス内のファイルの入力ストリームを取得する場合に使用されます。
  • ServletContextAware – MVCアプリケーションでServletContextオブジェクトをインジェクトするためのもので、コンテキストパラメータや属性を読み取る場合に使用されます。
  • ServletConfigAware – MVCアプリケーションでServletConfigオブジェクトをインジェクトするためのもので、サーブレット構成パラメータを取得する場合に使用されます。

これらのAwareインターフェースの使用方法を見てみましょう。Springビーンとして構成されるクラスにそれらのいくつかを実装してみましょう。

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 Example Configuration File

非常にシンプルなSpringビーン構成ファイルです。

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

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ビーンのライフサイクルメソッドと、Springビーンにフレームワーク固有のオブジェクトをインジェクトする方法については以上です。サンプルプロジェクトを以下のリンクからダウンロードして、詳細を学んでください。

Spring Bean Lifecycle プロジェクトのダウンロード

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