SpringフレームワークにおけるBeanのライフサイクル

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

スプリングビーンのライフサイクル

Springコンテキストは、springのautowiringによる依存関係のインジェクションや、セッターやコンストラクターメソッドを通じたbeanへの依存関係の注入にも責任を持っています。時には、クライアントのリクエストがある前に、データベース接続の作成やサードパーティーサービスのバリデーションなど、beanクラス内でリソースを初期化したい場合があります。Springフレームワークでは、spring beanのライフサイクルでの事後初期化や事前破棄の方法を提供しています。

    1. InitializingBeanとDisposableBeanインターフェースを実装することにより、これらのインターフェースはビーン内のリソースを初期化/クローズする単一のメソッドを宣言します。ポスト初期化では、InitializingBeanインターフェースを実装し、afterPropertiesSet()メソッドの実装を提供することができます。プリデストロイでは、DisposableBeanインターフェースを実装し、destroy()メソッドの実装を提供することができます。これらのメソッドはコールバックメソッドであり、サーブレットリスナーの実装と類似しています。このアプローチは使いやすいですが、ビーンの実装にSpringフレームワークとの緊密な結合を作成するため、推奨されていません。

 

    spring beanの設定ファイルでビーンにinit-methodとdestroy-method属性値を提供する方法です。これは推奨されるアプローチです。直接Springフレームワークへの依存がなく、独自のメソッドを作成できます。

以下のように日本語で言い換えます:

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

スプリング・ビーンのライフサイクル – @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のライフサイクル – 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>

スプリングビーンのライフサイクル – モデルクラス

サービスクラスで使用するシンプルなJavaビーンクラスを作成しましょう。

package com.scdev.spring.bean;

public class Employee {

	private String name;

	public String getName() {
		return name;
	}

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

春のビーンライフサイクル – InitializingBean, DisposableBeanを日本語で言い換えると、次のようになります。
ビーンのライフサイクルに関するSpringの処理 – InitializingBean、DisposableBean

「post-init」と「pre-destroy」メソッドの両方のインターフェースを実装するサービスクラスを作成しましょう。

package com.scdev.spring.service;

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

import com.scdev.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 Frameworkの依存関係を持つことを望んでいないので、post-initとpre-destroyのSpringライフサイクルメソッドを持つ別の形式のEmployee Serviceクラスを作成しましょう。そして、それらをSpringのbean設定ファイルで設定します。

package com.scdev.spring.service;

import com.scdev.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");
		}
	}
}

少々ですが、春のBean設定ファイルを調査しましょう。その前に、@PostConstructと@PreDestroyアノテーションを使用する別のサービスクラスを作成しましょう。

スプリングビーンのライフサイクル – @PostConstruct、@PreDestroy

以下は、Spring Beanとして設定される単純なクラスです。このクラスでは、@PostConstructアノテーションと@PreDestroyアノテーションを使用して、ポスト初期化とプリデストロイメソッドを実装しています。

package com.scdev.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のコンテキストファイルでどのようにビーンを設定するかを見てみましょう。」

<?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.scdev.spring.bean.Employee" />

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

<bean name="myEmployeeService" class="com.scdev.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.scdev.spring.service.MyService" />
</beans>

従業員の名前をビーンの定義で初期化していないことに注意してください。EmployeeServiceはインターフェースを使用しているため、ここでは特別な設定は必要ありません。MyEmployeeServiceビーンでは、init-methodとdestroy-method属性を使用して、Springフレームワークに実行するカスタムメソッドを知らせています。MyServiceビーンの構成に特別なものはありませんが、注釈ベースの設定を有効にしていることがわかります。アプリケーションは準備ができていますので、さまざまなメソッドがどのように実行されるかをテストプログラムを作成して確認しましょう。

春のビーンライフサイクル – テストプログラム

package com.scdev.spring.main;

import org.springframework.context.support.ClassPathXmlApplicationContext;

import com.scdev.spring.service.EmployeeService;
import com.scdev.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

春のBeanライフサイクルの重要ポイント:

  • From the console output it’s clear that Spring Context is first using no-args constructor to initialize the bean object and then calling the post-init method.
  • The order of bean initialization is same as it’s defined in the spring bean configuration file.
  • The context is returned only when all the spring beans are initialized properly with post-init method executions.
  • Employee name is printed as “Pankaj” because it was initialized in the post-init method.
  • When context is getting closed, beans are destroyed in the reverse order in which they were initialized i.e in LIFO (Last-In-First-Out) order.

上記のポイントに従って、`MyEmployeeService` の種類のビーンを取得し、出力が同様であることを確認するために、コードのコメントを外すことができます。

Spring Aware インターフェース

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

  • ApplicationContextAware – to inject ApplicationContext object, example usage is to get the array of bean definition names.
  • BeanFactoryAware – to inject BeanFactory object, example usage is to check scope of a bean.
  • BeanNameAware – to know the bean name defined in the configuration file.
  • ResourceLoaderAware – to inject ResourceLoader object, example usage is to get the input stream for a file in the classpath.
  • ServletContextAware – to inject ServletContext object in MVC application, example usage is to read context parameters and attributes.
  • ServletConfigAware – to inject ServletConfig object in MVC application, example usage is to get servlet config parameters.

これらのAwareインターフェースの使用方法を確認しましょう。私たちは、それらをいくつかクラスに実装して、SpringのBeanとして設定します。

package com.scdev.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の例となる設定ファイル

非常にシンプルな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.scdev.spring.bean.Employee" />

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

</beans>

春のアウェアテストプログラム

package com.scdev.spring.main;

import org.springframework.context.support.ClassPathXmlApplicationContext;

import com.scdev.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のライフサイクルメソッドとframework特有のオブジェクトをSpring Beanに注入する方法に関する説明は以上です。詳細については、以下のリンクからサンプルプロジェクトをダウンロードして分析してください。

スプリングビーンライフサイクルプロジェクトをダウンロードしてください。

コメントを残す 0

Your email address will not be published. Required fields are marked *