Spring Bean 生命周期
今天我们将研究 Spring Bean 的生命周期。Spring Bean是任何 Spring 应用程序中最重要的部分。Spring ApplicationContext负责初始化在 spring bean 配置文件中定义的 Spring Bean。
Spring Bean 生命周期
Spring Context 还负责通过 setter 或构造函数方法或通过spring 自动装配来注入bean 中的依赖项。有时我们希望初始化 bean 类中的资源,例如在任何客户端请求之前初始化时创建数据库连接或验证第三方服务。Spring 框架提供了不同的方法,通过这些方法我们可以在 spring bean 生命周期中提供初始化后和销毁前的方法。
- 通过实现InitializingBean和DisposableBean接口 - 这两个接口都声明了一个方法,我们可以通过该方法初始化/关闭 bean 中的资源。对于后初始化,我们可以实现
InitializingBean
接口并提供afterPropertiesSet()
方法的实现。对于预销毁,我们可以实现DisposableBean
接口并提供destroy()
方法的实现。这些方法是回调方法,类似于 servlet 侦听器实现。这种方法使用起来很简单,但不建议使用,因为它会在我们的 bean 实现中与 Spring 框架产生紧密耦合。 - 在 spring bean 配置文件中为 bean提供init-method和destroy-method属性值。这是推荐的方法,因为它不直接依赖于 spring 框架,我们可以创建自己的方法。
请注意,post-init和pre-destroy方法都不应带有参数,但它们可以抛出异常。我们还需要从 spring 应用程序上下文中获取 bean 实例以调用这些方法。
Spring Bean 生命周期 - @PostConstruct、@PreDestroy注释
Spring 框架还支持@PostConstruct
和@PreDestroy
注释,用于定义 post-init 和 pre-destroy 方法。这些注释是包的一部分javax.annotation
。但是,要使这些注释起作用,我们需要配置我们的 spring 应用程序以查找注释。我们可以通过在 spring bean 配置文件中定义类型的 beanorg.springframework.context.annotation.CommonAnnotationBeanPostProcessor
或context:annotation-config
元素来执行此操作。让我们编写一个简单的 Spring 应用程序来展示上述配置在 spring bean 生命周期管理中的使用。在 Spring Tool Suite 中创建一个 Spring Maven 项目,最终项目将如下图所示。
Spring Bean 生命周期 - Maven 依赖项
We don’t need to include any extra dependencies for configuring spring bean life cycle methods, our pom.xml file is like any other standard spring maven project.
<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
Let’s create a simple java bean class that will be used in service classes.
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 Life Cycle - InitializingBean, DisposableBean
Let’s create a service class where we will implement both the interfaces for post-init and pre-destroy methods.
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 Life Cycle - Custom post-init, pre-destroy
Since we don’t want our services to have direct spring framework dependency, let’s create another form of Employee Service class where we will have post-init and pre-destroy spring life cycle methods and we will configure them in the spring bean configuration file.
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");
}
}
}
We will look into the spring bean configuration file in a bit. Before that let’s create another service class that will use @PostConstruct and @PreDestroy annotations.
Spring Bean Life Cycle - @PostConstruct, @PreDestroy
Below is a simple class that will be configured as spring bean and for post-init and pre-destroy methods, we are using @PostConstruct and @PreDestroy annotations.
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
Let’s see how we will configure our beans in spring context file.
<?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>
Notice that I am not initializing employee name in it’s bean definition. Since EmployeeService is using interfaces, we don’t need any special configuration here. For MyEmployeeService bean, we are using init-method and destroy-method attributes to let spring framework know our custom methods to execute. MyService bean configuration doesn’t have anything special, but as you can see that I am enabling annotation based configuration for this. Our application is ready, let’s write a test program to see how different methods get executed.
Spring Bean Life Cycle - Test Program
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");
}
}
When we run above test program, we get below output.
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 Important Points:
- 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.
You can uncomment the code to get bean of type MyEmployeeService
and confirm that output will be similar and follow all the points mentioned above.
Spring Aware Interfaces
有时我们需要 bean 中的 Spring Framework 对象来执行某些操作,例如读取 ServletConfig 和 ServletContext 参数或了解 ApplicationContext 加载的 bean 定义。这就是为什么 spring 框架提供了一堆我们可以在 bean 类中实现的 *Aware 接口。org.springframework.beans.factory.Aware
是所有这些 Aware 接口的根标记接口。所有 *Aware 接口都是 Aware 的子接口,并声明一个要由 bean 实现的 setter 方法。然后 spring 上下文使用基于 setter 的依赖注入将相应的对象注入 bean 中并使其可供我们使用。Spring Aware 接口类似于具有回调方法和实现观察者设计模式的servlet 监听器。一些重要的 Aware 接口是:
- ApplicationContextAware - 注入 ApplicationContext 对象,示例用法是获取 bean 定义名称的数组。
- BeanFactoryAware - 注入 BeanFactory 对象,示例用法是检查 bean 的范围。
- BeanNameAware——知道配置文件中定义的 bean 名称。
- ResourceLoaderAware - 注入 ResourceLoader 对象,示例用法是获取类路径中文件的输入流。
- ServletContextAware - 在 MVC 应用程序中注入 ServletContext 对象,示例用法是读取上下文参数和属性。
- ServletConfigAware - 在 MVC 应用程序中注入 ServletConfig 对象,示例用法是获取 servlet 配置参数。
让我们在一个将配置为 spring bean 的类中实现其中几个 Aware 接口,看看它们的实际使用情况。
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 bean 配置文件。
<?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 Bean 生命周期方法以及将特定于框架的对象注入到 Spring Bean 中的全部内容。请从以下链接下载示例项目并进行分析以了解更多信息。
下载 Spring Bean Lifycycle 项目