3.1 依赖注入
IOC,控制翻转(Inversion of Control),又叫依赖注入(Dependency Inject)。即将代码里对象之间的依赖关系转移到容器中,这样就能够很灵活的通过面向接口的编程方式改变真正的实现类。
如下是用代码来维护依赖关系的,如果此时后面有了另外的IUser的实现类,那么如果要使用这个新的实现类则需要修改代码重新set。
而IOC的作用就是讲这些依赖关系的维护从代码里拿出来,通过容器来维护这些关系。一个典型的例子,伪代码如下:
一个典型的上下文配置文件如下:
这样当想要改变实现类时只需要修改配置文件即可。
对于依赖注入,JSR330(Dependency Injection for Java)做了一些规范。该规范主要是面向依赖注入使用者,而对注入器实现、配置并未作详细要求。目前Spring、Guice已经开始兼容该规范。JSR-330规范并未按JSR惯例发布规范文档,只发布了API源码。其指定了获取对象的一种方法,该方法与构造器、工厂以及服务定位器(例如 JNDI)这些传统方法相比可以获得更好的可重用性、可测试性以及可维护性。此方法的处理过程就是依赖注入。
目前市面上常见的IOC框架有以下几个:
Google Guice
PicoContainer
Dagger
SpringFramework
其中,兼容JSR330标准的Guice易用性最好;Pico比较轻量,不过需要手工添加Bean类到容器,用起来有点烦;Dagger使用注解处理工具,其性能非常好,是一种很有前途的DI方案;SpringFramework历史非常悠久,有自己的一套依赖注入体系, 依赖于Spring强大的生态是目前用的最广泛的依赖注入框架,目前已经兼容JSR330规范。
此外,基本所有的IOC框架都支持构造器注入、setter注入以及字段注入三种方式。
3.1.1 JSR330
如果要使用JSR330提供的注解等功能。可引入依赖:
主要提供了以下几个注解和类:
@Inject
注解@Inject标识了可注入的构造器、方法或字段。可以用于静态或实例成员。一个可注入的成员可以被任何访问修饰符(private、package-private、protected、public)修饰。注入顺序为构造器、字段、方法。超类的字段、方法将优先于子类的字段、方法被注入。对于同一个类的字段是不区分注入顺序的,同一个类的方法亦同。如:
@Qualifier
@Qualifier是一个元注解,用来构建自定义限定符,任何人都可以定义新的限定器注解。一个限定器注解如下:
是被@Qualifier、@Retention(RUNTIME)标注的,通常也被@Documented标注。
可以拥有属性。
可能是公共 API 的一部分,就像依赖类型一样,而不像类型实现那样不作为公共 API 的一部分。
如果标注了 @Target 可能会有一些用法限制。本规范只是指定了限定器注解可以被使用在字段和参数上,但一些注入器配置可能使用限定器注解在其他一些地方(例如方法或类)上。
@Named
@Named就是使用上面讲的Qualifier的一个限定器注解。可以指定依赖的组件的名称。
此外,@Named还可以用做标注一个组件。如
Provider
接口Provider用于提供类型T的实列。Provider一般情况是由注入器实现的。对于任何可注入的T而言,都可以注入 Provider。与直接注入T相比,注入 Provider 使得:
可以返回多个实例。
实例的返回可以延迟化或可选
打破循环依赖。
可以在一个已知作用域的实例内查询一个更小作用域内的实例。
@Scope
注解 @Scope是一个元注解,用于标识作用域注解。一个作用域注解是被标识在包含一个可注入构造器的类上的,用于控制该类型的实例如何被注入器重用。缺省情况下,如果没有标识作用域注解,注入器将为每一次注入都创建(通过注入类型的构造器)新实例,并不重用已有实例。如果多个线程都能够访问一个作用域内的实例,该实例实现应该是线程安全的。作用域实现由注入器完成。
@Singleton
@Singleton是基于Scope注解实现的一个作用域注解。表示注入器只实例化一次的类型。该注解不能被继承。如:
3.1.2 Guice
Guice是Google开源的轻量级ioc框架,兼容JSR330规范。其用 Module 來定义所有元件的实际类别。依赖定义部分可以使用JSR330的注解。
3.1.3 PicoContainer
PicoContainer是一个“微核心”(micro-kernel)的容器,它利用了Inversion of Control模式和Template Method模式,提供面向组件的开发、运行环境。PicoContainer是“极小”的容器,只提供了最基本的特性。其最重要的特性是实例化任意对象。这些通过它的API完成,这些API类似于HashMap。向PicoContainer指定java.lang.Class对象,之后能够获得对象实例。如:
不过目前PicoContainer的发展几乎已经停滞。笔者仅仅在Intellij的插件开发中见过对它的使用。
3.1.4 Dagger
Dagger是Google开源的一个框架(早先的版本是由Square创建的,现版本由Google维护),支持Android和Java,现在已经更新到2.0。它使用生成代码实现完整依赖注入的框架(在编译期),极大减少了使用者的编码负担,且其相对于其他大部分IOC框架来说没有使用反射,性能有一定的提升。相对于都出自Google的Guice来说,其更加轻量级,没有Guice中一些相对高级的功能功,如AOP等。
Dagger的依赖注入有自己的一些注解配置,如下:
使用如下:
3.1.5 Spring Framework
Spring的IOC应该是Java开发中最为常用的功能之一。其XML配置的一个例子如下:
使用代码:
此外,从Spring2.0开始引入了对注解的支持,并且后来逐步兼容了JSR330规范。这里简单对比以下Spring自身的依赖注入注解与JSR330。
Spring | Jsr330 | 备注 ----|-----|------|---- @Autowired | @Inject | @Inject注解没有required属性 @Component | @Named | JSR_330标准并没有提供复合的模型,只有一种方式来识别组件 @Scope(“singleton”) | @Singleton | JSR-330默认的作用域类似Spring的prototype,而Spring默认是单例的。如果要使用非单例的作用域,开发者应该使用Spring的@Scope注解。java.inject也提供一个@Scope注解,然而,这个注解仅仅可以用来创建自定义的作用域时才能使用。 @Qualifier | @Qualifier/@Named | javax.inject.Qualifier仅仅是一个元注解,用来构建自定义限定符的。而String的@Qualifier等限定符可以通过javax.inject.Named来实现
除上述的注解外,Spring还有注入@Value、@Required等注解,这在JSR330中都没有对应的东西。
这里需要补充的一点是,Spring的IOC目前早已支持JSR250(common annotations)中提供的注解:Resource、PostConstruct、PreDestroy。这里和Spring自带的注解做一下对比。
Spring | JSR250 | 备注 ----|-----|------|---- @Autowired | @Resource | @Resource是先根据Bean的名称去匹配Bean,获取不到的话再根据类型去匹配;而@Autowired则是根据类型匹配,通过名称则需要Spring的@Qualifier注解的配合。 @PostContruct | init-method | Spring中的XML配置中的init-method可以有同样的作用,即在Bean构造完后做一些初始化动作。@PostContruct具有更高优先级,同时存在的话会先执行。 @PreDesroy | destroy-method | Spring中的XML配置中的destroy-method可以有同样的作用,即在Bean销毁前做一些收尾工作。@PreDesroy注解具有更高优先级,同时存在的话会先执行。
这里需要说明的是,Spring对JSR250的支持的实现是在org.springframework.context.annotation.CommonAnnotationBeanPostProcessor此类中。
3.1.6 循环依赖
IOC中一个常见的问题就是循环依赖,如下:
以上几个IOC框架,对于循环的处理:
通过替换为Provider,并且在构造器或者方法中调用Provider的get方法来打破循环依赖
如果不依赖于Provider,对于构造器中的循环依赖是无法解决的,会抛出异常。
对于方法或字段注入的情况,将其依赖的一边放置到单例作用域中(可以缓存),可以使得循环依赖能够被注入器解析。
Last updated