一、简介
一个Bean,在进行实例化之后,需要进行两种初始化
- 初始化属性,由
PropertyValues
进行赋值 - 初始化方法,由
ApplicationContext
统一调用,例如加载配置文件
Bean的初始化与销毁,共有三种方式(注解、接口、XML),本章节,只实现接口和XML
@PostConstruct
和@PreDestroy
注解是比较推荐的方式。InitializingBean
和DisposableBean
是实现接口方式,比较少用。initMethod
和destroyMethod
适用于 XML 配置。
二、初始化方法
2.1 基于接口的实现
定义初始化接口
java
public interface InitializingBean {
/**
* Bean 处理了属性填充后调用
*
* @throws Exception
*/
void afterPropertiesSet();
}
定义销毁接口
java
public interface DisposableBean {
void destroy();
}
2.2 基于XML的实现
给BeanDefinition新增初始化和销毁属性
- 记录XML里面配置的初始化和销毁方法名称
java
@Data
public class BeanDefinition {
······
private String initMethodName;
private String destroyMethodName;
······
}
修改解析XML的逻辑
- 修改
XmlBeanDefinitionReader
类的doLoadBeanDefinitions
方法 - 增加对init-method、destroy-method标签的读取
- 并保存到BeanDefinition中
java
private void doLoadBeanDefinitions(InputStream inputStream) {
Document doc = XmlUtil.readXML(inputStream);
Element root = doc.getDocumentElement();
NodeList childNodes = root.getChildNodes();
for (int i = 0; i < childNodes.getLength(); i++) {
// 判断元素
if (!(childNodes.item(i) instanceof Element)) continue;
// 判断对象
if (!"bean".equals(childNodes.item(i).getNodeName())) continue;
// 解析标签
Element bean = (Element) childNodes.item(i);
String id = bean.getAttribute("id");
String name = bean.getAttribute("name");
String className = bean.getAttribute("class");
//增加对init-method、destroy-method的读取
String initMethod = bean.getAttribute("init-method");
String destroyMethodName = bean.getAttribute("destroy-method");
// 获取 Class,方便获取类中的名称
Class<?> clazz = null;
try {
clazz = Class.forName(className);
} catch (ClassNotFoundException e) {
throw new RuntimeException("不存在的类名" + className);
}
// 优先级 id > name,此处是Bean自己的id和name
String beanName = StrUtil.isNotEmpty(id) ? id : name;
if (StrUtil.isEmpty(beanName)) {
beanName = StrUtil.lowerFirst(clazz.getSimpleName());
}
// 定义Bean
BeanDefinition beanDefinition = new BeanDefinition(clazz);
//额外设置到beanDefinition中
beanDefinition.setInitMethodName(initMethod);
beanDefinition.setDestroyMethodName(destroyMethodName);
// 读取属性并填充
for (int j = 0; j < bean.getChildNodes().getLength(); j++) {
if (!(bean.getChildNodes().item(j) instanceof Element)) continue;
if (!"property".equals(bean.getChildNodes().item(j).getNodeName())) continue;
// 解析标签:property
Element property = (Element) bean.getChildNodes().item(j);
String attrName = property.getAttribute("name");
String attrValue = property.getAttribute("value");
String attrRef = property.getAttribute("ref");
// 获取属性值:引入对象、值对象
Object value = StrUtil.isNotEmpty(attrRef) ? new BeanReference(attrRef) : attrValue;
// 创建属性信息
PropertyValue propertyValue = new PropertyValue(attrName, value);
beanDefinition.getPropertyValues().addPropertyValue(propertyValue);
}
if (getRegistry().containsBeanDefinition(beanName)) {
throw new RuntimeException("Duplicate beanName[" + beanName + "] is not allowed");
}
// 注册 BeanDefinition
getRegistry().registerBeanDefinition(beanName, beanDefinition);
}
}
2.3 始化方法调用的时机
- 位于
AbstractAutowireCapableBeanFactory
类中
java
protected void invokeInitMethods(String beanName, Object bean, BeanDefinition beanDefinition) {
// 1.是否实现了InitializingBean接口
if (bean instanceof InitializingBean) {
((InitializingBean) bean).afterPropertiesSet();
}
// 2.是否xml中配置了
String initMethodName = beanDefinition.getInitMethodName();
if (StrUtil.isNotBlank(initMethodName)) {
try {
Method initMethod = beanDefinition.getBeanClass().getMethod(initMethodName);
initMethod.invoke(bean);
} catch (Exception e) {
throw new RuntimeException("Could not find an init method named '" + initMethodName + "' on bean with name '" + beanName + "'");
}
}
}
三、销毁方法
3.1 适配器模式实现销毁接口
由于销毁方法也有多种配置方式,接口、XML、注解,使用适配器模式将Bean包装,交给Spring调用
- 将实现了销毁方法的Bean,统一包装成
DisposableBeanAdapter
- destroy方法可能会调用两次,XML里面销毁方法配置成destroy,同时又实现DisposableBean接口,所以使用适配器模式重写了destroy方法,保证只调用一次
java
public class DisposableBeanAdapter implements DisposableBean {
private final Object bean;
private final String beanName;
private String destroyMethodName;
public DisposableBeanAdapter(Object bean, String beanName, BeanDefinition beanDefinition) {
this.bean = bean;
this.beanName = beanName;
this.destroyMethodName = beanDefinition.getDestroyMethodName();
}
@Override
public void destroy() {
// 1.实现接口 DisposableBean
if (bean instanceof DisposableBean) {
((DisposableBean) bean).destroy();
}
// 2.避免同时继承自DisposableBean,且自定义方法与DisposableBean方法同名,销毁方法执行两次的情况
if (StrUtil.isNotEmpty(destroyMethodName) && !(bean instanceof DisposableBean && "destroy".equals(this.destroyMethodName))) {
try {
Method destroyMethod = bean.getClass().getMethod(destroyMethodName);
destroyMethod.invoke(bean);
} catch (Exception e) {
throw new RuntimeException("Couldn't find a destroy method named '" + destroyMethodName + "' on bean with name '" + beanName + "'");
}
}
}
}
3.2 让DefaultSingletonBeanRegistry管理可销毁的Bean
给DefaultSingletonBeanRegistry
类,新增一个disposableBeans属性,保存可销毁的Bean
- 注意这里保存的是经过适配器模式包装的DisposableBean,重写了统一的destroy方法
- 这里实现了destroySingletons方法,这个方法由
ConfigurableBeanFactory
接口定义
java
public class DefaultSingletonBeanRegistry implements SingletonBeanRegistry {
......
private final Map<String, DisposableBean> disposableBeans = new HashMap<>();
......
public void registerDisposableBean(String beanName, DisposableBean bean) {
disposableBeans.put(beanName, bean);
}
public void destroySingletons() {
Set<String> beanNames = disposableBeans.keySet();
for (String beanName : beanNames) {
DisposableBean disposableBean = disposableBeans.get(beanName);
try {
disposableBean.destroy();
} catch (Exception e) {
throw new RuntimeException("Destroy method on bean with name '" + beanName + "' threw an exception", e);
}
}
disposableBeans.clear();
}
}
给ConfigurableBeanFactory
接口定义销毁Bean的方法
- 这个方法会在虚拟机关闭的统一调用
AbstractBeanFactory
实现了ConfigurableBeanFactory
接口,但具体实现却交给了父类DefaultSingletonBeanRegistry
,这是因为父类的功能就是管理单例Bean的,非常合理的设计(子类实现了接口,但具体的实现写在了父类)
java
public interface ConfigurableBeanFactory extends HierarchicalBeanFactory {
/**
* @param beanPostProcessor
*/
void addBeanPostProcessor(BeanPostProcessor beanPostProcessor);
/**
* 销毁单例bean
*/
void destroySingletons();
}
3.3 创建Bean的时候保存销毁方法
销毁方法会在BeanFactory关闭的时候调用,所以在Bean创建的时候,先进行保存
- 仍然是修改
AbstractAutowireCapableBeanFactory
类
java
@Override
protected Object createBean(String beanName, BeanDefinition beanDefinition, Object[] args) {
//实例化,包括构造函数注入
Object bean = doCreateBean(beanName, beanDefinition, args);
//依赖注入
populateBean(beanName, bean, beanDefinition);
//初始化
bean = initializeBean(beanName, bean, beanDefinition);
// 注册实现了 DisposableBean 接口的 Bean 对象
registerDisposableBeanIfNecessary(beanName, bean, beanDefinition);
//加入单例池
addSingleton(beanName, bean);
return bean;
}
protected void registerDisposableBeanIfNecessary(String beanName, Object bean, BeanDefinition beanDefinition) {
if (bean instanceof DisposableBean || StrUtil.isNotEmpty(beanDefinition.getDestroyMethodName())) {
registerDisposableBean(beanName, new DisposableBeanAdapter(bean, beanName, beanDefinition));
}
}
3.4 调用销毁Bean的方法
由于销毁bean会在虚拟机关闭的时候调用,先扩展一下ConfigurableApplicationContext
类
- 新增
registerShutdownHook
方法 - 新增
close
方法
java
public interface ConfigurableApplicationContext extends ApplicationContext {
void refresh();
void registerShutdownHook();
void close();
}
在AbstractApplicationContext
中实现对应的方法
- 虚拟机关闭的时候会调用注册到hook里面的方法
- 进而调用close方法
java
@Override
public void registerShutdownHook() {
Runtime.getRuntime().addShutdownHook(new Thread(this::close));
}
@Override
public void close() {
getBeanFactory().destroySingletons();
}
四、测试
Cat类
java
@Data
@ToString
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode
public class Cat {
private String name;
private int weight;
}
Person类
java
@Slf4j
@Data
@ToString
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode
public class Person {
private String name;
private int age;
private Cat cat;
public void initDataMethod(){
log.info("执行Person:init-method");
}
public void destroyDataMethod(){
log.info("执行Person:destroy-method");
}
}
spring.xml
java
<?xml version="1.0" encoding="UTF-8"?>
<beans>
<bean id="cat" class="cn.shopifymall.springframework.test.bean.Cat">
<property name="name" value="tomcat"/>
<property name="weight" value="2000"/>
</bean>
<bean id="person" class="cn.shopifymall.springframework.test.bean.Person" init-method="initDataMethod"
destroy-method="destroyDataMethod">
<property name="name" value="LeBron James"/>
<property name="age" value="18"/>
<property name="cat" ref="cat"/>
</bean>
</beans>
测试类
java
public class ApiTest {
@Test
public void test_xml() {
// 1.初始化 BeanFactory
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:spring.xml");
applicationContext.registerShutdownHook();
// 2. 获取Bean对象调用方法
Person person = (Person) applicationContext.getBean("person");
System.out.println("测试结果:" + person);
}
}
打印输出
- 记住要看这个测试类的日志,不是方法的日志,因为虚拟机运行结束的日志在测试类里
- 可以看到destroy-method打印
java
Connected to the target VM, address: '127.0.0.1:56254', transport: 'socket'
23:26:38.967 [main] INFO cn.shopifymall.springframework.test.bean.Person - 执行Person:init-method
测试结果:Person(name=LeBron James, age=18, cat=Cat(name=tomcat, weight=2000))
23:26:38.984 [Thread-0] INFO cn.shopifymall.springframework.test.bean.Person - 执行Person:destroy-method
Disconnected from the target VM, address: '127.0.0.1:56254', transport: 'socket'
Process finished with exit code 0