小游说源码之-SpringIOC(1)

501 阅读6分钟

Spring是每个JAVA程序员最最熟悉的伙伴,从上古时代开发的各种企业级软件系统,到如今的各种大型互联网应用,甚至有些穿戴式设备里面都有Spring的身影。如果将JAVA程序员比作为一个行走于江湖的侠客,那么Spring就是他手上的那柄宝剑,没有这柄剑当然也可以硬闯江湖,但是会非常的艰难。所以我们非常有必要深入了解一下Spring的源码及其体现出来的思想。

什么是IOC

在深入了解源码之前,我们有必要去了解一下Spring的用途和对我们程序来说真正的意义所在。很多同学都知道,Spring其中一个最核心的功能就是IOC(控制反转),那么什么是控制反转呢?见下图: 所谓的“控制反转”就是:原来我需要柜子,我负责生产和管理柜子,这种情况控制权在我;另一种情况是,我需要柜子,我告诉了“柜子工厂店”这个需求,它就会给我一个柜子,那么是由“柜子工厂店”来负责生产和管理柜子,这时候控制权在“柜子工厂店”,这就叫“控制反转”。所以看起来Spring在这个场景下就扮演了一个“柜子工厂店”的角色,那么我们有必要升入了解下这家店。

解密柜子工厂店

我们一般使用Spring获取Bean的时候,一般由两种方式,一种是基于XML方式,还有一种是基于注解的方式,我们下面关注的重点放在注解的方式,这种方式更为主流,而且源码体现出来的思想来更为先进。
注解方式

//配置类
@Configuration
@ComponentScan(basePackages = {"com.xxx.test.*"})
public class MyConfig {
}
//将MyConfig类交给“柜子工厂店”
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(MyConfig.class);
//拿到柜子实例
Closet closet = (Closet) ctx.getBean("closet");

我们可以将MyConfig看作一个“我要一批柜子”这样的原始需求,然后交给AnnotationConfigApplicationContext这个“柜子工厂店”,根据一些列生产过程后,用户通过调用getBean()方法获得成品的柜子,也就是一个Bean实例。 原始需求过来了,不可能依据这个原始需求就开始进行生产,肯定会有一个人来细化解读这些需求,那么AnnotatedBeanDefinitionReader就负责将我们的配置类MyConfig.class读取到容器里面去。 好的需求解读完了,这时候要准备生产了,但是目前“柜子工厂店”说到底还是个门店,它必须依托于一个工厂,才能完成生产过程。所以我们必须找一个工厂来提供生产能力。这就是我们熟知的BeanFactory。 好了,现在有了工厂了,就可以生产了吗?当然不行,生产的前提是需要图纸,在Spring里面就是以BeanDefinition(定义)来体现的。这个BeanDefinition定义了包括Bean所对应的Class,是否是单例,是否懒加载,依赖,优先级等等一系列生产Bean所需要参数。那么这些图纸保存到了工厂里面一个BeanDefinitionMap的图纸容器中。那么还需要一个图纸注册器,将图纸放到这个Map中,这个注册器需要实现BeanDefinitionRegistry接口。 现在图纸已经有了,但在生产之前还向客户提供了一个渠道可以增加或者修改图纸,在代码层面体现为BeanFactoryPostProcessor和BeanDefinitionRegistryPostProcessor,这两个都叫做Bean工厂的后置处理器,第一个提供修改BeanDefinition的方法,第二个提供新增BeanDefinition的方法。这实际上体现了Spring扩展点的思想。 好的,该改图纸的也改完了,图纸准备好了,我们就要开始生产柜子了,也就是Bean实例了。那么整个生产过程大体上如下:
(1)实例化:一般是通过Bean定义里面保存的Class信息反射成一个实例。
(2)属性填充:@Autowired或@Value注解的实例在这里填充。
(3)初始化:例如@PostConstruct就是在这个阶段调用。
在第二个阶段也就是属性填充这一阶段,会出现循环依赖的问题,简单说就是A类里面通过@Autowired引入B类,而B类也通过@Autowired引入了A类;那么这个问题Spring是通过三级缓存来解决的,实际上就是三个Map,这个问题后面的文章会详细讲解。
当然在生产Bean的过程中,我们可以不停的去干预生产过程,这里Spring给我们提供了很多的BeanPostProcessor(Bean的后置处理器)用来干预生产过程,有的可以直接停止bean实例化,有的可以终止赋值操作,有的用来创建AOP代理等。这也是Spring对外提供的扩展点,很多中间件的集成,例如mybaitis都是利用这些扩展点来集成Spring的。这个在后面的文章中会详细说到。 在整个生产周期的第三阶段,也就是初始化阶段,会调用一系列Aware接口提供的方法,相当于一系列的钩子方法,可以给使用者提供一系列有用的信息,例如:BeanNameAware可以获得Bean名称、BeanClassLoaderAware获得Bean类加载器、ApplicationEventPublisherAware获得事件发布器、还有一个我们用的最多的ApplicationContextAware,可以获得ApplicationContext上下文。 好了,柜子创建完成了,那么要找个仓库存放生产的柜子,那么这个仓库就叫做Spring的单例池也就是我们常说的容器或者一级缓存。那么它实际上就是一个Map,定义在BeanFactory中,叫做singletonObjects,然后我们getBean实际上就是从这里面拿到成品的柜子,也就是Bean。

总结

通过上面所说的,我们可以看到Spring将类加载到容器的大体步骤分如下几步:

  • 读取配置类。
  • 解析配置类。
  • 注册Bean定义。
  • 通过Bean工厂后置处理器修改和增加Bean定义。
  • 所有的Bean定义放入图纸容器。
  • 开始Bean的实例化。
  • 开始Bean的属性填充。(这里会遇到循环依赖问题,通过三级缓存解决)
  • 开始Bean的初始化。(这里会调用各种Aware钩子函数)
  • 整个Bean的生产过程都可以通过Bean的后置处理器来进行人为干预
  • 最后将生产出来的成品Bean放入到单例池,供使用者getBean获取

ApplicationContext与BeanFactory
这里还有一个知识点,也是一个经典的面试题,ApplicationContext与BeanFactory的区别是什么?那么从上面的内容很容易看出来,BeanFactory只是一个用于生产bean的工厂,它不具备读取和解析配置文件的能力,不具备生产图纸也就是Bean定义的能力,而且它还有很多缺乏的能力,例如:国际化能力,事件广播能力等。这些都是ApplicationContext所具备的。
而且这两个在生产Bean的方式也有所不同,BeanFactory在启动的时候不会去实例化Bean,中有从容器中拿Bean的时候才会去实例化;ApplicationContext在启动的时候就把所有的Bean全部实例化了。它还可以为Bean配置lazy-init=true来让Bean延迟实例化。