Spring5系列(七) | spring对象的生命周期(全是干货不要错过)

2021-12-13 09:41:46 浏览数 (1)

本篇文章,我们再来研究一下一道高频的面试题,就是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属性指定自定义的初始化方法,也能实现初始化的效果。

注意事项:

  1. 如果一个对象既实现了InitializingBean,又提供了普通方法。 先执行接口的初始化方法,再执行普通的初始化方法。
  2. 如果有属性赋值注入,又有初始化方法,哪个先执行? 先执行注入操作,再执行初始化方法(afterPropertiesSet) 可通过方法名理解
  3. 什么叫做初始化操作 主要指对资源的初始化,数据库.....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();
  
  }
}

复制代码

注意事项:

  1. 销毁方法只有在工厂调用close() 方法的时候才会调用
  2. 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" />
复制代码

细节分析:

  1. 销毁方法的操作只作用于 scope="singleton" 的对象
  2. 销毁操作主要指资源的释放操作
  3. 执行顺序: 接口先于自定义

2.4 后置处理Bean

我们在前面提到了关于类的初始化的方式,除此之外,spring还提供了一个叫做BeanPostProcessor的接口,可以让我们对spring工厂创建的对象进行再加工。他底层的实现原理是通过AOP实现的。我们来看下这个接口。这个接口有两个方法需要实现。

postProcessBeforeInitialization: 该方法执行在初始化代码之前 postProcessAfterInitialization: 该方法执行在初始化代码之后

这里说的初始化方法指的就是我们前面提到的InitializingBean 接口中的afterPropertiesSet方法。

步骤:

  1. 实现BeanPostProcessor
  2. 重写两个方法: postProcessBeforeInitialization postProcessAfterInitialization
  3. postProcessBeforeInitialization: Spring创建完对象并注入后,可以运行该方法进行加工,通过返回值交给spring框架
  4. postProcessAfterInitialization:spring执行完初始化操作后,进行的加工。
  5. 配置文件配置

这里要特别注意下这两个需要实现的方法,跟前面初始化的方式不太一样,首先这两个方法是默认方法,不实现也不会报错,其次这两个方法有参数,有返回值。我们直接上代码。

  1. 开发一个类
代码语言:javascript复制
@Data
public class Category{
  private String name;
  private int age;
}
复制代码
  1. 交给spring管理,并注入属性
代码语言:javascript复制
<bean id="c" class="xxx.Category" >
  <property name="name" value="张三" />
  <property name="age" value="10" />
</bean>
复制代码
  1. 创建一个bean的加工工厂,也就是BeanPostProcessor的实现类
代码语言:javascript复制
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;
  }  
}

复制代码
  1. 配置文件配置
代码语言:javascript复制
<bean id="myBeanPostProcessor" class = "com.xxx.MyBeanPostProcessor" />
复制代码

测试,获取c对象,打印name, 发现结果变为李四。

注意事项:

  1. BeanPostProcessor 是对spring工厂中的所有类都生效的,所以使用之前,最好使用 instanceof 做判断,对指定类型进行加工,避免出错。
  2. 由于是对所有类做操作,所以第一个参数 Object bean ,就代表被加工的类,spring工厂的的所有类,都会走这个方法,所以这个bean可能是User,也可能是Product,等等等。第二个参数就是类的名称。
  3. 加工之后,要把对象做返回操作,即使你什么都不处理,也要把bean返回。
  4. 一定要注意执行顺序。

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的生命周期。

  1. 对象的实例化(相当于new了出来)
  2. 填充属性
  3. 调用BeanNameAware的setBeanName方法
  4. 调用BeanFactoryAware的setBeanFacotry方法
  5. 调用ApplicationContextAware的setApplicationContext方法
  6. 调用BeanPostProcessor的postProcessBeforeInitialization方法
  7. 调用InitializingBean的afterPropertySet方法
  8. 调用自定义的初始化方法(init-method = myInit)
  9. 调用BeanPostProcessor的postProcessAfterInitialization方法
  10. bean可以使用了
  11. 容器关闭时调用DisposableBean的destroy方法
  12. 调用自定义的销毁方法

注意: 上面的步骤主要是为了说明对象生命周期各个阶段的执行顺序,如果实现了对应接口就执行,没实现就不执行。 好了,本篇文章很重要,希望大家有所收获!!!

参考资料: Spring IN Action(第四版) 中国邮电出版社 孙帅spring详解:www.bilibili.com/video/BV185…

0 人点赞