Продолжая обзор Spring, сегодня я взглянул на жизненный цикл Spring Bean.
1. Типичный жизненный цикл Spring
В традиционном Java-приложении жизненный цикл bean-компонента очень прост: используйте ключевое слово Java new для создания экземпляра bean-компонента, после чего можно использовать bean-компонент. Как только bean-компонент больше не используется, не так просто быть автоматически собранным мусором с помощью Java.
Напротив, Spring управляет жизненным циклом Bean-компонентов гораздо сложнее. Очень важно правильно понимать жизненный цикл Bean-компонентов, поскольку Spring управляет Bean-компонентами очень масштабируемо. Процесс построения Bean показан ниже.
Вышеприведенная картинка из книги "Весна в действии (четвертое издание)". На картинке описывается жизненный цикл классического Spring Bean. Объяснение в книге следующее:
1. Spring создает экземпляры bean-компонентов; 2. Spring вводит значения и ссылки на bean-компоненты в свойства, соответствующие bean-компоненту; 3. Если компонент реализует интерфейс BeanNameAware, Spring передает идентификатор компонента в метод setBean-Name(); 4. Если компонент реализует интерфейс BeanFactoryAware, Spring вызовет Используйте метод setBeanFactory() для передачи экземпляра контейнера BeanFactory; 5. Если компонент реализует интерфейс ApplicationContextAware, Spring вызовет Используйте метод setApplicationContext(), чтобы установить контекст приложения, в котором находится компонент. передается ссылка; 6. Если bean-компоненты реализуют интерфейс BeanPostProcessor, Spring вызовет их Метод post-ProcessBeforeInitialization(); 7. Если bean-компоненты реализуют интерфейс InitializingBean, Spring вызовет их после методаPropertiesSet(). Точно так же, если компонент использует init- method объявляет метод инициализации, который также будет вызываться; 8. Если bean-компоненты реализуют интерфейс BeanPostProcessor, Spring вызовет их Метод post-ProcessAfterInitialization(); 9. На этом этапе bean-компоненты готовы к использованию приложением, и они всегда будут Нахождение в контексте приложения до тех пор, пока контекст приложения не будет уничтожен; 10. Если компонент реализует интерфейс DisposableBean, Spring вызовет его метод интерфейса destroy(). Аналогично, если bean-компонент объявлен с помощью метода destroy Чтобы уничтожить метод, этот метод также будет вызван.
2. Проверьте цикл Spring Bean
После написания кода для проверки приведенного выше утверждения сначала создайте класс Person, который и является Bean, который мы хотим проверить.Для удобства тестирования он реализует BeanNameAware, BeanFactoryAware, ApplicationContextAware, InitializingBean, DisposableBean. код показывает, как показано ниже:
package com.nasus.bean;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.BeanNameAware;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.annotation.Scope;
/**
* Project Name:review_spring <br/>
* Package Name:PACKAGE_NAME <br/>
* Date:2019/9/1 16:29 <br/>
*
* @author <a href="turodog@foxmail.com">chenzy</a><br/>
*/
@Scope("ProtoType")
public class Person implements BeanNameAware, BeanFactoryAware,
ApplicationContextAware, InitializingBean, DisposableBean {
private static final Logger LOGGER = LoggerFactory.getLogger(Person.class);
private String name;
public Person(){
System.out.println("1、开始实例化 person ");
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
System.out.println("2、设置 name 属性");
}
@Override
public void setBeanName(String beanId) {
System.out.println("3、Person 实现了 BeanNameAware 接口,Spring 将 Person 的 "
+ "ID=" + beanId + "传递给 setBeanName 方法");
}
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
System.out.println("4、Person 实现了 BeanFactoryAware 接口,Spring 调"
+ "用 setBeanFactory()方法,将 BeanFactory 容器实例传入");
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
System.out.println("5、Person 实现了 ApplicationContextAware 接口,Spring 调"
+ "用 setApplicationContext()方法,将 person 所在的应用上下文的"
+ "引用传入进来");
}
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("8、Person 实现了 InitializingBean 接口,Spring 调用它的"
+ "afterPropertiesSet()方法。类似地,如果 person 使用 init-"
+ "method 声明了初始化方法,该方法也会被调用");
}
@Override
public void destroy() throws Exception {
System.out.println("13、Person 实现了 DisposableBean 接口,Spring 调用它的"
+ "destroy() 接口方法。同样,如果 person 使用 destroy-method 声明"
+ "了销毁方法,该方法也会被调用");
}
/**
* xml 中声明的 init-method 方法
*/
public void initMethod(){
System.out.println("9、xml 中声明的 init-method 方法");
}
/**
* xml 中声明的 destroy-method 方法
*/
public void destroyMethod(){
System.out.println("14、xml 中声明的 destroy-method 方法");
System.out.println("end---------------destroy-----------------");
}
// 自定义初始化方法
@PostConstruct
public void springPostConstruct(){
System.out.println("7、@PostConstruct 调用自定义的初始化方法");
}
// 自定义销毁方法
@PreDestroy
public void springPreDestory(){
System.out.println("12、@PreDestory 调用自定义销毁方法");
}
@Override
protected void finalize() throws Throwable {
System.out.println("finalize 方法");
}
}
Кроме того, создается класс MyBeanPostProcessor, наследуемый от BeanPostProcessor. Этот класс заботится только о том, что делать до и после инициализации Person. Например, перед инициализацией загрузите другие компоненты. код показывает, как показано ниже:
package com.nasus.lifecycle;
import com.nasus.bean.Person;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
/**
* Project Name:review_spring <br/>
* Package Name:PACKAGE_NAME <br/>
* Date:2019/9/1 16:25 <br/>
*
* @author <a href="turodog@foxmail.com">chenzy</a><br/>
*/
public class MyBeanPostProcessor implements BeanPostProcessor {
// 容器加载的时候会加载一些其他的 bean,会调用初始化前和初始化后方法
// 这次只关注 Person 的生命周期
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
if(bean instanceof Person){
System.out.println("6、初始化 Person 之前执行的方法");
}
return bean;
}
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if(bean instanceof Person){
System.out.println("10、初始化 Person 完成之后执行的方法");
}
return bean;
}
}
Создайте новый файл bean_lifecycle.xml в папке ресурсов для внедрения связанных компонентов, код выглядит следующим образом:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<!-- 扫描bean -->
<context:component-scan base-package="com.nasus"/>
<!-- 实现了用户自定义初始化和销毁方法 -->
<bean id="person" class="com.nasus.bean.Person" init-method="initMethod" destroy-method="destroyMethod">
<!-- 注入bean 属性名称 -->
<property name="name" value="nasus" />
</bean>
<!--引入自定义的BeanPostProcessor-->
<bean class="com.nasus.lifecycle.MyBeanPostProcessor"/>
</beans>
Протестируйте класс, получите bean-компонент person и используйте его, код выглядит следующим образом:
import com.nasus.bean.Person;
import java.awt.print.Book;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
* Project Name:review_spring <br/>
* Package Name:PACKAGE_NAME <br/>
* Date:2019/9/1 16:38 <br/>
*
* @author <a href="turodog@foxmail.com">chenzy</a><br/>
*/
public class lifeCycleTest {
@Test
public void testLifeCycle(){
// 为面试而准备的Bean生命周期加载过程
ApplicationContext context = new ClassPathXmlApplicationContext("bean_lifecycle.xml");
Person person = (Person)context.getBean("person");
// 使用属性
System.out.println("11、实例化完成使用属性:Person name = " + person.getName());
// 关闭容器
((ClassPathXmlApplicationContext) context).close();
}
}
Наконец, метод lifeCycleTest закрывает контейнер, и вывод журнала консоли выглядит следующим образом:
1、开始实例化 person
2、设置 name 属性
3、Person 实现了 BeanNameAware 接口,Spring 将 Person 的 ID=person传递给 setBeanName 方法
4、Person 实现了 BeanFactoryAware 接口,Spring 调用 setBeanFactory()方法,将 BeanFactory 容器实例传入
5、Person 实现了 ApplicationContextAware 接口,Spring 调用 setApplicationContext()方法,将 person 所在的应用上下文的引用传入进来
6、初始化 Person 之前执行的方法
7、@PostConstruct 调用自定义的初始化方法
8、Person 实现了 InitializingBean 接口,Spring 调用它的afterPropertiesSet()方法。类似地,如果 person 使用 init-method 声明了初始化方法,该方法也会被调用
9、xml 中声明的 init-method 方法
10、初始化 Person 完成之后执行的方法
11、实例化完成使用属性:Person name = nasus
12、@PreDestory 调用自定义销毁方法
13、Person 实现了 DisposableBean 接口,Spring 调用它的destroy() 接口方法。同样,如果 person 使用 destroy-method 声明了销毁方法,该方法也会被调用
14、xml 中声明的 destroy-method 方法
end---------------destroy-----------------
Из приведенных выше логов видно, что когда человек по умолчанию находится в режиме singleton, жизненный цикл бина совпадает с жизненным циклом контейнера, контейнер инициализируется, и бин тоже инициализируется. Контейнер уничтожается, и бин тоже уничтожается. Что, если компонент не является синглтоном?
3. После того, как создание экземпляра Bean завершено, сделайте что-нибудь перед уничтожением Иногда нам нужно что-то сделать после того, как значение свойства bean-компонента установлено и до того, как bean-компонент будет уничтожен, например, проверить, правильно ли установлено свойство в bean-компоненте. Spring Framework предоставляет множество методов, которые позволяют выполнять инициализацию и методы предварительного уничтожения в жизненном цикле Spring Bean. Я протестировал эти методы выше. Приведенный выше код реализует множество методов. Он повторяется. Во время разработки вы можете выбрать один из следующих, например:
- методы init-method и destroy-method, указанные в файле конфигурации
- Реализовать интерфейсы InitializingBean и DisposableBean.
- Используйте аннотации @PostConstruct и @PreDestroy (рекомендуется для трещин в стене)
4. Жизненный цикл компонента в многоэкземплярном режиме Человек в приведенном выше тесте по умолчанию является синглтоном.Теперь мы меняем человека в режим protoType, модифицируем bean_lifecycle.xml следующим образом и оставляем остальные классы без изменений:
<!-- 实现了用户自定义初始化和销毁方法 -->
<bean id="person" scope="prototype" class="com.nasus.bean.Person" init-method="initMethod" destroy-method="destroyMethod">
<!-- 注入bean 属性名称 -->
<property name="name" value="nasus" />
</bean>
Вывод журнала на данный момент выглядит следующим образом:
1、开始实例化 person
2、设置 name 属性
3、Person 实现了 BeanNameAware 接口,Spring 将 Person 的 ID=person传递给 setBeanName 方法
4、Person 实现了 BeanFactoryAware 接口,Spring 调用 setBeanFactory()方法,将 BeanFactory 容器实例传入
5、Person 实现了 ApplicationContextAware 接口,Spring 调用 setApplicationContext()方法,将 person 所在的应用上下文的引用传入进来
6、初始化 Person 之前执行的方法
7、@PostConstruct 调用自定义的初始化方法
8、Person 实现了 InitializingBean 接口,Spring 调用它的afterPropertiesSet()方法。类似地,如果 person 使用 init-method 声明了初始化方法,该方法也会被调用
9、xml 中声明的 init-method 方法
10、初始化 Person 完成之后执行的方法
11、实例化完成使用属性:Person name = nasus
В этот момент контейнер закрывается, а объект-человек не уничтожается. Причина в том, что в режиме единственного экземпляра жизненный цикл бина управляется контейнером, рождается контейнер, рождается бин, контейнер мертв, бин мертв. В многоэкземплярном режиме Spring не может так много управлять Жизненным циклом bean-компонентов управляет клиент, то есть программист или JVM.
5. Время загрузки бина в многоэкземплярном режиме
Прежде всего, давайте поговорим об одном экземпляре, в режиме одиночного экземпляра,Компонент создается в тот момент, когда контейнер загружает его., доказательство выглядит следующим образом: я включаю режим отладки, нажимаю точку останова в строке 20, и журнал выглядит следующим образом, указывая на то, что экземпляр bean-компонента был создан при инициализации контейнера в строке 19.
Кроме того, в многоэкземплярном режиме, в этом режиме бин инициализируется, когда бин нужно использовать.Доказательство следующее.После выполнения той же строки 19, в мультиэкземплярном режиме консоль пуста , указывая на то, что бин в это время не загружен.
Когда отладка достигает строки 23,Бин загружается только тогда, когда Бин нужен, проверено следующим образом. В процессе разработки мы называем этот тип загрузки отложенной загрузкой, и его цель — уменьшить накладные расходы программы и загружать ее только тогда, когда это необходимо, а не загружать все сразу.
6. Как bean-компонент с одним экземпляром реализует ленивую загрузку
Просто добавьте атрибут lazy-init в значение true в xml. Таким образом, его метод загрузки становится ленивой загрузкой.
<!-- 实现了用户自定义初始化和销毁方法 -->
<bean id="person" lazy-init="true" class="com.nasus.bean.Person" init-method="initMethod" destroy-method="destroyMethod">
<!-- 注入bean 属性名称 -->
<property name="name" value="nasus" />
</bean>
Если вы хотите применить ленивую инициализацию ко всем одноэлементным bean-компонентам по умолчанию, вы можете установить для свойства default-lazy-init значение true для корневых bean-компонентов следующим образом:
<beans default-lazy-init="true" …>
Рекомендуемое чтение: *1, *java | Что такое динамический прокси
*4, *SpringBoot | Принцип запуска
5, SpringBoot | Принцип автоматической настройки
*6, *Обзор Spring MVC | Принцип работы и детали конфигурации