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
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) và trước hủy (pre-destroy) trong vòng đời của một bean.
- Bằng cách triển khai các interface InitializingBean và DisposableBean– 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ứcafterPropertiesSet()
. Đối với trước khi hủy, chúng ta triển khaiDisposableBean
và định nghĩa phương thứcdestroy()
. Đâ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. - Khai báo thuộc tính init-method và destroy-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-init và pre-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
và @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.
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