# 3.1 依赖注入

IOC，控制翻转（Inversion of Control），又叫依赖注入（Dependency Inject）。即将代码里对象之间的依赖关系转移到容器中，这样就能够很灵活的通过面向接口的编程方式改变真正的实现类。

如下是用代码来维护依赖关系的，如果此时后面有了另外的IUser的实现类，那么如果要使用这个新的实现类则需要修改代码重新set。

```
public interface IUser{
    void say();
}

public class AdminUser implements IUser{
    public void say(){
        System.out.println("I'm admin");
    }
}

public class IOCTest{
    private IUser user;
    
    public void setUser(IUser user){
        this.user = user;
    }
    
    public IUser getUser(){
        return this.user;
    }
    
    public void test(){
        this.user.say();
    }
    
    public static void main(String[] args){
        IOCTest test = new IOCTest();
        test.setUser(new AdminUser());
        
        test.test();
    }
}
```

而IOC的作用就是讲这些依赖关系的维护从代码里拿出来，通过容器来维护这些关系。一个典型的例子，伪代码如下：

```
IOCContainer container = new IOCContainer("..");//可以通过一个上下文配置文件，也可以通过扫描注解
    
IOCTest iocTest = container.getInstance("iocTest");
iocTes.test();
```

一个典型的上下文配置文件如下：

```
<bean class="IOCTest" name="iocTest">
   <property name="user" ref="realUser"/>
</bean>
<bean class="AdminUser" name="realUser" />
```

这样当想要改变实现类时只需要修改配置文件即可。

对于依赖注入，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提供的注解等功能。可引入依赖:

```
<dependency>
  <groupId>javax.inject</groupId>
  <artifactId>javax.inject</artifactId>
  <version>1</version>
</dependency>
```

主要提供了以下几个注解和类：

1. @Inject

   注解@Inject标识了可注入的构造器、方法或字段。可以用于静态或实例成员。一个可注入的成员可以被任何访问修饰符（private、package-private、protected、public）修饰。注入顺序为构造器、字段、方法。超类的字段、方法将优先于子类的字段、方法被注入。对于同一个类的字段是不区分注入顺序的，同一个类的方法亦同。如：

   ```
   public class IOCTest {

      private IUser user;

      @Inject
      public void setUser(IUser user) {
          this.user= user;
      }
   }
   ```
2. @Qualifier

   @Qualifier是一个元注解，用来构建自定义限定符，任何人都可以定义新的限定器注解。一个限定器注解如下：

   * 是被@Qualifier、@Retention（RUNTIME）标注的，通常也被@Documented标注。
   * 可以拥有属性。
   * 可能是公共 API 的一部分，就像依赖类型一样，而不像类型实现那样不作为公共 API 的一部分。
   * 如果标注了 @Target 可能会有一些用法限制。本规范只是指定了限定器注解可以被使用在字段和参数上，但一些注入器配置可能使用限定器注解在其他一些地方（例如方法或类）上。
3. @Named

   @Named就是使用上面讲的Qualifier的一个限定器注解。可以指定依赖的组件的名称。

   ```
   public class IOCTest {

        private IUser user;
          
        @Inject
        public void setUser(@Named("adminUser") IUser user) {
            this.user= user;
        }
   }
   ```

   此外，@Named还可以用做标注一个组件。如

   ```
   @Named
   public class AdminUser implements IUser{
      public void say(){
          System.out.println("I'm admin").
      }
   }
   ```
4. Provider

   接口Provider用于提供类型T的实列。Provider一般情况是由注入器实现的。对于任何可注入的T而言，都可以注入 Provider。与直接注入T相比，注入 Provider 使得：

   * 可以返回多个实例。
   * 实例的返回可以延迟化或可选
   * 打破循环依赖。
   * 可以在一个已知作用域的实例内查询一个更小作用域内的实例。

   ```
   public class IOCTest {

     private IUser user;
       
     @Inject
     public void setUser(Provider<IUser> user) {
         this.user= user;
     }
   }
   ```
5. @Scope

   注解 @Scope是一个元注解，用于标识作用域注解。一个作用域注解是被标识在包含一个可注入构造器的类上的，用于控制该类型的实例如何被注入器重用。缺省情况下，如果没有标识作用域注解，注入器将为每一次注入都创建（通过注入类型的构造器）新实例，并不重用已有实例。如果多个线程都能够访问一个作用域内的实例，该实例实现应该是线程安全的。作用域实现由注入器完成。
6. @Singleton

   @Singleton是基于Scope注解实现的一个作用域注解。表示注入器只实例化一次的类型。该注解不能被继承。如：

   ```
   @Singleton 
   public class AdminUser implements IUser{
       public void say(){
           System.out.println("I'm admin").
       }
   }
   ```

## 3.1.2 Guice

Guice是Google开源的轻量级ioc框架，兼容JSR330规范。其用 Module 來定义所有元件的实际类别。依赖定义部分可以使用JSR330的注解。

```
public class IOCTest {

  private IUser user;
    
  @Inject
  public void setUser(IUser user) {
      this.user= user;
  }
}

public class IOCTestModule extends AbstractModule {
   @Override 
   protected void configure() {
       bind(IUser.class).to(AdminUser.class);
       bind(IOCTest.class).to(IOCTest.class);
   }
}

public static void main(String[] args) {
   Injector injector = Guice.createInjector(new IOCTestModule());
   IOCTest test = injector.getInstance(IOCTest.class);
   ...
}
```

## 3.1.3 PicoContainer

PicoContainer是一个“微核心”（micro-kernel）的容器,它利用了Inversion of Control模式和Template Method模式，提供面向组件的开发、运行环境。PicoContainer是“极小”的容器，只提供了最基本的特性。其最重要的特性是实例化任意对象。这些通过它的API完成，这些API类似于HashMap。向PicoContainer指定java.lang.Class对象，之后能够获得对象实例。如：

```
MutablePicoContainer pico = new DefaultPicoContainer();  

pico.addComponent(AdminUser.class); //通过class注册 
pico.addComponent(new AdminUser()) //通过type注册
pico.addComponent(IOCTest.class); 
    
IUser user = (IUser) pico.getComponent(AdminUser.class);  
IOCTest test = (IOCTest)pico.getComponent(IOCTest.class); 
```

不过目前PicoContainer的发展几乎已经停滞。笔者仅仅在Intellij的插件开发中见过对它的使用。

## 3.1.4 Dagger

Dagger是Google开源的一个框架（早先的版本是由Square创建的，现版本由Google维护），支持Android和Java，现在已经更新到2.0。它使用生成代码实现完整依赖注入的框架（在编译期），极大减少了使用者的编码负担，且其相对于其他大部分IOC框架来说没有使用反射，性能有一定的提升。相对于都出自Google的Guice来说，其更加轻量级，没有Guice中一些相对高级的功能功，如AOP等。

Dagger的依赖注入有自己的一些注解配置，如下：

```
public class IOCTest {

  private IUser user;
    
  @Inject
  public void setUser(IUser user) {
      this.user= user;
  }
}
    
@Module
public class TestModule {   
   @Provides IUser provideUser() {       
       return new AdminUser();    
   }
}
    
@Component(modules = TestModule.class)
public interface TestComponent {    
   void inject(IOCTest test);
}
```

使用如下：

```
IOCTest test = new IOCTest();
TestComponent component = DaggerActivityComponent.builder().activityModule(new TestModule()).build();                
component.inject(test);
test.test();
...
```

## 3.1.5 Spring Framework

Spring的IOC应该是Java开发中最为常用的功能之一。其XML配置的一个例子如下：

```
<!--applicationContext.xml-->
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:p="http://www.springframework.org/schema/p"
       xmlns:c="http://www.springframework.org/schema/c"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
	http://www.springframework.org/schema/beans/spring-beans.xsd">
    <bean class="IOCTest" id="iocTest">
        <property name="user" ref="realUser"/>
    </bean>
    <bean class="AdminUser" id="realUser" />
</beans>
```

使用代码：

```
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xm");
IOCTest iocTest = context.getBean(IOCTest.class);
iocTes.test();
```

此外，从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中一个常见的问题就是循环依赖，如下：

```
class TestA {
   @Inject TestB b;
}
    
class TestB {
   @Inject TestC c;
}
    
class TestC {
   @Inject TestA a;
} 
```

以上几个IOC框架，对于循环的处理：

* 通过替换为Provider，并且在构造器或者方法中调用Provider的get方法来打破循环依赖
* 如果不依赖于Provider，对于构造器中的循环依赖是无法解决的，会抛出异常。
* 对于方法或字段注入的情况，将其依赖的一边放置到单例作用域中（可以缓存），可以使得循环依赖能够被注入器解析。
