一、簡介
一個Bean,在進行實例化之后,需要進行兩種初始化
- 初始化屬性,由
PropertyValues
進行賦值 - 初始化方法,由
ApplicationContext
統一調用,例如加載配置文件
Bean的初始化與銷毀,共有三種方式(注解、接口、XML),本章節,只實現接口和XML
@PostConstruct
和@PreDestroy
注解是比較推薦的方式。InitializingBean
和DisposableBean
是實現接口方式,比較少用。initMethod
和destroyMethod
適用于 XML 配置。
二、初始化方法
2.1 基于接口的實現
定義初始化接口
public interface InitializingBean {/*** Bean 處理了屬性填充后調用** @throws Exception*/void afterPropertiesSet();
}
定義銷毀接口
public interface DisposableBean {void destroy();
}
2.2 基于XML的實現
給BeanDefinition新增初始化和銷毀屬性
- 記錄XML里面配置的初始化和銷毀方法名稱
@Data
public class BeanDefinition {······private String initMethodName;private String destroyMethodName;······
}
修改解析XML的邏輯
- 修改
XmlBeanDefinitionReader
類的doLoadBeanDefinitions
方法 - 增加對init-method、destroy-method標簽的讀取
- 并保存到BeanDefinition中
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和nameString beanName = StrUtil.isNotEmpty(id) ? id : name;if (StrUtil.isEmpty(beanName)) {beanName = StrUtil.lowerFirst(clazz.getSimpleName());}// 定義BeanBeanDefinition 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;// 解析標簽:propertyElement 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");}// 注冊 BeanDefinitiongetRegistry().registerBeanDefinition(beanName, beanDefinition);}}
2.3 始化方法調用的時機
- 位于
AbstractAutowireCapableBeanFactory
類中
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方法,保證只調用一次
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();}@Overridepublic void destroy() {// 1.實現接口 DisposableBeanif (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
接口定義
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的,非常合理的設計(子類實現了接口,但具體的實現寫在了父類)
public interface ConfigurableBeanFactory extends HierarchicalBeanFactory {/*** @param beanPostProcessor*/void addBeanPostProcessor(BeanPostProcessor beanPostProcessor);/*** 銷毀單例bean*/void destroySingletons();
}
3.3 創建Bean的時候保存銷毀方法
銷毀方法會在BeanFactory關閉的時候調用,所以在Bean創建的時候,先進行保存
- 仍然是修改
AbstractAutowireCapableBeanFactory
類
@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
方法
public interface ConfigurableApplicationContext extends ApplicationContext {void refresh();void registerShutdownHook();void close();
}
在AbstractApplicationContext
中實現對應的方法
- 虛擬機關閉的時候會調用注冊到hook里面的方法
- 進而調用close方法
@Override
public void registerShutdownHook() {Runtime.getRuntime().addShutdownHook(new Thread(this::close));
}@Override
public void close() {getBeanFactory().destroySingletons();
}
四、測試
Cat類
@Data
@ToString
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode
public class Cat {private String name;private int weight;
}
Person類
@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
<?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>
測試類
public class ApiTest {@Testpublic void test_xml() {// 1.初始化 BeanFactoryClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:spring.xml");applicationContext.registerShutdownHook();// 2. 獲取Bean對象調用方法Person person = (Person) applicationContext.getBean("person");System.out.println("測試結果:" + person);}
}
打印輸出
- 記住要看這個測試類的日志,不是方法的日志,因為虛擬機運行結束的日志在測試類里
- 可以看到destroy-method打印
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