控制反转即IoC (Inversion of Control),它把传统上由程序代码直接操控的对象的调用权交给容器,通过容器来实现对象组件的装配和管理。所谓的“控制反转”概念就是对组件对象控制权的转移,从程序代码本身转移到了外部容器。
Spring IOC 负责创建对象,管理对象(通过依赖注入(DI),装配对象,配置对象,并且管理这些对象的整个生命周期。
最少记住这句:对对象组件控制权转交容器,由IOC容器管理对象整个生命周期。
(1)管理对象的创建和依赖关系的维护。对象的创建并不是一件简单的事,在对象关系比较复杂时,如果依赖关系需要程序猿来维护的话,那是相当头疼的
(2)解耦,由容器去维护具体的对象
(3)托管了类的产生过程,比如我们需要在类的产生过程中做一些处理,最直接的例子就是代理,如果有容器程序可以把这部分处理交给容器,应用程序则无需去关心类是如何完成代理的
最少记住这句:管理对象创建和维护依赖关系,解耦,托管省事。
(1)IOC 或 依赖注入把应用的代码量降到最低。
(2)它使应用容易测试,单元测试不再需要单例和JNDI查找机制。
(3)最小的代价和最小的侵入性使松散耦合得以实现。
(4)IOC容器支持加载服务时的饿汉式初始化和懒加载。
最少记住这句:集中管理对象,方便维护。 解耦
最少记住这句:简单工厂+反射(BeanFactory、创建对象)
最少记住这句:管理着 Bean 的生命周期,控制着 Bean 的依赖注入。
Spring 的 IoC 设计支持以下功能:
(1)依赖注入
(2)依赖检查
(3)自动装配
(4)支持集合
(5)指定初始化方法和销毁方法
(6)支持回调某些方法(但是需要实现 Spring 接口,略有侵入)
其中,最重要的就是依赖注入,从 XML 的配置上说,即 ref 标签。
对于 IoC 来说,最重要的就是容器。容器管理着 Bean 的生命周期,控制着 Bean 的依赖注入。
最少记住这句:IOC是一种设计思想,DI是它的实现方式。
最少记住这句:紧耦合的类之间高度依赖,松耦合是通过促进单一职责和关注点分离、依赖倒置的设计原则来实现的。
最少记住这句:顶层接口生成Bean,也是容器管理Bean生命周期
最少记住这句:存储Bean的定义信息,决定Bean的生产方式。
最少记住这句:ApplicationContext是BeanFactory子接口,功能比它多,它不生成bean而是通知后者来生成bean,后者还是懒加载。
BeanFactory和ApplicationContext是Spring的两大核心接口,都可以当做Spring的容器。其中ApplicationContext是BeanFactory的子接口。
(1)依赖关系
BeanFactory:是Spring里面最底层的接口,包含了各种Bean的定义,读取bean配置文档,管理bean的加载、实例化,控制bean的生命周期,维护bean之间的依赖关系。
ApplicationContext接口作为BeanFactory的子接口,除了提供BeanFactory所具有的功能外,还提供了更完整的框架功能:比如说继承MessageSource,因此支持国际化、统一的资源文件访问方式、提供在监听器中注册bean的事件、同时加载多个配置文件、载入多个(有继承关系)上下文 ,使得每一个上下文都专注于一个特定的层次,比如应用的web层。
(2)加载方式
BeanFactroy采用的是延迟加载形式来注入Bean的,即只有在使用到某个Bean时(调用getBean()),才对该Bean进行加载实例化。这样,我们就不能发现一些存在的Spring的配置问题。如果Bean的某一个属性没有注入,BeanFacotry加载后,直至第一次使用调用getBean方法才会抛出异常。
ApplicationContext,它是在容器启动时,一次性创建了所有的Bean。这样,在容器启动时,我们就可以发现Spring中存在的配置错误,这样有利于检查所依赖属性是否注入。 ApplicationContext启动后预载入所有的单实例Bean,通过预载入单实例bean ,确保当你需要的时候,你就不用等待,因为它们已经创建好了。
相对于基本的BeanFactory,ApplicationContext 唯一的不足是占用内存空间。当应用程序配置Bean较多时,程序启动较慢。
(3)创建方式
BeanFactory通常以编程的方式被创建,ApplicationContext还能以声明的方式创建,如使用ContextLoader。
(4)注册方式
BeanFactory和ApplicationContext都支持BeanPostProcessor、BeanFactoryPostProcessor的使用,但两者之间的区别是:BeanFactory需要手动注册,而ApplicationContext则是自动注册。
最少记住这句:前者是工厂生产bean,容器管理bean。后者是一个特殊bean能产生bean,由前者管理。
BeanFactory:Spring 容器最核心也是最基础的接口,本质是个工厂类,用于管理 bean 的工厂,最核心的功能是加载 bean,也就是 getBean 方法,通常我们不会直接使用该接口,而是使用其子接口。
FactoryBean:该接口以 bean 样式定义,但是它不是一种普通的 bean,它是个工厂 bean,实现该接口的类可以自己定义要创建的 bean 实例,只需要实现它的 getObject 方法即可。
FactoryBean 被广泛应用于 Java 相关的中间件中,如果你看过一些中间件的源码,一定会看到 FactoryBean 的身影。
一般来说,都是通过 FactoryBean#getObject 来返回一个代理类,当我们触发调用时,会走到代理类中,从而可以在代理类中实现中间件的自定义逻辑,比如:RPC 最核心的几个功能,选址、建立连接、远程调用,还有一些自定义的监控、限流等等。
BeanFactory是一个工厂,也就是一个容器,是来管理和生产bean的;
FactoryBean是一个bean,但是它是一个特殊的bean,所以也是由BeanFactory来管理的,它是一个接口,他必须被一个bean去实现。不过FactoryBean不是一个普通的Bean,它会表现出工厂模式的样子,是一个能产生或者修饰对象生成的工厂Bean,里面的getObject()就是用来获取FactoryBean产生的对象。所以在BeanFactory中使用“&”来得到FactoryBean本身,用来区分通过容器获取FactoryBean产生的对象还是获取FactoryBean本身。
拿到原本实例
至少记住这段话:
核心的构建流程如下,也就是 refresh 方法的核心内容:
从概念态—>定义态的过程
1、实例化一个ApplicationContext的对象;
2:调用bean工厂后置处理器完成扫描;
3:循环解析扫描出来的类信息;
4、实例化一个BeanDefinition对象来存储解析出来的信息;
5、把实例化好的beanDefinition对象put到beanDefinitionMap当中缓存起来,
以便后面实例化bean;
6、再次调用其他bean工厂后置处理器;
从定义态到纯净态
7:当然spring还会干很多事情,比如国际化,比如注册BeanPostProcessor等等,如果我们只关心如何实例化一个bean的话那么这一步就是spring调用
finishBeanFactoryInitialization方法来实例化单例的bean,实例化之前spring要做验证, 需要遍历所有扫描出来的类,依次判断这个bean是否Lazy,是否prototype,是否abstract等等;
8:如果验证完成spring在实例化一个bean之前需要推断构造方法,因为spring实 例化对象是通过构造方法反射,故而需要知道用哪个构造方法;
9:推断完构造方法之后spring调用构造方法反射实例化一个对象;注意我这里说 的是对象、对象、对象;这个时候对象已经实例化出来了,但是并不是一个完整的bean,最简单的体现是这个时候实例化出来的对象属性是没有注入,所以不是一个完整的bean;
从纯净态到成熟态
10:spring处理合并后的beanDefinition
11:判断是否需要完成属性注入
12:如果需要完成属性注入,则开始注入属性
初始化
13、判断bean的类型回调Aware接口
14、调用生命周期回调方法
15、如果需要代理则完成代理
创建完成
16、put到单例池——bean完成——存在spring容器当中
概念态到注册态,通过此Bean工场的后置处理器方法对外扩展,对内解耦。
注册完,生产
单例不是懒加载不是抽象才会在ioc加载为你生产
找到就返回,没有就创建
创建呢,首先实例化(反射)
实例化完就到纯净态
然后属性赋值
DI的体现
然后初始化
创建Aop,调其他扩展接口
Bean创建完后就放到Map当中
IOC加载过程从创建一个容器开始,记住四个点:
概念态:配置的Bean,还没有真正的加载
定义态
会解析配置类
根据配置是扫描包,拿到所有.class,看看上面有没有注解。如果有就会
这就是概念态到定义态
下一个就是纯净态(已经实例化完成),还没有进行依赖注入
从定义态到纯净态就是BeanFactory负责生产了,开始生成前判断是否符合标准,判断是不是单例是不是抽象是不是懒加载,如果不是懒加载,不是抽象,是单例,才会在ioc加载时立马生产。
生产过程会去容器看有没有已经创建好,有就返回,没有才创建。
生产过程:1、实例化(反射),得到纯净态的Bean,然后解析DI,注入属性,调回调、Aware接口等对外扩展接口,这俩都是在初始化的时候做的
别忘了初始化时也会创建Aop,当然是需要实现Aop才会去创建Aop动态代理
最后创建完成,放到Map中
至少记住这段话:
refresh方法体现了整个IoC的加载过程
这个方法中注册DeFination
两个扩展接口的方法
没配置就想实现注册用这个
玩一下
BeanDefination就是Bean之前的一个形态
工厂后置处理器
咋玩呢?
其实上一个和这个是子父关系
重写这个方法的原因就是这个
负责Bean生产过程中一切
可以做很多事情
可以拿到定义类去修改
注册单例
销毁Bean
注册的先调用啊
生产过程中的扩展接口
初始化也有一堆扩展接口哈
玩一下
不同的Aware提供的东西也不同
提供名字
提供BeanFactory
刚才的那个扩展接口是在注册并定义的时候调用的
Aware是初始化的时候,调用时机不一样
生命周期回调又怎么玩呢?
每一个都有三种:
初始化
通过注解初始化阶段自动调用
销毁也是三种
总结
最少记住这句话:FileSystemXmlApplicationContext绝对路径加载XML里面的Bean定义,ClassPathXmlApplicationContext类路径加载XML里的Bean定义,WebXmlApplicationContext加载Web应用所有的bean定义,AnnotationConfigApplicationContext加载相应包路径下的标了注解的bean定义
(1)FileSystemXmlApplicationContext :可以加载磁盘路径下的配置文件。此容器从一个XML文件中加载beans的定义,XML Bean 配置文件的全路径名必须提供给它的构造函数。
(2)ClassPathXmlApplicationContext:此容器也从一个XML文件中加载beans的定义,这里,你需要正确设置classpath因为这个容器将在classpath里找bean配置。
(3)WebXmlApplicationContext:此容器加载一个XML文件,此文件定义了一个WEB应用的所有bean。
(4)AnnotationConfigApplicationContext:读取注解创建容器。
最少记住这句话:由容器动态地将依赖关系的对象实例注入到的组件之中。
控制反转IoC是一个很大的概念,可以用不同的方式来实现。其主要实现方式有两种:依赖注入和依赖查找
依赖注入:相对于IoC而言,依赖注入(DI)更加准确地描述了IoC的设计理念。所谓依赖注入(Dependency Injection),即组件之间的依赖关系由容器在应用系统运行期来决定,也就是由容器动态地将某种依赖关系的目标对象实例注入到应用系统中的各个关联的组件之中。组件不做定位查询,只提供普通的Java方法让容器去决定依赖关系。
最少记住这句话:配置对象的工作交由IoC容器负责。
依赖注入的基本原则是:应用组件不应该负责查找资源或者其他依赖的协作对象。配置对象的工作应该由IoC容器负责,“查找资源”的逻辑应该从应用组件的代码中抽取出来,交给IoC容器负责。容器全权负责组件的装配,它会把符合依赖关系的对象通过属性(JavaBean中的setter)或者是构造器传递给需要的对象。
最少记住这句话:不用写查找定位资源的代码,不依赖容器的API,不需要特殊接口。
依赖注入之所以更流行是因为它是一种更可取的方式:让容器全权负责依赖查询,受管组件只需要暴露JavaBean的setter方法或者带参数的构造器或者接口,使容器可以在初始化时组装对象的依赖关系。其与依赖查找方式相比,主要优势为:
(1)查找定位操作与应用代码完全无关。
(2)不依赖于容器的API,可以很容易地在任何容器以外使用应用对象。
(3)不需要特殊的接口,绝大多数对象可以做到完全不必依赖容器。
最少记住这句话:构造器传参注入和set方法注入
依赖注入是时下最流行的IoC实现方式,依赖注入分为接口注入(Interface Injection),Setter方法注入(Setter Injection)和构造器注入(Constructor Injection)三种方式。
(1)其中接口注入由于在灵活性和易用性比较差,现在从Spring4开始已被废弃。
(2)构造器依赖注入:构造器依赖注入通过容器触发一个类的构造器来实现的,该类有一系列参数,每个参数代表一个对其他类的依赖。
(3)Setter方法注入:Setter方法注入是容器通过调用无参构造器或无参static工厂 方法实例化bean之后,调用该bean的setter方法,即实现了基于setter的依赖注入。
最少记住这句话:set方法可部分注入,也可覆盖构造方法注入,顺序是先实例再注入,且能解决循环依赖。构造器方法则反过来。
在Setter注入,可以将依赖项部分注入,构造方法注入不能部分注入,因为调用构造方法如果传入所有的参数就会报错。
如果我们为同一属性提供Setter和构造方法注入,Setter注入将覆盖构造方法注入。但是构造方法注入不能覆盖setter注入值。显然,构造方法注入被称为创建实例的第一选项。
使用setter注入你不能保证所有的依赖都被注入,这意味着你可以有一个对象依赖没有被注入。在另一方面构造方法注入直到你所有的依赖都注入后才开始创建实例。
在构造函数注入,如果A和B对象相互依赖:A依赖于B,B也依赖于A,此时在创建对象的A或者B时,Spring抛出ObjectCurrentlyInCreationException。所以Spring可以通过setter注入,从而解决循环依赖的问题。