Spring 框架(一)
Spring 框架创建的目的是用来替代更加重量级的企业级 Java 技术,简化开发流程。实现了基于 POJO 轻量级和最小侵入式开发,通过依赖注入和面向接口实现了解耦。
IOC
IOC 控制反转是一种重要的概念,是一种解耦的设计思想。它的主要目的是借助第三方(Spring 中的 IOC 容器)实现以来关系的对象之间的解耦(IOC 容器管理对象,你只管使用即可),从而降低代码之间的耦合度。
IOC 容器负责创建对象,将对象连接在一起,配置这些对象,并管理这个对象的整个生命周期,直到被完全销毁。
DI
依赖注入是实现控制反转的一种设计模式,调用类对某一接口实现类的依赖关系又第三方容器注入,以移除调用类对某一接口实现类的依赖。
通过 DI,对象之间的依赖关系由容器在创建对象的时候进行设定,对象无需自行创建或者管理他们的依赖关系,依赖对象将会被自动注入到需要它的对象之中。
IOC 容器
上面提到的 IOC 和 DI 都是抽象的概念,真正的实现就要依靠 IOC 容器。容器中管理的对象我们称之为 Bean,我们上面提到了,IOC 容器会管理 Bean 的生命周期和互相之间的依赖关系。那么这些 Bean 的创建和依赖关系是如何实现的那?
答案是反射技术。Spring 框架通过反射获取类的相关信息(成员变量,类名)等,获取到类的相关信息后,构建出类的实例并完成对象之间依赖关系的管理,最后放到 IOC 容器中。
我们通过图片来看一下 Bean 的创建过程(对象的创建和依赖关系)。
- 根据 Bean 配置信息(注解,XML,代码)在容器内部创建 Bean 定义注册表。
- 根据注册表加载,实例化 Bean,建立 Bean 与 Bean 之间的依赖关系。
- 将这些实例化就绪的 Bean 放入到 Map 缓存池中,等待应用程序调用。
有了上面的知识铺垫后,我觉得你也可以写一个 IOC/DI 容器了。具体可以参考我以前的博文:手写一个 IOC/DI 容器。
在 Spring 中 IOC 容器到底是那个类那?
在 Spring 容器中,Bean 工厂可以简单分为两种:BeanFactory(这是最基础的,面向 Spring 的),ApplicationContext(这是在 BeanFactory 的基础之上,面向使用 Spring 框架的开发这,提供了一系列的功能)。
Bean 的生命周期
我们来看看一个 Bean 从创建到放入容器中到最后销毁要经历那些步骤。
- 容器启动后,通过反射的方式对 scope 为 singleton 且非懒加载的 bean 进行实例化。
- 按照 Bean 定义的配置信息,注入所有的属性。此时 Bean 没有进行初始化。
- 如果 Bean 实现了 BeanNameAware 接口,会回调该接口的 setBeanName() 方法,传入 Bean 的 id,此时该 Bean 就获得了自己在配置文件中的 id。
- 如果 Bean 实现了 BeanFactoryAware 接口,会回调该接口的 setBeanFactory() 方法,传入该 Bean 的 BeanFactory,这样该 Bean 就获得了自己所在的 BeanFactory。
- 如果 Bean 实现了 ApplicationContextAware 接口,会回调该接口的 setApplicationContext() 方法,传入该 Bean 的 ApplicationContext,这样该 Bean 就获得了自己所在的 ApplicationContext。
- 如果有 Bean 实现了 BeanPostProcessor 接口,则会回调该接口的 postProcessBeforeInitialization() 方法。
- 如果 Bean 实现了 InitilizingBean 接口,则会回调该接口的 afterPropertiesSet() 方法。
- 如果 Bean 配置了 init-method 方法,则会执行 init-method 配置的方法。
- 如果有 Bean 实现了 BeanPostProcessor 接口,则会回调该接口的 postProcessAfterInitialization() 方法。
- 经过流程 9 初始化之后就可以正式使用该 Bean 了,对于 scope 为 singleton 的 Bean,Spring 的 IOC 容器会缓存一份该 Bean 的实例,对于 scope 为 prototype 的 Bean,每次调用都会生成一个新的对象,生命周期就交给调用放来管理了,不再是 Spring 容器管理了。
- 容器关闭后,如果 Bean 实现了 DisposableBean 接口,则会回调该接口的 destory() 方法。
- 如果 Bean 配置了 destory-method 方法,则会执行 destory-method 配置的方法,至此,整个 Bean 的生命周期结束。
在详细一点:
- BeanDefinitionReader 读取 Resource 所指向的配置文件资源,然后解析配置文件。配置文件中的每个 会被解析成一个 BeanDefinition 对象,并保存到 BeanDefinitionRegistry 中。
- 容器扫描 BeanDefinitionRegistry 中的 BeanDefinition,对 Bean 进行实例化,使用 BeanWrapper 完成 Bean 属性的设置工作。
- Spring 在 DefaultSingletonBeanRegistry 类中提供了一个用于缓存单例 Bean 的缓存起,它是一个 HashMap,以 BeanName 为键。
Bean 的装配
装配 Bean 的方式
- XML 配置
- 注解
- JavaConfig(4.x 以后这个用的比较多,配合注解使用)
依赖注入方式
- 属性注入(setter 方法),比较灵活方便,这是大多数人的选择。
- 构造函数注入
- 工厂方法注入。
对象之间的关系
对象之间有三种关系:
- 依赖(比较少用,使用 depends-on,依赖的 Bean 初始化后,当前 Bean 才会初始化)。
- 继承(指定 abstract 和 parent 来实现继承关系)
- 引用(使用 ref 就是引用关系)
Bean 的作用域
- 单例
- 原型(多例)
- 与 web 应用相关的 Bean 作用域
- request(每个 request 创建一个对象)
- session(每个 session 创建一个对象)
自动装配的歧义性
一个接口的实现类怎么在注入的时候优先调用某个实现类?
- 使用 @Primary 注解设置为首选注入的 Bean。
- 使用 @Qulifier 注解设置特定名称的 Bean 来限定注入。