一, IoC 概念
上一篇已经了解了 spring 的相关概念, 并且创建了一个 spring 项目. spring 中有最重要的两个概念: IoC 和 AOP, 我们先从 IoC 入手.
IoC 全称 Inversion of Control, 中文通常翻译为 "控制反转", 这其实不是一种技术, 而是一种思想.
简单理解就是把原先我们代码里面需要实现的对象创建, 依赖的代码, 反转给容器来帮忙实现.
这里分享 Iteye 的开涛对 IoC 的精彩讲解
地址: https://jinnianshilongnian.iteye.com/blog/1413846
IoC 是什么
IoC-Inversion of Control, 即 "控制反转", 不是什么技术, 而是一种设计思想. 在 Java 开发中, IoC 意味着将你设计好的对象交给容器控制, 而不是传统的在你的对象内部直接控制. 如何理解好 IoC 呢? 理解好 IoC 的关键是要明确 "谁控制谁, 控制什么, 为何是反转(有反转就应该有正转了), 哪些方面反转了", 那我们来深入分析一下:
●谁控制谁, 控制什么: 传统 Java SE 程序设计, 我们直接在对象内部通过 new 进行创建对象, 是程序主动去创建依赖对象; 而 IoC 是有专门一个容器来创建这些对象, 即由 IoC 容器来控制对 象的创建; 谁控制谁? 当然是 IoC 容器控制了对象; 控制什么? 那就是主要控制了外部资源获取(不只是对象包括比如文件等).
●为何是反转, 哪些方面反转了: 有反转就有正转, 传统应用程序是由我们自己在对象中主动控制去直接获取依赖对象, 也就是正转; 而反转则是由容器来帮忙创建及注入依赖对象; 为何是反转? 因为由容器帮我们查找及注入依赖对象, 对象只是被动的接受依赖对象, 所以是反转; 哪些方面反转了? 依赖对象的获取被反转了.
用图例说明一下, 传统程序设计如图 1-1, 都是主动去创建相关对象然后再组合起来:
图 1-1 传统应用程序示意图
当有了 IoC/DI 的容器后, 在客户端类中不再主动去创建这些对象了, 如图 2-2 所示:
图 1-2 有 IoC/DI 容器后程序结构示意图
IoC 能做什么
IoC 不是一种技术, 只是一种思想, 一个重要的面向对象编程的法则, 它能指导我们如何设计出松耦合, 更优良的程序. 传统应用程序都是由我们在类内部主动创建依赖对象, 从而导致类与类之间高耦合, 难于测试; 有了 IoC 容器后, 把创建和查找依赖对象的控制权交给了容器, 由容器进行注入组合对象, 所以对象与对象之间是 松散耦合, 这样也方便测试, 利于功能复用, 更重要的是使得程序的整个体系结构变得非常灵活.
其实 IoC 对编程带来的最大改变不是从代码上, 而是从思想上, 发生了 "主从换位" 的变化. 应用程序原本是老大, 要获取什么资源都是主动出击, 但是在 IoC/DI 思想中, 应用程序就变成被动的了, 被动的等待 IoC 容器来创建并注入它所需要的资源了.
IoC 很好的体现了面向对象设计法则之一 -- 好莱坞法则:"别找我们, 我们找你"; 即由 IoC 容器帮对象找相应的依赖对象并注入, 而不是由对象主动去找.
IoC 和 DI
DI-Dependency Injection, 即 "依赖注入": 组件之间依赖关系由容器在运行期决定, 形象的说, 即由容器动态的将某个依赖关系注入到组件之中. 依赖注入的目的并非为软件系统带来更多功能, 而是为了提升组件重用的频率, 并为系统搭建一个灵活, 可扩展的平台. 通过依赖注入机制, 我们只需要通过简单的配置, 而无需任何代码就可指定目标需要的资源, 完成自身的业务逻辑, 而不需要关心具体的资源来自何处, 由谁实现.
理解 DI 的关键是:"谁依赖谁, 为什么需要依赖, 谁注入谁, 注入了什么", 那我们来深入分析一下:
●谁依赖于谁: 当然是应用程序依赖于 IoC 容器;
●为什么需要依赖:** 应用程序需要 IoC 容器来提供对象需要的外部资源 **;
●谁注入谁: 很明显是 IoC 容器注入应用程序某个对象, 应用程序依赖的对象;
●注入了什么: 就是注入某个对象所需要的外部资源(包括对象, 资源, 常量数据).
IoC 和 DI 由什么关系呢? 其实它们是同一个概念的不同角度描述, 由于控制反转概念比较含糊(可能只是理解为容器控制对象这一个层面, 很难让人想到谁来维护对象关系), 所以 2004 年大师级人物 Martin Fowler 又给出了一个新的名字:"依赖注入", 相对 IoC 而言,"** 依赖注入" 明确描述了 "被注入对象依赖 IoC 容器配置依赖对象".
相信通过上面的文章, 对 IoC 的理解会更深. 下面讲讲三种依赖注入的方式
构造方法注入
顾名思义, 构造方法注入, 就是被注入对象可以通过在其构造方法中声明依赖对象的参数列表, 让外部 (通常是 IoC 容器) 知道它需要哪些依赖对象
- public classA(IinterfaceA a,IinterfaceB b){
- this.a=a;
- this.b=b;
- }
构造方法注入方式比较直观, 对象被构造完成后, 即进入就绪状态, 可以马上使用.
setter 方法注入
对于 JavaBean 对象来说, 通常会通过 setXXX()和 getXXX()方法来访问对应属性. 这些 setXXX()方法统称为 setter 方法, getXXX()当然就称为 getter 方法.
- public class classB(){
- private IinterfaceA a;
- private IinterfaceB b;
- public IinterfaceA getIinterfaceA(){
- return a;
- }
- public void setIinterfaceA(IinterfaceA a){
- this.a=a;
- }
- public IinterfaceB getIinterfaceB(){
- return b;
- }
- public void setIinterfaceB(IinterfaceB b){
- this.b=b;
- }
- }
接口注入
相对于前两种注入方式来说, 接口注入没有那么简单明了. 被注入对象如果想要 IoC Service Provider 为其注入依赖对象, 就必须实现某个接口. 这个接口提供一个方法, 用来为其注入依赖对象. IoC Service Provider 最终通过这些接口来了解应该为被注入对象注入什么依赖对象.
创建 Person (被注入对象)要实现的接口
- interface UserInject{
- void injectUser(User user);// 这里必须 是被注入对象依赖的对象
- }
Person 对象实现接口
- class Person implements UserInject{
- private User user; public Person(){}
- @Override
- public void injectUser(User user)
- {
- this.user = user;// 实现注入方法, 外部通过此方法给此对象注入 User 对象
- }
- }
外部调 injectUser 方法为 Persion 对象注入 User 对象, 此即接口注入
三种注入方式的比较
接口注入. 从注入方式的使用上来说, 接口注入是现在不甚提倡的一种方式, 基本处于 "退役状态". 因为它强制被注入对象实现不必要的接口, 带有侵入性. 而构造方法注入和 setter 方法注入则不需要如此.
构造方法注入. 这种注入方式的优点就是, 对象在构造完成之后, 即已进入就绪状态, 可以 9 马上使用. 缺点就是, 当依赖对象比较多的时候, 构造方法的参数列表会比较长. 而通过反射构造对象的时候, 对相同类型的参数的处理会比较困难, 维护和使用上也比较麻烦. 而且在 Java 中, 构造方法无法被继承, 无法设置默认值. 对于非必须的依赖处理, 可能需要引入多个构造方法, 而参数数量的变动可能造成维护上的不便.
setter 方法注入. 因为方法可以命名, 所以 setter 方法注入在描述性上要比构造方法注入好一些. 另外, setter 方法可以被继承, 允许设置默认值, 而且有良好的 IDE 支持. 缺点当然就是对象无法在构造完成后马上进入就绪状态.
综上所述, 构造方法注入和 setter 方法注入因为其侵入性较弱, 且易于理解和使用, 所以是现在使用最多的注入方式; 而接口注入因为侵入性较强, 近年来已经不流行了.
二, 源码分析
在学习 spring 的具体配置之前, 先了解下源码的基本结构. 上一篇的测试代码
- ApplicationContext ctx=new ClassPathXmlApplicationContext("META-INF/applicationContext.xml");
- // 获取 bean 的实例
- HelloWorld t=(HelloWorld) ctx.getBean("hello");
我们大致分析下过程:
通过 Resource 对象加载配置文件
解析配置文件, 得到 bean
解析 bean,id 作为 bean 的名字, class 用于反射得到 bean 的实例(Class.forName(className));
调用 getBean 的时候, 从容器中返回对象实例.
当然这只是简单的理解, IoC 核心内容是 beanFactory 与 ApplicationContext
BeanFactory
BeanFactory 是 Spring 的 "心脏". 它就是 Spring IoC 容器的真面目. Spring 使用 BeanFactory 来实例化, 配置和管理 Bean,BeanFactory 有着庞大的继承, 实现体系, 有众多的子接口, 实现类.
BeanFactory 作为一个主接口不继承任何接口, 暂且称为一级接口.
有 3 个子接口继承了它, 进行功能上的增强. 这 3 个子接口称为二级接口.
ConfigurableBeanFactory 可以被称为三级接口, 对二级接口 HierarchicalBeanFactory 进行了再次增强, 它还继承了另一个外来的接口 SingletonBeanRegistry
ConfigurableListableBeanFactory 是一个更强大的接口, 继承了上述的所有接口, 无所不包, 称为四级接口.(这 4 级接口是 BeanFactory 的基本接口体系. 继续, 下面是继承关系的 2 个抽象类和 2 个实现类:)
AbstractBeanFactory 作为一个抽象类, 实现了三级接口 ConfigurableBeanFactory 大部分功能.
AbstractAutowireCapableBeanFactory 同样是抽象类, 继承自 AbstractBeanFactory, 并额外实现了二级接口 AutowireCapableBeanFactory
DefaultListableBeanFactory 继承自 AbstractAutowireCapableBeanFactory, 实现了最强大的四级接口 ConfigurableListableBeanFactory, 并实现了一个外来接口 BeanDefinitionRegistry, 它并非抽象类.
最后是最强大的 XmlBeanFactory, 继承自 DefaultListableBeanFactory, 重写了一些功能, 使自己更强大.
最基本的 IoC 容器接口 BeanFactory
- public interface BeanFactory {
- /**
- * 用来引用一个实例, 或把它和工厂产生的 Bean 区分开, 就是说, 如果一个 FactoryBean 的名字为 a, 那么,&a 会得到那个 Factory
- */
- String FACTORY_BEAN_PREFIX = "&";
- /*
- * 四个不同形式的 getBean 方法, 获取实例
- */
- // 根据 bean 的名字, 获取在 IoC 容器中得到 bean 实例
- Objecpublic interface BeanFactory {
- /**
- * 用来引用一个实例, 或把它和工厂产生的 Bean 区分开, 就是说, 如果一个 FactoryBean 的名字为 a, 那么,&a 会得到那个 Factory
- */
- String FACTORY_BEAN_PREFIX = "&";
- /*
- * 四个不同形式的 getBean 方法, 获取实例
- */
- // 根据 bean 的名字, 获取在 IoC 容器中得到 bean 实例
- Object getBean(String name) throws BeansException;
- // 根据 bean 的名字和 Class 类型来得到 bean 实例, 增加了类型安全验证机制.
- <T> T getBean(String name, Class<T> requiredType) throws BeansException;
- <T> T getBean(Class<T> requiredType) throws BeansException;
- Object getBean(String name, Object... args) throws BeansException;
- // 是否存在
- boolean containsBean(String name);
- // 是否为单实例
- boolean isSingleton(String name) throws NoSuchBeanDefinitionException;
- // 是否为原型(多实例)
- boolean isPrototype(String name) throws NoSuchBeanDefinitionException;
- // 名称, 类型是否匹配
- boolean isTypeMatch(String name, Class<?> targetType)
- throws NoSuchBeanDefinitionException;
- // 得到 bean 实例的 Class 类型
- Class<?> getType(String name) throws NoSuchBeanDefinitionException;
- String[] getAliases(String name);// 根据实例的名字获取实例的别名 getBean(String name) throws BeansException;
- // 根据 bean 的名字和 Class 类型来得到 bean 实例, 增加了类型安全验证机制.
- <T> T getBean(String name, Class<T> requiredType) throws BeansException;
- <T> T getBean(Class<T> requiredType) throws BeansException;
- Object getBean(String name, Object... args) throws BeansException;
- // 是否存在
- boolean containsBean(String name);
- // 是否为单实例
- boolean isSingleton(String name) throws NoSuchBeanDefinitionException;
- // 是否为原型(多实例)
- boolean isPrototype(String name) throws NoSuchBeanDefinitionException;
- // 名称, 类型是否匹配
- boolean isTypeMatch(String name, Class<?> targetType)
- throws NoSuchBeanDefinitionException;
- // 得到 bean 实例的 Class 类型
- Class<?> getType(String name) throws NoSuchBeanDefinitionException;
- String[] getAliases(String name);// 根据实例的名字获取实例的别名
BeanFactory 接口只是做了最基本的定义, 里面不管如何定义和加载, 只关心如何得到对象, 要知道如何得到对象, 必须看具体的实现类, 其中 XmlBeanFactory 就是针对最基本的 IoC 容器的实现.
- public class XmlBeanFactory extends DefaultListableBeanFactory {
- private final XmlBeanDefinitionReader reader;
- public XmlBeanFactory(Resource resource) throws BeansException {
- this(resource, (BeanFactory)null);
- }
- public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) throws BeansException {
- super(parentBeanFactory);
- this.reader = new XmlBeanDefinitionReader(this);
- this.reader.loadBeanDefinitions(resource);
- }
- }
使用:
- // 根据 xml 配置文件创建 Resource 资源对象, 该对象中包含了 BeanDefinition 的信息
- Resource resource = new ClassPathResource("META-INF/applicationContext.xml");
- // 创建 XmlBeanDefinitionReader 读取器, 用于载入 BeanDefinition. 之所以需要 BeanFactory 作为参数, 是因为会将读取的信息回调配置给 factory
- BeanFactory beanFactory = new XmlBeanFactory(resource);
- HelloWorld helloWorld = beanFactory.getBean("hello",HelloWorld.class);
- System.out.println(helloWorld.getInfo());
- ApplicationContext
ApplicationContext 是 Spring 提供的一个高级的 IoC 容器, 它除了能够提供 IoC 容器的基本功能外, 还为用户提供了以下的附加服务.
支持信息源, 可以实现国际化.(实现 MessageSource 接口)
访问资源.(实现 ResourcePatternResolver 接口)
支持应用事件.(实现 ApplicationEventPublisher 接口)
两者的区别
1.BeanFactroy 采用的是延迟加载形式来注入 Bean 的, 即只有在使用到某个 Bean 时(调用 getBean()), 才对该 Bean 进行加载实例化, 这样, 我们就不能发现一些存在的 Spring 的配置问题. 而 ApplicationContext 则相反, 它是在容器启动时, 一次性创建了所有的 Bean. 这样, 在容器启动时, 我们就可以发现 Spring 中存在的配置错误. 相对于基本的 BeanFactory,ApplicationContext 唯一的不足是占用内存空间. 当应用程序配置 Bean 较多时, 程序启动较慢.
BeanFacotry 延迟加载, 如果 Bean 的某一个属性没有注入, BeanFacotry 加载后, 直至第一次使用调用 getBean 方法才会抛出异常; 而 ApplicationContext 则在初始化自身是检验, 这样有利于检查所依赖属性是否注入; 所以通常情况下我们选择使用 ApplicationContext. 应用上下文则会在上下文启动后预载入所有的单实例 Bean. 通过预载入单实例 bean , 确保当你需要的时候, 你就不用等待, 因为它们已经创建好了.
2.BeanFactory 和 ApplicationContext 都支持 BeanPostProcessor,BeanFactoryPostProcessor 的使用, 但两者之间的区别是: BeanFactory 需要手动注册, 而 ApplicationContext 则是自动注册.(Applicationcontext 比 beanFactory 加入了一些更好使用的功能. 而且 beanFactory 的许多功能需要通过编程实现而 Applicationcontext 可以通过配置实现. 比如后处理 bean , Applicationcontext 直接配置在配置文件即可而 beanFactory 这要在代码中显示的写出来才可以被容器识别. )
3.beanFactory 主要是面对与 spring 框架的基础设施, 面对 spring 自己. 而 Applicationcontex 主要面对与 spring 使用的开发者. 基本都会使用 Applicationcontex 并非 beanFactory .
spring 的 IoC 实现当然不止这些, 这些以后再学, 推荐一篇大牛写的博客: https://www.cnblogs.com/ITtangtang/p/3978349.html
看完这些相信对 spring IoC 概念及其实现会有了一些理性认识了, 这里面参考了很多园子里大神的文字, 下一篇开始学习 spring 的配置
来源: https://www.cnblogs.com/yuanqinnan/p/10301499.html