3. “依赖注入”登场
于是诸多优秀的IT工程师开始想出了更加轻量便利、更加具有可测试性和可维护性的设计模式——IoC模式。IoC,即Inversion of Control的缩写,中文里被称作“控制反转”。至于为什么会有这么一个看似古怪的名字,我们稍后会做解释。2004年著名软件工程学者和工程师Martin Fowler在其论文《Inversion ofControl Containers and the Dependency Injection pattern》中将IoC更名为DependencyInjection,从此依赖注入模式成为实现组件间松耦合的主流设计思想。
3.1. 依赖注入的原理
那么应用了依赖注入,我们的开发模式将会发生什么样的变化呢?我们还是先来看看刚才的例子。下面的代码就是应用了依赖注入之后的储户Depositor类:
|
可以看到,这个Depositor类几乎就是最开始我们定义的那个最简单的、没有任何初始化依赖的过程的类!唯一不同的是在定义依赖成员变量的时候,多加了一个JavaSE5标准的注解——@In。这里@In注解是现今主流依赖注入框架之一JBoss Seam所提供的一个注解,对于其它的依赖注入框架有着同样的使用方式,例如SpringFramework中提供了@Autowired注解等同于Seam的@In,Google Guice框架中提供了@Inject注解等同于Seam的@In。关于依赖注入框架的话题我们稍候再做介绍,现在先来了解一下依赖注入框架的关于依赖注入的实现过程,这里我们还是以Seam框架为例进行介绍。
依赖注入框架的核心是依赖注入容器,也常被称为IoC容器或者叫Injector。其起的作用是管理一切注册到其中的依赖以及将这些依赖提供给请求依赖的依赖者。说到此或许有人会问,那这个依赖注入容器与前一节提到的ServiceLocator容器有什么不一样,不都是一个管理依赖对象的容器吗?答案是非常的不一样。不一样在于,首先依赖注入容器是极其轻量可控的,注册到其中的依赖对象可以方便的由开发者决定其标识符以及注册过程等,并且可以方便的放到单元测试环境中执行;其次依赖注入容器不仅仅是替开发者管理依赖,更重要的是它能够根据需要自动的去管理依赖对象的作用域及其生命周期,以及增强依赖对象的行为(即AOP),这一点我们在后面的章节中还会有详细的介绍。
回到刚才的例子:
|
这里被@In标注的地方被称作“注入点”(injectionpoint),声明注入点起到的作用就是,告诉依赖注入容器“在这里请把依赖对象给我!”,也就是@In向依赖注入容器发出了一个请求,之后容器会利用Java的反射机制将其保管的对象set到注入点,即完成了一次依赖注入。
如果@In注入点所请求的依赖对象自身还有依赖,那么依赖注入容器同样会以同样的方式先去设定好这个“依赖的依赖”,也就是说,依赖注入容器会帮助开发者构建一套完整的依赖关系图,最后将一个完全初始化好的依赖对象提供给依赖者。
严格地讲,依赖注入同样有很多种模式,当遇到诸如循环依赖模式、部分依赖模式等情况时,依赖注入容器并不能将“完全初始化”的对象提供给依赖者。这些情况比较特殊,各种依赖注入框架也有不同的解决方式,我们在此就不再详加讨论了。
至于如何将依赖注册到容器中,每种依赖注入框架都有不同的运用方式,但是基本上都是大同小异,这里我们看一段以Seam框架为例的代码:
|
这里@Name标注的类代表这个类需要作为依赖对象被注册到Seam容器中,在Seam容器随着应用程序启动过程中即可完成这个注册过程。之后注册到Seam容器的这两个依赖就可以在被请求的时候提供给依赖者了。Seam框架中除了@Name标注,还可以用XML文件的<component>标签配置的方式来声明一个需要被注册到容器中的组件类。而如果使用的是Spring框架,则可通过@Component或者XML的<bean>标签等方式来声明组件类;Guice框架中则是通过Module的binding等方式来完成这一过程,总而言之各种依赖注入框架都提供了方便的向其容器去注册依赖的方法。
需要注意的是,注解(如前面举例的@In)并不是定义注入点的唯一方式。一般依赖注入框架还会提供API的方式和XML配置的方式来声明注入点。例如API的方式中,使用Seam框架时调用其Component#getInstance的API,或者使用Spring框架时调用其BeanFactory#getBean的API,或者使用Guice时调用其Injector# getInstance的API等,都是声明注入点的一种方法。再比如XML配置方式中,Seam和Spring框架都提供了<property>标签用于声明依赖者类中的注入点。
我们可以看到,应用了依赖注入设计思想之后,我们的Depositor类就可以以一种最简单的姿态出现在我们的代码库中。对于今后的维护,不管Bank和DepositBook两个依赖类发生了什么样的构造方法的变化,都不会影响到Depositor类本身。并且在单元测试中,我们可以方便的用Mock类替换掉实际的依赖。只需将Mock类绑定为相同的标识符,那么当依赖者请求依赖的时候,容器就会将Mock类提供给依赖者。例如:
|