可以学习到什么?
0. spring整体脉络
1. 描述beanfactory
2. beanfactory和ApplicationContext的区别
3. 简述SpringIoC的加载过程
4. 简述Bean的生命周期
5. Spring中有哪些扩展接口及调用机制
一. spring源码整体脉络介绍及源码编译
1.1. 什么是IOC
ioc是控制反转,这是一种设计理念,用来解决的是层和层之间,类和类之间的耦合问题.
比如,现在有A,B两个类,在A类中引用了B类. 那么如果有一天,B类要被替换掉,我们会怎么办呢?如果B类被引用了100次,我们要替换100次?
现在呢,A是直接调用B,如果我们间接的调用B,将B包装起来,如果以后将B换成C,只需要在包装类里面替换就可以了. 我们不需要修改A类. 这就是控制反转.
Spring使用了ioc,Spring.ioc(A,B) 将A和B的引用都存在ioc中,spring会帮我们维护好,完全不用担心.
当我们在A中要使用B的时候,使用B对应的接口,然后使用@Autowired注解
A { @Autowired private IB b; }
什么时候把B换掉了,不痛不痒的,只需要把新的类放到IoC中就可以了.
1.2. Spring源码的整体脉络梳理
Spring IoC是一个容器,在Spring Ioc中维护了许多Bean
那这些bean是如何被注册到IoC中的呢? 换句话说,我们自定义的类,是如何作为一个bean交给IoC容器去管理的呢?
先来回忆,我们在开发spring的时候的步骤:
第一步: 配置类. 配置类可以使用的方式通常由 1) xml配置 2) 注解配置 3) javaconfig方式配置 第二步: 加载spring上下文 1) 如果是xml,则new ClassPathXmlApplicationContext("xml"); 2) 如果是注解配置: 则new AnnotationConfigApplicationContext(config.class) 第三步: getBean() 我们会讲自定义的类,通过xml或者注解的方式注入到ioc容器中.
在这一步,会将xml或注解中指定的类注入到IoC容器中.
1.2.1 那么,到底是如何将一个类注入到ioc中的呢?
下面就来梳理一下整个过程.
第一问: 一个类要生产成一个Bean,最重要最核心的类是什么?
第二问: beanfactory是什么呢?
beanfactory是Spring顶层的核心接口--使用了简单工厂模式. 通常都是根据一个名字生产一个实例,根据传入的唯一的标志来获得bean对象,但具体是穿入参数后创建,还是穿入参数前创建,这个要根据 具体情况而定,根据名字或类型生产不同的bean.
一句话总结: beanfactory的责任就是生产Bean
来看下面这段代码:
public static void main( String[] args ) { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); context.getBean(***); }
这段代码实现的功能是,读取当前文件所在目录及其子目录中的文件,然后获取指定名称的bean,整个流程如下图所示:
首先,通过ClassPathXmlApplicationContext或者AnnotationConfigApplicationContext去读取配置,然后将其交给beanfactory.
第三. beanfactory调用getBean()方法,将Bean注入到IoC容器中
我们发现,配置的读取,可能是xml方式,也可能是annotation的方式,不同的方式读取应该使用的是不同的工具. 那么这些工具读取的结果应该是统一的,然后才能交给beanfactory去处理.
因为在beanfactory中是不会对这些异同点进行处理的. beanfactory的作用只有一个,就是个生产Bean.
1.2.2 那么,不同的工具读取配置是如何统一的呢?
我们知道,读取配置这一块,应该会有一个不同的实现. 将xml和注解方式读取成统一的东西,放入到beanfactory中. 这个东西是谁呢?就是BeanDefinition(Bean定义)
什么意思呢? 如下图:
看绿色框框住的部分. 这个含义是: 通过不同的工具,可能是xmlApplicationContext,可能是annotationApplicationContext工具 读取的配置,最后都会构造成BeanDefinition对象. 然后将BeanDefinition传递给beanfactory,beanfactory统一处理BeanDefinition对象,调用getBean()方法,将其放入IoC容器中.
1.2.3 那么又是是如何读取配置统一构造成BeanDefinition的呢?
我们来举个例子,现在有一个人,比如说我刚买了一个房子,我要装修. 需要一个衣柜,这时候,我会找到一个衣柜店. 然后告诉他我的需求,柜子的颜色,款式格式什么样. 然后衣柜店记录我的需求,这个时候,他不会自己生产,他会通知工厂,让工厂来生产. 工厂按照什么生产呢,衣柜店有一个设计师, 他们的设计师. 会按照我的需求设计出一张图纸. 然后将图纸交给工厂. 工厂按照图纸要求生产Bean.
整个过程如下图:
入口是"我"
1. 我有一个需求,打一个柜子,找到衣柜店
2. 我告诉衣柜店我的需求,款式,然后衣柜店的设计师按照我的要求,设计出一张图纸
3. 衣柜店将图纸给到工厂,工厂按照图纸生产柜子
这是制造衣柜的过程. 其中在画图纸的时候,画一张就给工厂给一张,这样效率太低了. 我们可以画了n张,一起给工厂. 所以,在设计图纸这块是一个容器,存放多张图纸
后面,如果我还想定制一个橱柜店. 那么,就告诉设计师我的橱柜的颜色,就可以了. 流程和上面都是一样的.
整个这个过程,就类似于我们的bean生产的过程
1. 定义了一个带有@Component注解的类,我找到衣柜店,衣柜店就类似于ApplicationContext.
2. 我告诉ApplicationContext我的需求,我要懒加载@Lazy,设置单例模式还是多例模式@Scope. 对应的就是定制柜子的颜色,款式. 然后衣柜店里的设计师BeanDefinitionRegistry根据我的需求设计出图纸,也就是构造成BeanDefinition. 不同的BeanDefinitionRegistry设计出不同的BeanDefinition,然后将他们都放在容器中.
3. 衣柜店ApplicationContext统一将一摞图纸BeanDefinitionMap交给工厂, 然后工厂按照要求生产Bean,然后将生成的bean放入到IoC容器中.
这是一个带有@Component的类被加载的过程.
衣柜店要要想生意好,那么他要去拉活呀,所以还需要好的销售. 销售要去扫楼盘,去联系,哪些人有装修的需求. 挨个询问.
可是问了100个人,可能只有10个人有装修的需求. 于是还要有一个接待,这个接待要联系客户,看看哪些是有意向的客户,将其筛选出来. 然后定制家具.
这里多了两类人: 销售和接待. 具体工作如下.
销售就相当于我们的BeanDefinitionReader,他的作用是去扫楼盘,找到潜在客户. 对应的就是BeanDefinitionReader去读取xml配置或者Annotation注解.
xml中的配置有很多,注解也有很多,并不都是我们的目标. 于是有了接待
接待要去扫描所有潜在客户. 将有意向的客户扫描出来. 这就类似于我们的BeanDefinitionScanner,去扫描潜在客户,最后将带有@Component注解的类筛选出来
这就是后面需要定制家具的客户了
BeanDefinitionReader对应的就去读取配置类,看看有哪些需求需要搞装修.
它本身也是一个抽象类,可以看到他有AnnotationBeanDefinitionReader和XmlBeanDefinitionReader
我们配置了配置包,去扫描这个包下所有的类,然后将扫描到的所有的类交给BeanDefinitionScanner,它会去过滤带有@Component的类.
在和上面的流程连接起来,就是整个配置文件被加载到IoC的过程了.
1.3. ApplicationContext和factorybean的区别
1. factorybean的功能就是生产bean. 他生产bean是根据BeanDefinition来生产的. 所以,一次只能生产一个
2. ApplicationContext有两种. 一种是xmlApplicationContext,另一种是annotationApplicationContext,他传入的参数是一个配置文件. 也就是可以加载某个目录下所有带有@Component的类
他们两个都各有使用场景. 使用ApplicationContext的居多.
另一个区别: 就是后面会说到的,ApplicationContext有两个扩展接口,可以用来和外部集成. 比如和MyBatis集成.
1.4. Bean的生命周期
如上图,beanfactory拿到BeanDefinition,直接调用getBean()就生产Bean了么?
不是的,生产Bean是有一个流程的. 下面我们来看看Bean的生命周期
第一步: 实例化. bean实例化的时候从BeanDefinition中得到Bean的名字,然后通过反射机制,将Bean实例化. 实例化以后,这是还只是个壳子,里面什么都没有. 第二步: 填充属性. 经过初始化以后,bean的壳子就有了,bean里面有哪些属性呢? 在这一步填充 第三步: 初始化. 初始化的时候,会调用initMethod()初始化方法,destory()初始化结束方法 这个时候,类就被构造好了. 第四步: 构造好了的类,会被放到IoC的一个Map中. Map的key是beanName,value是bean实例. 这个Map是一个单例池,也就是我们说的一级缓存 第五步: 我们就可以通过getBean(user"),从单例池中获取雷鸣是user的类了.
在构造bean的过程中,还会有很多细节的问题,比如循环依赖.
A类里面调用了B类,所以beanfactory在构造A的时候,会去构造B. 然后在构造B的时候,发现,B还依赖了A. 这样,就是循环依赖. 这是不可以的.
Spring是如何解决循环依赖的问题的呢?
设置出口. 比如A在构造的过程中,那么设置一个标记,正在构造中. 然后构造B,B在构造的过程中应用了A,有趣构造A,然后发现A正在构造中,那么,就不会再次构造A了.
后面还会详细讲解Spring是如何解决循环引用的. 这里我们需要知道的是: Spring使用的是三级缓存来解决循环引用的问题
其实,bean是存在一级缓存里面,循环引用使用的是三级缓存来解决的. 其实,一、二、三级缓存就是Map。
1.5. Spring中的扩展接口
有两个非常重要的扩展接口. beanfactoryPostProcessor(Bean工厂的后置处理器) 和 BeanDefinitionRegistryPostProcessor
这两个接口是干什么的呢?
我们在这个图里面,看到了设计师要设计出图纸,然后把图纸交给工厂去生产. 那么设计师设计出来的图纸,有没有可能被修改呢?
beanfactoryPostProcessor(Bean工厂的后置处理器)的作用就是修改BeanDefinition.
1. beanfactoryPostProcessor: 修改BeanDefinition.
是一个接口,我们的类可以实现这个接口,然后重写里面的方法
DefinedPost implements beanfactoryPostProcessor { /** * 重写Bean工厂的后置处理器 * @param beanfactory * @throws BeansException */ @Override postProcessbeanfactory(ConfigurableListablebeanfactory beanfactory) throws BeansException { // beanfactory 拿到工厂了,就可以获取某一个Bean定义了 GenericBeanDefinition car = (GenericBeanDefinition) beanfactory.getBeanDefinition(Car); 拿到了car,然后修改了Car的类名为com.example.tulingcourse.Tank. 那么后面在获取的Bean里面,将其转换为Car,就会报错了 car.setBeanClassName(com.example.tulingcourse.Tank); } }
第一步: 实现了beanfactoryPostProcessor接口,然后需要重写里面的方法
第二步: 我们发现重写方法直接给我们了beanfactory,bean工厂
第三步: 拿到bean工厂,我们就可以根据名称获取BeanDefinition,也就是bean定义了.
第四步: 我们修改了bean定义中的类名为Tank.
这时候会发生什么呢? 从bean工厂中构建的car,取出来以后转换成Car对象,会报错,
main(String[] args) { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Application.); Car car = context.getBean("car",Car.class); // 这里会报错,因为已经被修改 System.out.println(car.getName()); }
执行流程: 当spring启动的时候,就会去执行AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(TulingCourseApplication.class);
然后ApplicationContext回去扫描所有实现了beanfactoryPostProcessor对象的类,然后执行postProcessbeanfactory方法.
beanfactoryPostProcessor被使用到的场景非常多,在集成其他组件的时候,比如集成mybatis
2. BeanDefinitionRegistryPostProcessor 注册BeanDefinition
这是一个Bean定义注册的后置处理器.BeanDefinitionRegistryPostProcessor本事是实现了beanfactoryPostProcessor 接口
我们来看个demo
DefinedPost implements BeanDefinitionRegistryPostProcessor { postProcessbeanfactory(ConfigurableListablebeanfactory beanfactory) throws BeansException { 获取某一个Bean定义了 GenericBeanDefinition car = (GenericBeanDefinition) beanfactory.getBeanDefinition(); ); } @Override postProcessBeanDefinitionRegistry(BeanDefinitionRegistry beanDefinitionRegistry) throws BeansException { } }
一个类实现了BeanDefinitionRegistryPostProcessor,需要重写postProcessBeanDefinitionRegistry方法,这个方法直接将BeanDefinitionRegistry就给我们了.
然后使用beanDefinitionRegistry.registerBeanDefinition(); 就可以添加图纸了
在这里可以注册新的bean,也可以删除注册的bean. 多注册一个,bean工厂就要多构建一个.
总结:
beanfactoryPostProcessor和BeanDefinitionRegistryPostProcessor这两个扩展类是很重要的类,这对于向外部扩展起到了很大的的作用,比如: 集成mybatis
beanfactoryPostProcessor和BeanDefinitionRegistryPostProcessor是在ApplicationContext中的两个扩展接口. 这也是ApplicationContext和beanfactory的区别之一,因为有了这两个扩展节点,就可以和外部做集成. 比如Mybatis集成. 比如: 扫描配置类,就是通过 这两个扩展点的方式实现的.
这个扩展点的作用:
1. 除了IoC,其他的扩展,比如AOP,和MyBatis集成,都要用到这两个扩展点. 之所以Spring能够有序不乱的和很多外部组件整合,都是这两个扩展点的功能
1.6 Bean的扩展点
除了ApplicationContext有扩展点, 在Spring IoC中的bean也有扩展点. BeanPostProcessor(Bean的后置处理器). 如果使用在getBean()之前,那么可以阻止构建Bean,还可以自定义构建Bean.
BeanPostProcessor使用的场景有很多. 在Bean实例化之前和之后会被调用. 在填充属性之前和之后也会被调用,初始化之前和之后也会调用. 有些过程不只调用一次. 整个过程一共会调用9次. 在每一个过程都可以扩展Bean.
思考: Spring加入AOP是如何实现呢?
集成AOP肯定不会和IoC糅合在一块了. AOP就是通过BeanPostProcessor(Bean后置处理器)整合进来的.
AOP的实现方式有两种: 一种是CGLIB,另一种是JDK.
假如说要进行集成,会在那个步骤继承呢? 比如要加日志,使用AOP的方式加. 我们通常是在初始化之后加AOP. 在这里将AOP集成进来.
如上图: 当面试的时候面试官问你,Bean的生命周期,我们不能只说实例化-->填充属性-->初始化. 还需要说初始化的时候,还有一些列的aware.
1.7. Spring IOC的加载过程
对照上图,我们来简述ioc的加载过程
我们将一个类加载成Bean,不是一步到位的,需要经历一下的过程.
1. 首先,我们要将类加载成BeanDefinition(Bean定义)
加载成bean定义,有以下几个步骤:
1) 使用BeanDefinitionReader加载配置类,此时是扫描所有的xml文件或者项目中的注解. 这里面有些使我们的目标类,有些不是
2) 使用BeanDefinitionScanner扫描出我们的目标类.
3) 使用BeanDefinitionRegistry注册bean到BeanDefinitionMap中.
2. 然后,ApplicationContext可以调用beanfactoryPostProcessor修改bean定义,还可以调用BeanDefinitionRegistryPostProcessor注册bean定义
3. 将BeanDefinition交给beanfactory处理,beanfactory调用getBean()生成Bean或者调用Bean(getBean()有两个功能).
4. 成产bean的时候,首先会实例化,然后填充属性(主要是读取@Autowire,@Value等注解). 在初始化Bean,这里会调用initMethod()方法和初始化销毁方法destroy(). 初始化的时候还会调用一堆的Aware,而且在bean生成的过程中 会有很多扩展点,供我们去扩展.
5. 将生产出的Bean放入到Map中,map是一个一级缓存池. 后面,我们可以通过getBean("user")从缓存池中获取bean
1.9 Spring源码编译过程演示
as