如何理解IOC

在JAVA的古早时期,程序员自己既要定义对象,然后还要通过new的方式创建出对象。而在Spring中则我们只需要定义Bean,然后交给Spring来创建对象(从甲乙方的角度,定义对象是提需求的甲方,实现对象是执行的乙方。那么new的方式是即做甲方又做乙方。而Spring让程序员当了一回甲方)。

而这个动作就是IOC(Inversion of Control)控制反转。不过IOC不是一种具体的技术,而是一种设计思想,用来解决的是层和层之间, 类和类之间的耦合问题。

举个例子来说,现在有Cat、Dog两个类,Cat中引用了Dog类。如果这时Dog需要被替换为Pig类,我们会如何做呢?一点点改倒是可以,但如果Dog类被引用了100次,我们也要修改100次吗?

现在假设我们做如下调整,我们将Dog包装起来,然后Cat间接调用它;如果后面需要将Dog改为Pig,只要在包装类中奖Dog改为Pig就行,完全不需要修改Cat类中的引用了,而这就是控制反转。

Spring使用了IOC机制会将Cat、Dog等类的引用存在IOC中,Spring会帮我们维护好他们的生命周期,我们完全不用担心。

Spring IOC脉络梳理

我们已经初步知道,Spring是一个内部维护了很多Bean的容器,那么这些Bean是如何被加载到IOC中的?比如所,我们一个自定义的Controller类是如何作为一个Bean交给IOC管理的呢?

先看下Spring的简单步骤

  1. 配置类. 配置类可以使用的方式通常有

    • XML配置
    • 注解配置
    • javaconfig方式配置
  2. 加载Spring上下文

    • 如果是xml: 则new ClassPathXmlApplicationContext(“xml”);
    • 如果是注解配置: 则new AnnotationConfigApplicationContext(config.class)
  3. getBean()

    我们会讲自定义的类, 通过XML或者注解的方式注入到IOC容器中.

一个类是如何注入到IOC中的

先看下面代码,只是一个简单的根据Bean 名称获取Bean实例的代码。通过下面代码我们可以知道,Bean并不是IOC创建的,而是通过beanFactory创建的。

1
2
3
4
5
6
7
8
9
10
11
public static void main(String[] args) {
SpringApplication.run(SpringbootDemoApplication.class, args);
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
applicationContext.getBean("helloService");
}

@Override
public Object getBean(String name) throws BeansException {
assertBeanFactoryActive();
return getBeanFactory().getBean(name);
}

通过上面代码我们可以绘制出下面这张图

IOC脉络-1

可以看出,读取配置的方式可能是注解的方式,也可能是XML的方式,不同的方式读取应该使用的是不同的工具。那么这些工具读取到结构应该是统一的,然后才能交给BeanFactory处理。

不同的工具读取配置是如何统一的?

读取配置这一块儿需要一个统一的实现,将XML和注解读取成统一的内容并放入到BeanFactory中,那么这个东西其实就是BeanDefinition。

IOC脉络-1 (1)

那么又是是如何读取配置统一构造成BeanDefinition的呢?

我们还是举个例子,假设我们现在有一栋需要装修的房子,我们需要一个柜子,那我们就去衣柜店,然后告诉店员我们的需求,比如说柜子的颜色、款式等等,衣柜店的设计师会根据我们的需求设计出图纸并将图纸转发给工厂,工厂会根据图纸来生产衣柜(Bean)。

IOC梳理-2

通过这张图我们可以得出一个带有@Component注解的类被声场Bean的大致过程:

  1. 定义一个带有@Component注解的类,然后找到ApplicationContext
  2. 告诉ApplicationContext我们的需求:要懒加载@Lazy,设置单例模式还是多例模式@Scope,对应的就是定制柜子的颜色,款式。然后衣柜店里的设计师BeanDefinitionRegistry根据我的需求设计出图纸,也就是构造成BeanDefinition。 不同的BeanDefinitionRegistry设计出不同的BeanDefinition, 然后将他们都放在容器中。
  3. 衣柜店ApplicationContext统一将一摞图纸BeanDefinitionMap交给工厂,然后工厂按照要求生产Bean,然后将生成的bean放入到IOC容器中。

衣柜店要想生意好,他们就需要拉更多的顾客进来,所以他们就需要销售去扫楼盘,去联系买房的人看谁有装修需求。可能问了100个人,其中10个人有装修需求,然后衣柜店还需要一个接待,他负责联系这10个客户,筛选出来需要衣柜的客户,然后再继续后面的定制衣柜操作。

这里多了两个职位:销售和接待。我们和Spring的IOC类比一下。

IOC梳理-3

XML中有很多配置,注解也有很多,但并不都是我们的目标,于是有了接待,节点要去扫描所有潜在的客户,将有意向的客户扫描出来, 这就类似于我们的BeanDefinitionScanner, 去扫描潜在客户,最后将带有@Component注解的类筛选出来

ApplicationContext和BeanFactory的区别

ApplicationContext是Spring容器,负责管理Bean的生命周期和依赖注入,而BeanFactory是一个创建复杂Bean的工厂,你可以通过实现BeanFactory接口来控制Bean的创建过程。

  1. ApplicationContext:这是Spring的核心接口,代表了Spring的应用上下文,也就是Spring容器。ApplicationContext负责管理Bean的生命周期,处理Bean的依赖注入,以及提供访问应用配置的能力。你可以通过ApplicationContext获取注册在容器中的Bean,发布事件,以及访问其他的应用资源。ApplicationContext还提供了国际化支持,消息源处理,应用事件发布等高级功能。
  2. BeanFactory:这是一个创建对象的工厂Bean。当你需要创建的Bean不是简单的对象,而是需要一些复杂的初始化逻辑,或者需要返回的是某个对象的代理时,你可以使用BeanFactory。BeanFactory有一个getObject()方法,Spring容器会调用这个方法来生成Bean。这样,你就可以在getObject()方法中编写创建Bean的逻辑。

Bean的生命周期

Bean的生命周期 (1)

从上图可以看到,BeanFactory拿到BeanDefiniton,然后调用其getBean()方法并不是直接就生成Bean了,它是有一个流程的。

  1. 实例化。Bean实例化的时候从BeanDefiniton在拿到Bean的名字,然后通过反射机制将Bean实例化,此时的Bean还只是个空壳;
  2. 属性填充。经过初始化以后,bean的壳子就有了, bean里面有哪些属性呢? 在这一步填充
  3. 初始化。初始化的时候, 会调用initMethod()初始化方法, destory()初始化结束方法,此时类已经被构造好了
  4. 构造好了的类,会被放到IOC的一个Map中。 Map的key是beanName, value是bean实例。这个Map是一个单例池, 也就是我们说的一级缓存
  5. 我们就可以通过getBean(“dog”),从单例池中获取类名是dog的类了

在构造Bean的过程中会有很多细节问题,比如经典的循环依赖,在这里我们先知道Spring是通过三级缓存解决循环依赖的,后面我们会分析这个问题。

总结来说就是

  • 实例化Bean
  • 设置Bean实例的属性
  • 调用Bean的初始化方法
  • 应用通过IOC容器使用Bean
  • 容器关闭时的Bean销毁

Spring中的扩展接口

Spring中有两个非常重要的扩展接口:BeanFactoryPostProcessor和BeanDefinitonRegistryPostPorcessor,我们分析下这两个扩展接口。

BeanFactoryPostProcessor

这个接口允许我们在Spring容器实例化任何其他Bean之前,修改Bean的BeanDefinition。在这个方法中,我们可以对BeanDefinition进行修改。具体来说,它内部定义了一个方法:postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory)。当所有的BeanDefinition已经加载到容器,但还没有实例化任何Bean时,Spring会调用这个方法。

还是举个例子来说明,在这个例子中,我们要修改name的属性值。可以看到name默认是”lisi”,而我们要将它修改为”zhangsan”.

先定义一个Action处理请求

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@RestController()
@RequestMapping("/hello")
public class HelloAction {
private String name = "lisi";

@Value("${com.zidingyi1}")
private String name1;

@GetMapping("/boot")
public String hello() {
return "Hello, Spring Boot! " + name + " " + name1;
}

public void setName(String name) {
this.name = name;
}
}

重写BeanFactoryPostProcess中的postProcessBeanFactory方法,然后在其中修改name属性的值。

1
2
3
4
5
6
7
8
@Configuration
public class SelfBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
BeanDefinition bd = beanFactory.getBeanDefinition("helloAction");
bd.getPropertyValues().addPropertyValue("name", "zhangsan");
}
}

最终执行结果为:Hello, Spring Boot! zhangsan ziding。

BeanDefinitonRegistryPostPorcessor

它的主要作用有两个:

  1. 动态注册Bean:在Spring容器启动阶段,可以动态地向容器中注册新的Bean。这在需要根据运行时条件(例如配置文件、环境变量等)决定是否创建某个Bean,或者需要创建的Bean的类型或属性需要在运行时决定时,非常有用。
  2. 修改已有Bean的定义:可以修改已经注册在容器中的Bean的定义。例如,可以修改Bean的scope,将singleton改为prototype,或者修改property values,或者更改required属性等

这里不在贴例子Demo,无外乎实现接口并重写对应的方法即可。

BeanPostProcessor:AOP的关键

BeanPostProcessor后置处理器有两个方案,一个是BeanPostProcessor:before,一个是BeanPostProcessor:after,分别用去前后执行。BeanPostProcessor和前面的BeanFactoryProcessor一样是Spring留给我们的扩展点。但是不同的是BeanFactoryProcessor是对Bean实例化之前的扩展,而BeanPostProcessor是对Bean实例化之后的扩展。

但你发现这些扩展点的特性与AOP是很类似的,而实际上BeanPostProcessor后置处理器就是实现AOP的关键。

我们可以从继承BeanPostProcessor接口的AbstractAutoProxyCreator抽象类里找到postProcessBeforeInstantiation后置处理器的createProxy()方法,而在此方法中找到调用了关键的代码getProxy()方法,而这个方法定义自AopProxy接口。

AOP中的JDK和cglib动态代理都是继承AopProxy。

所以当你再回过头看Spring,都是由IOC所主导的,纵使Spring的核心还有AOP,但AOP是以IOC来作为基础的。AOP也只不过是IOC中的分支,而IOC才是树的主干。

总结

最后有一张图总结一下整个流程

IOC脉络-全