Reading Time: 8 minutes

Hôm nay, chúng ta sẽ tìm hiểu về vòng đời của Spring Bean. Spring Beans là phần quan trọng nhất trong bất kỳ ứng dụng Spring nào. ApplicationContext của Spring chịu trách nhiệm khởi tạo các Bean đã được định nghĩa trong tập tin cấu hình Spring.

Vòng đời của Spring Bean

Vòng đời của Spring Bean

Spring Context cũng chịu trách nhiệm tiêm (inject) các phụ thuộc (dependencies) vào bean, thông qua setter, constructor hoặc sử dụng spring autowiring. Đôi khi, chúng ta muốn khởi tạo một số tài nguyên trong class của bean, ví dụ như thiết lập kết nối cơ sở dữ liệu hoặc xác thực các dịch vụ bên thứ ba ngay khi khởi tạo trước khi có bất kỳ yêu cầu nào từ phía client. Spring framework cung cấp nhiều cách để khai báo các phương thức sau khởi tạo (post-initialization)trước hủy (pre-destroy) trong vòng đời của một bean.

  1. Bằng cách triển khai các interface InitializingBeanDisposableBean– Hai interface này đều chỉ định một phương thức duy nhất, nơi chúng ta có thể thực hiện các thao tác mở/tắt tài nguyên trong bean. Đối với phương thức sau khởi tạo, chúng ta triển khai InitializingBean và định nghĩa phương thức afterPropertiesSet(). Đối với trước khi hủy, chúng ta triển khai DisposableBean và định nghĩa phương thức destroy(). Đây là các phương thức callback, tương tự như các servlet listener. Cách tiếp cận này đơn giản nhưng không được khuyến khích vì nó khiến bean phụ thuộc chặt chẽ vào Spring framework.
  2. Khai báo thuộc tính init-methoddestroy-method trong tập tin cấu hình Spring. Đây là cách tiếp cận được khuyến khích hơn, vì không có sự phụ thuộc trực tiếp vào Spring framework và bạn có thể sử dụng các phương thức của riêng mình.

Lưu ý: Cả hai phương thức post-initpre-destroy không nên có tham số, nhưng vẫn có thể ném ra Exception. Ngoài ra, bạn cần lấy instance của bean từ Spring ApplicationContext để gọi các phương thức này.

Vòng đời của Spring Bean: @PostConstruct, @PreDestroy

Spring framework cũng hỗ trợ các annotation @PostConstruct@PreDestroy để định nghĩa các phương thức hậu khởi tạo và trước khi hủy. Các annotation này thuộc package javax.annotation. Tuy nhiên, để các annotation này hoạt động, chúng ta cần cấu hình ứng dụng Spring để tìm kiếm annotation. Chúng ta có thể làm điều này bằng cách khai báo một bean có kiểu org.springframework.context.annotation.CommonAnnotationBeanPostProcessor hoặc bằng phần tử context: annotation-config trong file cấu hình Spring Bean. Hãy viết một ứng dụng Spring đơn giản để trình bày cách sử dụng các cấu hình trên trong việc quản lý vòng đời của Spring Bean. Tạo một dự án Maven Spring trong Spring Tool Suite, dự án hoàn chỉnh sẽ trông giống như hình dưới đây.

Spring Bean life circle

Vòng đời Spring Bean – Các phụ thuộc Maven

Chúng ta không cần thêm bất kỳ phụ thuộc nào bổ sung để cấu hình các phương thức vòng đời của Spring Bean, file pom.xml của chúng ta giống như bất kỳ dự án Spring Maven tiêu chuẩn nào khác.

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

Model Class cho Spring Bean

Tạo một Java Bean đơn giản để được sử dụng trong các service class.

package com.journaldev.spring.bean;

public class Employee {

	private String name;

	public String getName() {
		return name;
	}

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

Vòng đời Spring Bean – InitializingBean, DisposableBean

Tạo một class service để triển khai cả hai interface trên cho các phương thức post-init và 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");
		}
	}
}

Vòng đời Spring Bean – Tùy chỉnh phương thức sau khởi tạo và trước hủy

Vì chúng ta không muốn các service của mình phụ thuộc trực tiếp vào Spring Framework, hãy tạo một phiên bản khác của lớp EmployeeService, trong đó chúng ta sẽ định nghĩa các phương thức vòng đời của Spring post-init và pre-destroy, và sẽ cấu hình chúng trong file cấu hình Spring Bean.

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");
		}
	}
}

Chúng ta sẽ xem xét file cấu hình Spring Bean sau một chút. Trước đó, hãy tạo một lớp service khác sử dụng các annotation @PostConstruct và @PreDestroy.

Vòng đời Spring Bean – @PostConstruct, @PreDestroy

Dưới đây là một lớp đơn giản sẽ được cấu hình như một Spring Bean, và cho các phương thức post-init và pre-destroy, chúng ta sử dụng các chú thích @PostConstruct và @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");
	}
}

Vòng đời Spring Bean – Tệp cấu hình

Hãy xem cách chúng ta cấu hình các bean trong file context của 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>

Lưu ý rằng tôi không khởi tạo tên nhân viên trong định nghĩa bean của nó. Vì EmployeeService đang sử dụng interface nên chúng ta không cần bất kỳ cấu hình đặc biệt nào ở đây. Với bean MyEmployeeService, chúng ta sử dụng thuộc tính init-method và destroy-method để cho Spring Framework biết các phương thức tùy chỉnh cần thực thi. Cấu hình bean MyService không có gì đặc biệt, nhưng như bạn thấy, tôi đang bật cấu hình dựa trên annotation cho nó. Ứng dụng của chúng ta đã sẵn sàng, hãy viết một chương trình kiểm thử để xem các phương thức khác nhau được thực thi như thế nào.

Vòng đời Spring Bean – Chương trình kiểm thử

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");
	}

}

Khi chạy chương trình test trên, bạn sẽ thấy kết quả sau.

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

Các điểm quan trọng trong vòng đời Spring Bean:

  • Từ kết quả hiển thị trên bảng điều khiển, có thể thấy rõ rằng Spring Context dùng constructor không tham số để tạo đối tượng bean, sau đó gọi phương thức post-init.
  • Thứ tự khởi tạo bean đúng theo thứ tự định nghĩa trong file cấu hình Spring.
  • Context chỉ được trả về sau khi tất cả các bean được khởi tạo đầy đủ, bao gồm thực thi các phương thức post-init.
  • Tên nhân viên được in ra là “Pankaj” vì nó được gán trong phương thức post-init.
  • Khi context bị đóng, các bean sẽ bị hủy theo thứ tự ngược lại với lúc khởi tạo — tức theo nguyên tắc LIFO (Last-In-First-Out).

Bạn có thể bỏ ghi chú dòng code tạo bean kiểu MyEmployeeService để kiểm chứng các điểm trên cũng áp dụng tương tự.

Spring Aware Interfaces

Đôi khi chúng ta cần các đối tượng của Spring Framework trong các bean để thực hiện một số thao tác, ví dụ như đọc các tham số ServletConfig và ServletContext hoặc để biết các định nghĩa bean được nạp bởi ApplicationContext. Đó là lý do Spring Framework cung cấp một loạt các interface dạng *Aware mà chúng ta có thể implement trong các lớp bean của mình. org.springframework.beans.factory.Aware là interface đánh dấu gốc cho tất cả các interface *Aware này. Tất cả các interface *Aware đều là các sub-interface của Aware và khai báo một phương thức setter duy nhất để bean implement. Sau đó, Spring context sử dụng dependency injection theo kiểu setter để tiêm các đối tượng tương ứng vào bean và làm cho chúng sẵn sàng cho chúng ta sử dụng. Các interface Aware của Spring tương tự như các servlet listener với các phương thức callback và thực hiện theo mẫu thiết kế observer. Một số interface Aware quan trọng gồm có:

  • ApplicationContextAware: để inject đối tượng ApplicationContext, ví dụ để lấy danh sách tên các bean.
  • BeanFactoryAware: để inject BeanFactory, ví dụ để kiểm tra phạm vi của bean.
  • BeanNameAware: để biết tên bean được định nghĩa trong tập tin cấu hình.
  • ResourceLoaderAware: để inject ResourceLoader, ví dụ để lấy stream của một file trong classpath.
  • ServletContextAware: để inject ServletContext trong ứng dụng MVC, ví dụ để đọc tham số và thuộc tính context.
  • ServletConfigAware: để inject ServletConfig trong ứng dụng MVC, ví dụ để lấy các tham số cấu hình servlet.Hãy cùng xem cách sử dụng các interface Aware này trong thực tế bằng cách implement một vài interface trong một lớp mà chúng ta sẽ cấu hình như một Spring Bean.
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");
	}

}

Ví dụ sử dụng Spring Aware – Tập tin cấu hình

Tập tin cấu hình Spring bean rất đơn giản.

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

Chương trình kiểm thử 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();
	}

}

Bây giờ khi chúng ta thực thi lớp ở trên, chúng ta sẽ nhận được kết quả sau.

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

Kết quả hiển thị trên bảng điều khiển của chương trình kiểm thử khá dễ hiểu, vì vậy tôi sẽ không đi vào chi tiết. Đó là tất cả về các phương thức vòng đời của Spring Bean và cách tiêm các đối tượng đặc thù của framework vào các Spring Bean. Vui lòng tải dự án mẫu từ liên kết bên dưới và phân tích để tìm hiểu thêm về chúng.

Tải xuống Dự án Vòng đời Spring Bean

0 Bình luận

Đăng nhập để thảo luận

Chuyên mục Hướng dẫn

Tổng hợp các bài viết hướng dẫn, nghiên cứu và phân tích chi tiết về kỹ thuật, các xu hướng công nghệ mới nhất dành cho lập trình viên.

Đăng ký nhận bản tin của chúng tôi

Hãy trở thành người nhận được các nội dung hữu ích của CyStack sớm nhất

Xem chính sách của chúng tôi Chính sách bảo mật.

Đăng ký nhận Newsletter

Nhận các nội dung hữu ích mới nhất