本篇文章,我们再来研究一下一道高频的面试题,就是spring所管理对象的生命周期。
一. 传统类的生命周期
在传统的java应用中,bean的生命周期很简单。使用Java关键字new进行bean的实例化,然后该bean就可以使用了。一旦bean不再被使用,则有java的垃圾回收器自动进行垃圾回收。
二. spring控制的对象的生命周期
相比之下,spring容器中的bean的生命周期就显得相对复杂多了。我们为什么要学习对象的生命周期呢,因为有了spring之后,现在都是由spring来控制对象的创建,存活和销毁,所以学习对象的生命周期,有利于我们更好的了解spring,使用spring.
我们本次对于对象的生命周期主要讲解三个阶段,分别是创建阶段,初始化阶段和销毁阶段。然后在最后,我们在给出一个spring容器中的对象完整的生命周期。
2.1 对象的创建阶段
在这个阶段,我们有必要回忆一个问题,就是spring工厂在什么时候创建的对象。这个我们在初始spring这个章节的注意细节中有提到,不知道大家有没有注意到。但是我们的案例:
代码语言:javascript复制<bean id="user" class="com.spring.User" />
复制代码
代码语言:javascript复制public static void main(String[]args){
//指定spring配置文件并创建工厂
ApplicationContext ctx = new ClassPathXmlApplicationContext("/applicationContext.xml");
// 根据配置文件中的id值获取对象
User user = (User)ctx.getBean("user");
}
复制代码
然后我们在注意细节中提到,user对象是什么时候创建的呢,其实是在spring工厂创建的时候,也就是当第一行代码,new ClassPathXmlApplicationContext("/applicationContext.xml");这行代码执行完毕的时候,User对象就已经创建出来了。但是要注意的是,并不是说所有的对象都是在工厂创建的同时完成创建,这其实是和对象的scope有关的。scope我们前面提到,有单例的和非单例的区别。当对象是单例的scope的时候,对象的创建是在工厂创建的同时完成创建的。但是如果对象的scope="prototype" 那么他的创建时机就是在获取对象的时候完成创建。这个大家一定要弄明白。也就是如果此时我再 注解上指定scope="prototype", 那么user对象就在执行getBean的时候才会创建出来。而对于单例的对象,如果我们也想让他在使用的时候再创建可以么,也是可以的,只需要在bean标签中加入lazy-init=true, 代表懒加载,那么这个单例的对象就会在获取的时候才被创建,要注意 lazy-init=true只对单例对象生效。我们可以通过在构造方法中打印一句话,来追踪对象的创建时机。
2.2 对象的初始化阶段
spring工厂在创建对象后,会调用对象的初始化方法,完成对应的初始化操作。 初始化方法的提供: 由程序员根据需要,提供初始化方法,最终完成初始化操作 初始化方法的调用: 由spring工厂完成调用。
也就相当于,如果我们想在spring创建好的对象,执行一些操作,spring为我们提供了一个钩子,我们通过这个钩子书写一段代码,spring工厂可以在创建好对象后帮我们执行这段代码,其实就是一种接口回调的思想,那么接下来我们来看一下,如何实现初始化操作。
方式一:实现InitializingBean接口,重写 void afterPropertiesSet() 方法。
通过方法名称我们就是,这个方法是在属性赋值完成后执行。也就是如果有依赖注入,先进行注入,再执行这个方法。好了我们在验证一下。
代码语言:javascript复制public class Product implements InitializingBean{
public Product(){
System.out.println("Product.product");
}
// 这就是初始化方法,做一些初始化操作,spring会调用
@Override
public void afterPropertiesSet() throws Exception{
System.out.println("Product.afterPropertiesSet");
}
}
复制代码
然后我们在spring配置文件中,配置Product类,测试了通过工厂获取对象(代码省略)。观察结果
代码语言:javascript复制Product.product
Product.afterPropertiesSet
复制代码
在对象创建之后,执行的该方法。
方式二. 如果有一些类无法实现或不想实现InitializingBean接口,但他们也想做一些初始化的操作怎么办呢,spring也为我们提供了一种方式。
那就是自己定义一个方法,在里边执行初始话的操作,然后指定该方法为初始化方法。
代码语言:javascript复制public class Product{
//自定义初始化方法
public void myInit(){
// 初始化操作
}
}
复制代码
代码语言:javascript复制<bean id="product" class="xxx.Product" init-method="myInit" />
复制代码
通过在bean标签的init-method属性指定自定义的初始化方法,也能实现初始化的效果。
注意事项:
- 如果一个对象既实现了InitializingBean,又提供了普通方法。 先执行接口的初始化方法,再执行普通的初始化方法。
- 如果有属性赋值注入,又有初始化方法,哪个先执行? 先执行注入操作,再执行初始化方法(afterPropertiesSet) 可通过方法名理解
- 什么叫做初始化操作 主要指对资源的初始化,数据库.....io......网络
2.3 对象的销毁阶段
spring销毁对象前, 会调用对象的销毁方法,完成销毁操作。那么我们就先需要了解几个点。 spring什么时候销毁所创建的对象 工厂关闭的时候,
ctx.close(); //注意该方法只在子类中有,所有不能用多态创建工厂
关于销毁方法: 和初始化方法一样,有程序员完成,由spring调用。如果我们在对象销毁前想执行一些操作,可以写这个里面
方式一. 实现Disposable接口,重写destroy方法。
代码语言:javascript复制public class Product implements InitializingBean,DisposableBean{
public Product(){
System.out.println("Product.product");
}
// 这就是初始化方法,做一些初始化操作,spring会调用
@Override
public void afterPropertiesSet() throws Exception{
System.out.println("Product.afterPropertiesSet");
}
// 销毁操作,就是资源的释放操作
@Override
public void destroy() throws Exception{
System.out.println("Product.destroy");
}
}
复制代码
测试类:
代码语言:javascript复制public class Test{
// close方法定义在子类中,使用多态无法调用
public static void main(String[] args){
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("xxx.xml");
Product p = (Product) ctx.getBean("product");
ctx.close();
}
}
复制代码
注意事项:
- 销毁方法只有在工厂调用close() 方法的时候才会调用
- close方法只有子类中,ApplicationContext中没有,所以不能使用多态调用。
方式二. bean标签指定销毁方法 自己定义销毁方法,配置文件中指定
代码语言:javascript复制public class Product implements InitializingBean,DisposableBean{
public Product(){
System.out.println("Product.product");
}
// 这就是初始化方法,做一些初始化操作,spring会调用
@Override
public void afterPropertiesSet() throws Exception{
System.out.println("Product.afterPropertiesSet");
}
// 销毁操作,就是资源的释放操作
@Override
public void destroy() throws Exception{
System.out.println("Product.destroy");
}
public void myDestroy() throws Exception(){
System.out.println("Product.myDestroy");
}
}
复制代码
代码语言:javascript复制<bean id="product" class="xxx.Product" init-method="myInit" destroy-method="myDestroy" />
复制代码
细节分析:
- 销毁方法的操作只作用于 scope="singleton" 的对象
- 销毁操作主要指资源的释放操作
- 执行顺序: 接口先于自定义
2.4 后置处理Bean
我们在前面提到了关于类的初始化的方式,除此之外,spring还提供了一个叫做BeanPostProcessor的接口,可以让我们对spring工厂创建的对象进行再加工。他底层的实现原理是通过AOP实现的。我们来看下这个接口。这个接口有两个方法需要实现。
postProcessBeforeInitialization: 该方法执行在初始化代码之前 postProcessAfterInitialization: 该方法执行在初始化代码之后
这里说的初始化方法指的就是我们前面提到的InitializingBean 接口中的afterPropertiesSet方法。
步骤:
- 实现BeanPostProcessor
- 重写两个方法: postProcessBeforeInitialization postProcessAfterInitialization
- postProcessBeforeInitialization: Spring创建完对象并注入后,可以运行该方法进行加工,通过返回值交给spring框架
- postProcessAfterInitialization:spring执行完初始化操作后,进行的加工。
- 配置文件配置
这里要特别注意下这两个需要实现的方法,跟前面初始化的方式不太一样,首先这两个方法是默认方法,不实现也不会报错,其次这两个方法有参数,有返回值。我们直接上代码。
- 开发一个类
@Data
public class Category{
private String name;
private int age;
}
复制代码
- 交给spring管理,并注入属性
<bean id="c" class="xxx.Category" >
<property name="name" value="张三" />
<property name="age" value="10" />
</bean>
复制代码
- 创建一个bean的加工工厂,也就是BeanPostProcessor的实现类
public class MyBeanPostProcessor implements BeanPostProcessor{
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException{
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException{
if(bean instanceof Category) {
Category c = (Category)bean;
c.setName("李四");
}
return bean;
}
}
复制代码
- 配置文件配置
<bean id="myBeanPostProcessor" class = "com.xxx.MyBeanPostProcessor" />
复制代码
测试,获取c对象,打印name, 发现结果变为李四。
注意事项:
- BeanPostProcessor 是对spring工厂中的所有类都生效的,所以使用之前,最好使用 instanceof 做判断,对指定类型进行加工,避免出错。
- 由于是对所有类做操作,所以第一个参数 Object bean ,就代表被加工的类,spring工厂的的所有类,都会走这个方法,所以这个bean可能是User,也可能是Product,等等等。第二个参数就是类的名称。
- 加工之后,要把对象做返回操作,即使你什么都不处理,也要把bean返回。
- 一定要注意执行顺序。
2.5 ApplicationContextAware
这个接口也是spring容器中非常重要的一个接口,关于他的使用场景我们后面会有一个更详细的案例来进行解释。这是先给出一个简单的用法。 spring中提供了很多以Aware结尾的接口。比如BeanNameAware, BeanFactoryAware,EnvironmentAware, 以及我们今天要说的ApplicationContextAware.
Aware翻译过来是知道的,已感知的,意识到的意思。其实就是可以帮我们获取到单词前面的对象。那么我们来说下ApplicationContext, 这个是spring的应用上下文,也可以简单理解成spring的工厂,我们可以通过getBean的方式获取spring工厂所管理的bean ,所以相对而言,它是一个比较重量级的资源,我们不能频繁创建,只需要创建一次就好了。那么当我们创建好了之后,如果其他地方也想获得这个工厂应该怎么办呢,就可以实现这个ApplicationContextAware接口,在里面就能得到对应的这个工厂,而不需要重复创建。 我们来看用法:
代码语言:javascript复制public class UserServiceImpl implements UserService implements ApplicationContextAware{
private ApplicationContext ctx;
@Override
public void setApplicationContext(AppliactionContext application){
this.ctx = application;
}
@Override
public void register(User user){
System.out.println("registe----")
// 调用的是原始对象的login方法,---核心功能,切面功能不执行
// 设计目的是: 调用代理对象的login方法
this.login("abc", "123456");
//获取代理对象
UserService userService = (UserService)ctx.getBean("userService");
userService.login("abc", "123456");
}
@Override
public boolean login(String name, String password){
System.out.println("login-----")
}
}
复制代码
实现这个接口,需要实现setApplicationContext方法,里边的参数就是当前的工厂,我们把它赋值给自己定义的同类型的成员变量就可以使用了。
同理,BeanNameAware接口,就是可以获得我们在配置文件配置的id值,也是通过重写setBeanName实现。
代码语言:javascript复制@Data
public class User implements BeanNameAware{
private String id;
private String name;
private String address;
public void setBeanName(String beanName) {
//ID保存BeanName的值,就是<bean>标签中的id值
id=beanName;
}
}
复制代码
三. 总结
好了,本篇文章我们大概完成了,希望大家一定好好理解,真的是干货满满,我也是弄了三天才搞出来。接写来我们就好总结spring bean的生命周期。
- 对象的实例化(相当于new了出来)
- 填充属性
- 调用BeanNameAware的setBeanName方法
- 调用BeanFactoryAware的setBeanFacotry方法
- 调用ApplicationContextAware的setApplicationContext方法
- 调用BeanPostProcessor的postProcessBeforeInitialization方法
- 调用InitializingBean的afterPropertySet方法
- 调用自定义的初始化方法(init-method = myInit)
- 调用BeanPostProcessor的postProcessAfterInitialization方法
- bean可以使用了
- 容器关闭时调用DisposableBean的destroy方法
- 调用自定义的销毁方法
注意: 上面的步骤主要是为了说明对象生命周期各个阶段的执行顺序,如果实现了对应接口就执行,没实现就不执行。 好了,本篇文章很重要,希望大家有所收获!!!
参考资料: Spring IN Action(第四版) 中国邮电出版社 孙帅spring详解:www.bilibili.com/video/BV185…