1、IoC的理念

IoC全称为Inversion of Control,中文翻译为控制反转,同时还有一个别名叫 依赖注入DI(Dependency Injection)。大多将IoC与DI看作同等概念,也有部分观点认为 依赖注入可以看作IoC的一种实现方式。

在没有Spring的时候,当我们需要依赖某个类或服务时,一般通过new创建一个对象(或者通过ServiceLocator解决直接的依赖耦合),这些都需要主动的去获取需要的对象。

ServiceLocator是通过引入中间代理者消除对象间复杂的耦合关系,并统一管理分散的复杂耦合关系。

IoC的出现就为我们提供了更加简洁的方式,不用主动去构造对象,将“主动获取”转变为“被动接收”,下图即为IoC在整个过程中的所承担的角色
《Spring揭秘》-第二章- 学习记录-小白菜博客
通常情况下,被注入对象会直接依赖于被依赖对象。但是在IoC的场景中,二者通过IoC Service Provider来交互,全部由IoC Service Provider统一管理。控制权由被注入对象转变为了IoC Service Provider 那里。

public class NewsProvider {
	// NewsProvider 为被注入对象
	// INewsListener和INewsPersister为被依赖对象
	private INewsListener newsListener;
	private INewsPersister newsPersister;
}

其实IoC就是这么简单!以前是需要什么自己去拿,现在是需要什么东西别人主动送过来。下图形象的说明了使用IoC模式的前后差别
file

2、IoC(DI)的几种依赖注入的方式

IoC模式最常用的有三种依赖注入方式,分别是构造方法注入(Constructor Injection)、setter方法注入(Setter Injection)以及接口注入(Interface Injection)。

2.1、构造方法注入

构造方法注入即 被注入对象可以通过在其构造方法中声明所依赖对象的参数列表,让IoC容器知道它所需要的依赖对象列表。由于同一个对象是不可能是被构造两次的,因此,被注入对象的构造乃至其整个生命周期,都是由IoC Service Provider来管理的。

IoC Service Provider会检查被注入对象的构造方法,取得它所需要的依赖对象列表,进而为其注入相应的对象。具体代码示例如下

public class NewsProvider {
	// NewsProvider 为被注入对象
	// INewsListener和INewsPersister为被依赖对象
	private INewsListener newsListener;
	private INewsPersister newsPersister;
	
	public NewsProvider(INewsListener newsListener, INewsPersister newsPersister) {
		this.newsListener = newsListener;
		this.newsPersister = newsPersister;
	}
}

构造方法注入的方式比较直观,对象被构造完成后,就进入了就绪装填,可以马上使用。

2.2、setter方法注入

对于JavaBean对象来说,通常会通过setter方法来更改相应的对象属性。所以,当前对象只要为其依赖对象所对应的属性添加setter方法,就可以通过setter方法将相应的依赖对象设置到被注入对象中。代码示例如下

public class NewsProvider {
    // NewsProvider 为被注入对象
    // INewsListener和INewsPersister为被依赖对象
    private INewsListener newsListener;
    private INewsPersister newsPersister;
    
    public void setNewsListener(INewsListener newsListener) {
        this.newsListener = newsListener;
    }
    public void setNewsPersister(INewsPersister newsPersister) {
        this.newsPersister = newsPersister;
    }
}

这样,外界就可以通过调用setNewsListener和setNewsPersister方法来注入依赖对象了。

需要注意的是,setter方法注入不像构造方法注入那样,对象构造完成后即可使用,相对而言更加宽松一些,可以在对象构造完成之后再注入。

2.3、接口注入(基本废弃)

相对前面的两种注入方式来说,接口注入会更加复杂。被注入对象如果想让IoC Service Provider为其注入依赖对象,就必须实现某个接口。这个接口提供一个方法,用来为其注入依赖对象。IoC Service Provider 最终会通过这些接口来获取被注入对象所需要的依赖对象列表。

NewsProvicer为了让IoC Service Provider 为其注入所依赖的NewsListener对象,首先需要实现一个接口 INewsListenerCallable(名称随意),这个接口会声明一个injectNewsListener方法(名称随意),重要的是该方法的参数,必须是所依赖对象的类型。这样,对应的IoC Service Provider 就可以通过这个接口方法将依赖对象注入到 被注入对象 NewsProvider 当中。代码示例如下。

public interface NewsListenerCallable {
    // 声明方法
    void injectNewsListener(INewsListener newsListener);
}

public class NewsProvider implements NewsListenerCallable{
    private INewsListener newsListener;
    @Override
    public void injectNewsListener(INewsListener newsListener) {
        this.newsListener = newsListener;
    }
}

2.3、三种注入方式的比较

  • 构造方法注入。这种注入方式的优点就是,对象在构造完成之后,就已进入就绪状态,可以马上使用。缺点就是,当依赖对象变多时,构造方法的参数列表会比较长。同时通过反射构造对象时,对相同类型的参数处理会比较困难,维护和使用上也比较麻烦。而且在Java中,构造方法无法被继承,无法设置默认值。对于非必须的依赖处理,可能还需要引入多个构造方法,维护不便。
  • setter方法注入优点就是,在描述性上会比构造方法注入要好,同时可以被继承,允许设置默认值,而且有良好的IDE支持。缺点就是对象无法在构造完成后立马进入就绪状态。
  • 接口注入。从注入方式的使用上来说,接口注入是目前不提倡的一种方法,基本处于“退役状态”。因为需要被注入对象实现不必要的接口,带有侵入性。

综上,构造方法注入和setter注入因为其侵入性较弱,且易于理解和使用,所以是现在使用最多的注入方式;而接口注入由于其侵入性,已经不流行了。

本文由博客一文多发平台 OpenWrite 发布!