【Spring】理解Spring IOC容器

一、概述

IOC中文名为控制反转,这里的反转指的是依赖对象的获得被反转了。为了更明确说明控制反转的含义,我举了一个简单的例子,如下:
假设有一个对象A依赖于对象B:

class A{
	//A依赖B
	private B b;
	//构造函数
	public A(B b){
		this.b = b;
	}
	//setter
	public void setB(B b){
		this.b = b;
	}
}
class B{
	//类B
}
  • 不使用IOC时:当类A依赖类B时,我们首先要先实例化对象B,然后通过A的构造函数将对象B传给A,或者通过A的setter()方法将B传给A。
class Main{
	public static void main(String[] args){
		B b = new B();
		A a = A(b);
	}
}
  • 使用了IOC时:我们只需要手动实例化A即可,IOC容器会自动帮我们注入A所需要的B。
class A{
	//A依赖B
	@Autowired
	private B b;
	//构造函数
	public A(){
	}
	//setter
	public void setB(B b){
		this.b = b;
	}
}
class Main{
	public static void main(String[] args){
		//属性b会自动注入
		A a = new A();
	}
}

有了IOC,我们在实例化某个对象的时候,就不需要再关注它依赖哪些类,全部交给IOC容器来管理即可。

二、Spring IOC 加载过程

IOC的加载过程如下图所示:
在这里插入图片描述

  1. 首先,通过BeanDefinitionReader读取指定的配置文件生成bean定义信息(BeanDefinition对象),然后通过BeanFactoryPostProcessor(BeanFactory后置增强器)对Bean定义信息进行个性化定制,从而得到修改后的BeanDefinition
  2. 得到增强后的BeanDefiniton对象后,容器会通过反射的方式将BeanDefinition对象实例化成具体的bean对象
    注意:上面提到的BeanFactoryPostProcessor其实是一个后置增强器的接口,这个后置增强器是可以有多个的,只要我们在多个不同类实现BeanFactoryPostProcessor接口即可。使用方法如下:
    在这里插入图片描述
    我们可以在postProcessBeanFactory()方法中对Bean定义信息进行修改。

三、Spring Bean的生命周期

粗略的看,bean的生命周期主要分为以下4个步骤:
在这里插入图片描述
但是spring在这4个过程背后做了很多事,细化后的流程图如下:
在这里插入图片描述

3.1 实例化

3.1.1 实例化前置处理

实例化前置处理使用的是InstantiationAwareBeanPostProcessor.postProcessBeforeInstantiation(Class<?> beanClass, String beanName)方法,方法里面两个参数分别是beanClassbeanName,顾名思义,就是对在对象实例化之前对bean对象的class信息进行修改或者扩展,以达到我们想要的功能,它的底层是动态代理AOP技术实现的,且是bean生命周期中最先执行的方法。

返回非空:返回值是Object类型,这意味着我们可以返回任何类型的值,由于这个时候目标对象还未实例化,所以这个返回值可以用来代替原本该生成对象的目标对象的实例,也就是说,如果返回了非空的值,那么以后我们需要用到这个bean的时候,拿到的就现在返回的对象了,也就不会去走第二步去实例化对象了。

返回空(null)值:默认也是返回null值的,那么就直接返回,接下来会调用doCreateBean方法来实例化对象。

3.1.2 实例化对象

调用doCreateBean()方法创建实例对象,用反射技术创建。需要注意的是,这个时候只是将对象实例化了,对象内的属性还未设置。

3.1.3 实例化后置

方法名称: InstantiationAwareBeanPostProcessor.postProcessAfterInstantiation(Object bean, String beanName)

在目标对象实例化之后调用,这个时候对象已经被实例化,但是该实例的属性还未被设置,都是null。因为他的返回值是决定要不要调用postProcessPropertyValues方法中的一个因素(因为还有一个因素是mbd.getDependencyCheck())。

返回false :如果该方法返回false,并且不需要check,那么postProcessPropertyValues就会被忽略不执行。

返回true : 如果返回true,postProcessPropertyValues就会被执行。

3.1.4 属性修改

方法名称 :InstantiationAwareBeanPostProcessor.PropertyValues postProcessPropertyValues(PropertyValues pvs, PropertyDescriptor[] pds, Object bean, String beanName)
此方法可对属性值进行修改,修改范围包括添加、修改、删除操作,如果实例化后置 postProcessAfterInstantiation()方法返回false,那么该方法不会被调用。

3.2 初始化

3.2.1 给Bean对象属性赋值

Bean对象属性指的是用spring 的人自定义的bean对象属性,像 User、Student、Teacher 、UserService、IndexService 这类的对象都是自定义bean对象,第5步主要给这类属性进行赋值操作,使用的是 AbstractAutowireCapableBeanFactory.populateBean()方法进行赋值。

3.2.2 给容器属性赋值

容器属性其实就是容器自带的属性,这些属性都是spring本来就有的。可以肯定的是,它们都是 Aware 接口的实现类,主要有以下实现类,我已经将它们的执行顺序都排列好了,如下图:
在这里插入图片描述

3.2.3 初始化前置

方法名称: BeanPostProcessor.postProcessBeforeInitialization()
在每一个 Bean 初始化之前执行的方法(有多少 Bean 调用多少次)
注意 : 启用该方法后,标注了@PostConstruct注解的方法会失效

3.2.4 初始化后置

方法名称: BeanPostProcessor.postProcessAfterInitialization()

在每一个 Bean 初始化之后执行的方法(有多少 Bean 调用多少次)

初始化前置和初始化后置的实现代码如下:

@Component
public class ExtBeanPostProcessor implements BeanPostProcessor {
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        // 在每一个 Bean 初始化之前执行的方法(有多少 Bean 调用多少次)
        // 注意 : 启用该方法后,标注了@PostConstruct注解的方法会失效
        System.out.println("初始化前置方法");
        return null;
    }
    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        //在每一个 Bean 初始化之后执行的方法(有多少 Bean 调用多少次)
        System.out.println("初始化后置方法");
        return null;
    }
}

3.2.5 执行初始化方法

初始化方法有三个,分别是 添加了@PostConstruct 注解的方法、实现InitializingBean接口、在@bean注解上添加 initMethod属性。

初始化方法一:@PostConstruct

在bean对象内添加@PostConstruct 注解后即可实现初始化的功能,被@PostConstruct修饰的方法会在构造函数之后,init()方法之前运行。 有多个则会执行多次;
注意: 如果spring 实现了 BeanPostProcessor接口的postProcessBeforeInitialization() 方法,也就是12的初始后置方法,那么@PostConstruct注解会失效。
示例代码:

// @PostConstruct注解
@Component
public class ExtPostConstruct {
    /**
     * 被@PostConstruct修饰的方法会在构造函数之后,init()方法之前运行。如果有多个则会执行多次
     * 注意: 如果spring 实现了 BeanPostProcessor接口的postProcessBeforeInitialization方法,该@PostConstruct注解会失效
     */
    @PostConstruct
    public void init() {
        System.out.println("第一个init");
    }
 
    // 有多个会执行多次
    @PostConstruct
    public void init1() {
        System.out.println("第二个init方法");
    }
 
}
初始化方法二: InitializingBean.afterPropertiesSet()

spring 初始化方法之一,作用是在BeanFactory完成属性设置之后,执行自定义的初始化行为。

执行顺序:在initMethod之前执行,在@PostConstruct之后执行

代码示例:

@Component
public class ExtInitializingBean implements InitializingBean {
    @Override
    public void afterPropertiesSet() throws Exception {
        // 一个 InitializingBean 执行一次
        // spring 初始化方法,作用是在BeanFactory完成属性设置之后,执行自定义的  初始化行为.
        // 执行顺序:在initMethod之前执行,在@PostConstruct之后执行
        System.out.println("InitializingBean");
    }
}
初始化方法二:init-method

bean 配置文件属性 init-method 用于在bean初始化时指定执行方法,用来替代继承 InitializingBean接口,

注意的一点是只有一个类完整的实例被创建出来后,才能走初始化方法。

示例代码,先定义一个类: BeanTest.java ,在类中定义一个初始化方法 initMethod_1()

public class BeanTest {
    
    // 将要执行的初始化方法
    public void initMethod_1(){
        System.out.println("我是beanTest的init方法");
    }
}

xml 配置方式
<bean id="beanTest" class="com.BeanTest" init-method="init"></bean> 注解配置方式

@Component()
public class InitMethod  {
    // 在@Bean注解上添加initMethod属性,指向类中的 initMethod_1 执行初始化方法
    @Bean(initMethod = "initMethod_1")
    public BeanTest getBeanTest(){
        return new BeanTest();
    }
}

3.3 使用中

到这一步,bean对象就已经完全创建好了,是一个完整对象了,并且正在被其他对象使用了。

3.4 销毁

在这里需要先说一下,被spring容器管理的bean默认是单例的,默认在类上面有个 @Scope注解,也就是这样的

@Component()
@Scope(value = ConfigurableBeanFactory.SCOPE_SINGLETON)
// @Scope(value = "singleton")  // 也可以这样写
public class InitMethod  {
 
  // methods....
 
}

如果要设置成多例,只需要把@Scope的属性值改一下就行,就像这样,多例模式也叫原型模式,它底层不是重新创建一个bean对象出来,而是使用深拷贝技术实现的,就是复制一个对象出来进行使用

@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)
// @Scope(value = "prototype") // 也可以这样写

为什么要介绍单例和多例呢? 因为啊,销毁流程的走向就跟你是单例还是多例有关。

如果是单例模式,会先执行 DisposableBean.destroy()方法,然后在执行 destroy-Method 方法。

3.4.1 DisposableBean.destroy()

单例模式的销毁方式,示例代码

/**
 * 销毁方法
 */
@Component
public class ExtDisposableBean implements DisposableBean {
    @Override
    public void destroy() throws Exception {
        System.out.println("我被销毁了");
    }
}

3.4.2 destory-method方法

还是拿 第11 个流程的例子来讲,只不过这次我们在@Bean注解里加上 destroyMethod属性,指向销毁方法 :destroyMethod_1()

package com.Spring.Boot.init;
 
import com.Spring.Boot.init.bean.BeanTest;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;
 
@Component()
public class InitMethod  {
 
    // 在@Bean注解上添加initMethod属性,指向类中的 initMethod_1 执行初始化方法
    // 在@Bean注解上添加destroyMethod属性,指向类中的 destroyMethod_1 执行销毁方法
    @Bean(initMethod = "initMethod_1",destroyMethod = "destroyMethod_1")
    public BeanTest getBeanTest(){
        return new BeanTest();
    }
}
BeanTest.java

package com.Spring.Boot.init.bean;
 
public class BeanTest {
    // 将要执行的初始化方法
    public void initMethod_1(){
        System.out.println("我是beanTest的init方法");
    }
    // 将要执行的销毁方法
    public void destroyMethod_1(){
        System.out.println("我是beanTest的init方法");
    }
}

xml的配置方式
<bean id="beanTest" class="com.BeanTest" destroy-method="destroyMethod_1"></bean>

3.4.3 返回bean给用户,剩下的生命周期由用户控制因为

多例模式下,spring无法进行管理,所以将生命周期交给用户控制,用户用完bean对象后,java垃圾处理器会自动将无用的对象进行回收操作。

参考文献:
[1] https://juejin.cn/post/6966158157202587662

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值