依赖对象的获得被反转了
UserDao userDao = new UserDaoImpl(); 复制代码
这种方式对于 UserSerivce 来说,它是依赖了 UserDao。但是直接 new 对象有什么不好吗?好,这是教科书般的写法!但是这种写法会导致高度耦合度。当 UserService 想换一个 UserDao 的实现类的时候,它需要进行代码的更换。如果一个企业级别的项目已经有一百个地方需要更改的话,相信你会疯掉。
回到 Martin Fowler 说的那句精髓。如果依赖的对象被反转了,那么相对 UserService 来说,UserDao 是被反转了。
而怎么反转呢?因为 new 是主动的表现,那么反转你可以理解为被动接收!所谓的被动也就是在某个时刻别人把东西送到我们而不用我们主动去拿。相信大家都听说监听者模式。监听者模式是等待广播器进行广播事件的;又或者是队列,消费者是等待生产者推送消息的。这两个例子都可以理解为被动接收!
或许你会说,那么 IOC 是不是监听者设计模式呢?其实它们仅仅是交集,它们都有被动接收的异曲同工之妙而已。当然你也可以认为,所有这种具有“被动”意义的设计理念都有些 IOC 的影子。
更加,上面的内容说明了 IOC 是实打实一种面向对象编程的一种设计原则,可以用于降低计算机代码之间的耦合度。
其实,对于上面的策略来说,被实现和体现的是我们现在依旧流行的 Spring 和传说中的重量级框架 EJB。现在我们来讲一下 IOC 的依赖注入和依赖查找。依赖查找的意思是,应用通过一个框架性质的上下文来进行 IOC 的实现,然后我们可以通过这个上下文容器去获得查找自己想要的依赖;而依赖注入是说,组建不需要去查找自己需要的依赖,而是上下文容器会通过写入 Java Bean 的参数或构造器来将实现依赖注入。
上面是一些抽象性的描述,我们来看一下它比较明确的实现描述。IOC 的职责有以下几点:
上面有一点需要说明的,就是“Java Beans 或其他资源”。对于 IOC 容器来说,它负责解决的不仅仅是 Bean,还有一些非 Bean,例如说配置资源例如说在 IOC 发生事件的时候外部添加进去的,又或者是 Spring 重的事件监听,这些无法通过依赖注入或查找来进行管理的资源。所以不要把 IOC 容器的职责发布范围仅仅局限于 Java Bean。
相同,在“配置”一栏中,托管资源属于“其他资源”的话,也有可能是 XML 和注解信息等资源信息;而外部化配置呢,是将通过配置文件来影响 IOC 的运行行文的一种方式。
不仅限于 Java Bean 是 IOC 考虑周全的一种表现。
IOC 让人最深刻的实现其实就是 Spring。当面试官问“什么是 IOC”,几乎大部分 Java 面试者或多或少都会说到 Spring,几乎来说是“根深蒂固”的感觉。
其实对于有关 IOC 的问题,我觉得应该从其历史演变性来谈的话,才是最迷人的。例如说,IOC 的早期在 Java SE 上的实现已经有了:
而在 Java EE 的时候,有了:
再到开源企业级应用的时候:
我挑几个来回答一下。
我们回想 SPI 的机制,我们会发现其实当我们使用 ServiceLoader.load(Class.class) 进行调用的时候,Java 是不是已经自动搬我们将在 META-INF 下面的 Bean 加载进行来?实质上 Java 已经将实例化与调用进行了分离。而我们“主动去调用”这个动作算是 IOC “依赖查找”的这种方式。
我问过身边挺多人的,其实很多人都没想过这个问题。其实我在刚开始学习 Java 的时候就想过一个问题:servlet 的 request 和 response 从哪里来的?
但是现在一旦用 IOC 来想的话,是不是有内味儿?有人会说,不是 Tomcat 的容器传进来的吗?对,不够严谨的说就是 Tomcat 传进来的。当时主要是传进来的这个过程,servlet 并不关心于传进来的具体实现,也不用关注需要什么时候去 new 一个。servlet 仅仅需要知道,它被调用的时候有这个对象即可。
♥ 像企业级别的开源 IOC 实现就不需要多说了。从中我们可见这个十几二十年来,IOC 也不算地创新不断地前进,值得我们去学习。
总结一些比较常见的 IOC 在 Spring 的策略:
策略 | 体现 |
---|---|
依赖注入 | @Autowire / 构造器 / Getter |
依赖查找 | 直接通过 BeanFactory 进行 getBean 操作 |
接口注入 | Aware 接口 / BeanPostProcessor 接口 |
服务定位模式 | spring.factories |
看到表格,其实我们能发现我们最常用的是依赖注入和 依赖查找。两者更进一步的话,我们会发现两者很大概率我们会使用依赖注入。或许你会有疑问,既然有了注入为什么还要查找呢?列举一个它们两的区别图[下面使用简称代替中文名称]:
类型 | 依赖处理 | 便利性 | 侵入性 | API 依赖性 | 可读性 |
---|---|---|---|---|---|
DS | 主动 | 相对繁琐 | 侵入业务逻辑 | 依赖容器 API | 良好 |
DI | 被动 | 相对便利 | 低侵入性 | 不依赖容器 API | 一般 |
我解释一下,一般来说 DS 一般来说至少有一句代码 container.getBean("beanName") 来获取 Bean,这样子就是算是侵入业务逻辑了,但是这样子可以比较明确清楚我们想要哪个 bean,所以可读性比较好利于排查问题;而 DI 一般来说是 IOC 容器根据规则来注入的,所以如果发生注入前被修改或者注入了其他 Bean,是相对来说比较难定位问题的,所以其虽然便利,低侵入性,但是可读性一般。
在 Spring 中,BeanFactory 和 ApplicationContext 是 IOC 容器的实现。如果你看过 BeanFactory 的 API,你会发现它有通过姓名或类型来查找 Bean 的 getBean() / 还有判断是否是单例 isSingleton() / 判断是否匹配 isTypeMatch() 等等方法,是比较典型的一个 IOC 容器。但是 ApplicationContext 为什么也是 IOC 容器呢?在 Spring 官档中,ApplicationContext 被描述为 BeanFactory 的子类,而 ApplicationContext 在 BeanFactory 基础上增多了更多的特性例如 AOP 的整合 / 事务的发布 / 国际化等等。
所以如果当我们被问起 BeanFactory 和 ApplicationContext 的区别或者谁才是Spring IoC容器 的时候,我们可以回答 BeanFactory 是 IOC 容器,但是它仅仅是提供了一个非常基础功能的容器;而 ApplicationContext 是包含了 BeanFactory,而且在此基础上增加了更多符合企业级应用的特性。它们两者是包含关系。但是注意一点,当你的应用并不需要这些特性的时候,你可以直接使用 BeanFactory 来完成你的需求,这样更简洁更轻量,这也是 Spring 在 IOC 的层次组合上的优势。
对于 IOC 的数据来源,作了以下的总结:
我们说了这么多关于 IOC 的策略和依赖注入类型,但是唯独没有讲到 IOC 容器中的元信息是从哪里来的!下面,我们根据 Spring 的情况来做几个维度的讲述
来源方向 | 来源途径 |
---|---|
Bean 定义配置 | 基于 XML / Properties / Java 注解/ Java API |
IOC 容器配置 | 基于 XML / Java 注解 / Java API |
外部化属性配置 | 基于 Java 注解 |
上面说明一下, Bean 定义配置讲的是需要进行依赖的 Bean,这个一般会影响到你业务行为;IOC 容器配置是指通过配置参数来影响容器的行为;外部化属性配置这个是指通过外部配置文件来配置元信息,它可以影响 Bean 的行为。