spring5源码 -- IOC容器设计理念和核心注解的作用
?可以学习到什么? 0. spring整体脉络 1. 描述beanfactory 2. beanfactory和ApplicationContext的区别 3. 简述SpringIoC的加载过程 4. 简述Bean的生命周期 5. Spring中有哪些扩展接口及调用机制 ?一. spring源码整体脉络介绍及源码编译?1.1. 什么是IOCioc是控制反转,这是一种设计理念,用来解决的是层和层之间,类和类之间的耦合问题. 比如,现在有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是什么呢? 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 { ); ); } @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 (编辑:北几岛) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |