Жизненный цикл бина Spring

Сегодня мы рассмотрим жизненный цикл Spring Bean. Spring Beans являются самой важной частью любого приложения Spring. ApplicationContext Spring отвечает за инициализацию Spring Beans, определенных в файле конфигурации боба Spring.

Жизненный цикл Spring Bean

Контекст Spring также отвечает за внедрение зависимостей в боб, либо через методы установки, либо через конструкторные методы или с помощью автосвязывания Spring. Иногда мы хотим инициализировать ресурсы в классах бобов, например, создавать соединения с базой данных или проверять сторонние службы во время инициализации перед любым запросом клиента. Spring Framework предоставляет различные способы, с помощью которых мы можем предоставить методы после инициализации и перед уничтожением в жизненном цикле боба Spring.

  1. Реализуя интерфейсы InitializingBean и DisposableBean – Оба эти интерфейса объявляют единственный метод, где мы можем инициализировать/закрывать ресурсы в бине. Для пост-инициализации мы можем реализовать интерфейс InitializingBean и предоставить реализацию метода afterPropertiesSet(). Для предварительного уничтожения мы можем реализовать интерфейс DisposableBean и предоставить реализацию метода destroy(). Эти методы являются методами обратного вызова и аналогичны реализациям слушателя сервлетов. Этот подход прост в использовании, но не рекомендуется, потому что он создаст тесную связь с фреймворком Spring в наших реализациях бина.
  2. Предоставление значений атрибутов init-method и destroy-method для бина в файле конфигурации бинов Spring. Этот подход рекомендуется из-за отсутствия прямой зависимости от фреймворка Spring и возможности создания собственных методов.

Обратите внимание, что как пост-инициализационные, так и предварительные методы уничтожения не должны иметь аргументов, но они могут генерировать исключения. Также нам потребуется получить экземпляр бина из контекста приложения Spring для вызова этих методов.

Жизненный цикл бина Spring – @PostConstruct, @PreDestroy Аннотации

Фреймворк Spring также поддерживает аннотации @PostConstruct и @PreDestroy для определения методов после инициализации и перед уничтожением. Эти аннотации являются частью пакета javax.annotation. Однако для работы этих аннотаций необходимо сконфигурировать наше приложение Spring для поиска аннотаций. Мы можем сделать это либо, определив бин типа org.springframework.context.annotation.CommonAnnotationBeanPostProcessor, либо с помощью элемента context:annotation-config в файле конфигурации бина Spring. Давайте напишем простое приложение Spring, чтобы продемонстрировать использование вышеуказанных конфигураций для управления жизненным циклом бина Spring. Создайте проект Spring Maven в Spring Tool Suite, итоговый проект будет выглядеть как на изображении ниже.

Жизненный цикл бина Spring – Зависимости Maven

Нам не нужно добавлять какие-либо дополнительные зависимости для конфигурации методов жизненного цикла бина 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 – Класс модели

Давайте создадим простой класс Java 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 – InitializingBean, DisposableBean

Давайте создадим сервисный класс, в котором мы реализуем оба интерфейса для методов пост-инициализации и предварительного уничтожения.

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 – Пользовательские методы пост-инициализации, предварительного уничтожения

Поскольку мы не хотим, чтобы наши сервисы зависели от прямой зависимости от фреймворка Spring, давайте создадим другую форму класса Employee Service, где у нас будут методы жизненного цикла 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");
	}

	//метод предварительного уничтожения
	public void destroy() throws Exception {
		System.out.println("MyEmployeeService Closing resources");
	}

	//метод пост-инициализации
	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 – @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 – Файл конфигурации

Давайте посмотрим, как мы будем конфигурировать наши бобы в файле контекста 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 – Тестовая программа

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 сначала использует конструктор без аргументов для инициализации объекта бина, а затем вызывает метод post-init.
  • Порядок инициализации бинов такой же, как определено в конфигурационном файле бинов Spring.
  • Контекст возвращается только тогда, когда все бины Spring правильно инициализированы с выполнением методов post-init.
  • Имя сотрудника выводится как “Pankaj”, потому что оно было инициализировано в методе post-init.
  • При закрытии контекста бины уничтожаются в обратном порядке, в котором они были инициализированы, то есть в порядке LIFO (Last-In-First-Out).

Вы можете раскомментировать код, чтобы получить бин типа 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 – для внедрения объекта ApplicationContext, пример использования – получение массива имен определений бинов.
  • BeanFactoryAware – для внедрения объекта BeanFactory, пример использования – проверка области видимости бина.
  • BeanNameAware – для получения имени бина, определенного в конфигурационном файле.
  • ResourceLoaderAware – для внедрения объекта ResourceLoader, пример использования – получение входного потока для файла в classpath.
  • ServletContextAware – для внедрения объекта ServletContext в приложение MVC, пример использования – чтение параметров контекста и атрибутов.
  • ServletConfigAware – для внедрения объекта ServletConfig в приложение MVC, пример использования – получение параметров конфигурации сервлета.

Давайте рассмотрим использование этих интерфейсов 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

Очень простой файл конфигурации бина 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

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