Spring入门学习——自定义Bean初始化和析构

时间:2021-08-13 20:37:54
许多现实世界中的组件在使用之前必须进行某种初始化任务。这种任务包括打开文件、打开网络/数据库连接、分配内存等。在组件生命期结束时,也必须要执行相应的析构任务,所以,就有在Spring IoC容器中自定义Bean初始化和析构的需求。
除了Bean注册外,Spring IoC容器还负责管理Bean的生命周期,允许你在它们的生命周期特定时点执行自定义人物。你的人物应该封装在回调方法中,由Spring IoC容器在合适的时候调用。
下面是Spring IoC容器管理Bean周期的基本步骤: (1)构造程序或者工厂方法创建Bean实例 (2)向Bean属性设置值和Bean引用 (3)调用初始化回调方法 (4)Bean就绪 (5)容器关闭时,调用析构回调方法
Spring有三种识别初始化和析构回调方法的方式。第一,你的Bean可以实现InitializingBean和DisposalBean生命周期接口,并且实现用于初始化和析构的afterPropertiesSet()和destroy()方法。第二,你可以在Bean声明中设置init-method和destroy-method属性,指定回调方法名称。在Spring2.5或者更高版本中,你还可以用生命周期注解@PostConstruct和@PreDestroy注解初始化和析构回调方法,这两个注解在JSR-250《Java平台常用注解》中定义。然后可以在IoC容器中注册CommonAnnotationBeanPostProcessor实例来调用这些回调方法。
应用场景:为了理解Spring IoC容器管理Bean生命周期的方法,来使用一个设计结账功能的示例作为学习场景。
编写一个Cashier类,用于结账,代码里面涉及的商品类,购物车类在之前博客有。

package com.cgy.springrecipes.shop;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.util.Date;
/**
* 用于购物车中产品的结账
* 在一个文本文件中记录每次结账的时间和数量
*/
public class Cashier {

private String name;//收银员姓名
private String path;
private BufferedWriter writer;

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

public void setPath(String path) {
this.path = path;
}

public void openFile() throws IOException {

//目录应该预先创建或指定另一个现有的目录
File logFile = new File(path, name + ".txt");
writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(logFile, true)));
}

/**
* @param cart 购物车
* @throws IOException
*/
public void checkout(ShoppingCart cart) throws IOException {
double total = 0;
for (Product product : cart.getItems()) {
total += product.getPrice();
}
writer.write(new Date()+"\t"+total+"\r\n");
writer.flush();
}

public void closeFile() throws IOException {
writer.close();
}
}

配置文件内容如下,该配置文件声明了一个结账Bean,一个购物车Bean,两个商品Bean:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.springframework.org/schema/beans" xmlns:p="http://www.springframework.org/schema/p"
xmlns:util="http://www.springframework.org/schema/util" xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/util
http://www.springframework.org/schema/util/spring-util-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd">

<bean id="cashier1" class="com.cgy.springrecipes.shop.Cashier">
<property name="name" value="cgy" />
<property name="path" value="E:\kj\mySSM\temp" />
</bean>

<bean id="cart" class="com.cgy.springrecipes.shop.ShoppingCart" />

<bean id="battery" class="com.cgy.springrecipes.shop.Battery">
<property name="name" value="AAA" />
<property name="price" value="2.5" />
</bean>

<bean id="disc1" class="com.cgy.springrecipes.shop.Disc">
<property name="name" value="taylor" />
<property name="price" value="1.5" />
</bean>

</beans>

现在编写Main代码如下,往购物车添加商品,并使用名为cgy的收银员对该购物车结账:

package com.cgy.springrecipes.shop;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Main {
public static void main(String[] args) throws Exception {
ApplicationContext context = new ClassPathXmlApplicationContext("beans_two.xml");
Cashier cashier_cgy = (Cashier)context.getBean("cashier1");
Product battery = (Product) context.getBean("battery");
Product disc1 = (Product) context.getBean("disc1");
ShoppingCart cart = (ShoppingCart) context.getBean("cart");
// 购物车添加两样商品
cart.addItem(battery);
cart.addItem(disc1);
//收银员cgy使用checkouot()方法对购物车进行结账
cashier_cgy.checkout(cart);
}
}

运行该程序会出现报错,抛出异常java.lang.NullPointerException,因为Cashier类里面的openFile()方法没有预先调用进行初始化。这时候就应该使用Bean的初始化来解决该问题!首先应该调用openFile()这个方法,按照以往学习经验,应该会想在Cashier的构造函数来调用openFile()方法,但是想一想这样子势必需要一个接受两个参数的构造函数来获取name和path, ,因为name和path属性必须设置才能正确运行openFile()方法。实际上,调用openFile()方法的最佳时机是Spring IoC容器设置了所有属性之后
如果直接如下编写构造函数,name和path属性并没有被配置就调用了openFile

public Cashier() throws IOException {
openFile();
}

/*这样写构造函数是错误的,因为name和path属性是IoC容器实现注入的,在创建实例时(调用Cashier构造函数时)

这两个属性还未设置,而这也是高亮文字的原因!*/


解决方法(1)实现InitializingBean和DisposalBean生命周期接口 Cashier类修改代码如下:

public class Cashier implements InitializingBean,DisposableBean{

private String name;//收银员姓名
private String path;
private BufferedWriter writer;

public void destroy() throws Exception {
closeFile();
}

public void afterPropertiesSet() throws Exception {
openFile();
}


......
}

再次运行Main,将会在你指定的目录下有一个文本文件记录了本次购物车购买东西的情况。

Mon Apr 17 19:53:59 CST 20174.0


解决方法(2):设置init-method和destroy-method属性 修改cashier的bean配置,内容如下:

<bean id="cashier1" class="com.cgy.springrecipes.shop.Cashier"
init-method="openFile" destroy-method="closeFile">
<property name="name" value="cgy" />
<property name="path" value="E:\kj\mySSM\spring_one\temp" />
</bean>

对比第一种实现接口方法,简单很多。

解决方法(3):用生命周期注解@PostConstruct和@PreDestroy注解初始化和析构回调方法 maven项目可以在pom.xml中加入

<dependency>
<groupId>javax.annotation</groupId>
<artifactId>jsr250-api</artifactId>
<version>1.0</version>
</dependency>

Caisher进行一点修改,内容如下:

public class Cashier{

......

@PostConstruct
public void openFile() throws IOException {
//目录应该预先创建或指定另一个现有的目录
File logFile = new File(path, name + ".txt");
writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(logFile, true)));
}


@PreDestroy
public void closeFile() throws IOException {
writer.close();
}

......

}

就像之前博客介绍使用@Resource 自动装配Bean一样,需要在配置文件注册一个实例,配置文件添加

<context:annotation-config/>

第三种方法使用注解出去配置文件后更为简洁。