文章目录

  • 1. 什么是依赖注入?
  • 2. 依赖注入的方式有几种?
  • 2.1 构造方法注入
  • 2.2 setter 方法注入
  • 2.3 接口注入
  • 3. 你建议使用哪种依赖注入方式?


1. 什么是依赖注入?

这个问题和 什么是 IoC? 是一样的。

IoC 的全称为Inversion of Control,中文通常翻译为“控制反转”,它还有一个别名叫做依赖注入(Dependency
Injection)。那么,为什么需要IoC?IoC的具体意义是什么?它到底有什么独到之处?

public class FXNewsProvider {
   private IFXNewsListener newsListener = new DowJonesNewsListener();
   private IFXNewsPersister newPersistener = new DowJonesNewsPersister();
   public void getAndPersistNews() {
      String[] newsIds = newsListener.getAvailableNewsIds();
      if(ArrayUtils.isEmpty(newsIds)) {
         return;
      }
      for(String newsId : newsIds) {
         FXNewsBean newsBean = newsListener.getNewsByPK(newsId);
         newPersistener.persistNews(newsBean);
         newsListener.postProcessIfNecessary(newsId);
      }
   }
}

看,这就是我们通常的做事方式!如果我们依赖于某个类或服务,最简单而有效的方式就是直接在类的构造函数中新建相应的依赖类。

这就好比要装修新房,需要用家具,这个时候,根据通常解决对象依赖关系的做法,我们就会直接打造出需要的家具来(通过new构造对象)。我们需要自己主动地去获取依赖的对象!

可是回头想想,我们自己每次用到什么依赖对象都要主动地去获取,这是否真的必要?我们最终所要做的,其实就是直接调用依赖对象所提供的某项服务而已。只要用到这个依赖对象的时候,它能够准备就绪,我们完全可以不管这个对象是自己找来的还是别人送过来的。对于FXNewsProvider来说,那就是在getAndPersistNews()方法调用newsListener的相应方法时,newsListener能够准备就绪就可以了。如果有人能够在我们需要时将某个依赖对象送过来,为什么还要大费周折地自己去折腾?

实际上,IoC就是为了帮助我们避免之前的“大费周折”,而提供了更加轻松简洁的方式。它的反转,就反转在让你从原来的事必躬亲,转变为现在的享受服务。你想啊,原来还得鞍马劳顿,什么东西都得自己去拿。现在是用什么,让别人直接送过来就成。所以,简单点儿说,IoC的理念就是,让别人为你服务!在图2-1中,也就是让IoC Service Provider来为你服务!

java 依赖指定目录下的jar_ide


通常情况下,被注入对象会直接依赖于被依赖对象。但是,在IoC的场景中,二者之间通过IoC Service Provider来打交道,所有的被注入对象和依赖对象现在由IoC Service Provider统一管理。被注入对象需要什么,直接跟IoC Service Provider招呼一声,后者就会把相应的被依赖对象注入到被注入对象中,从而达到IoC Service Provider为被注入对象服务的目的。

IoC Service Provider在这里就是通常的IoC容器所充当的角色。从被注入对象的角度看,与之前直接寻求依赖对象相比,依赖对象的取得方式发生了反转,控制也从被注入对象转到了IoC Service Provider那里。

其实IoC就这么简单!原来是需要什么东西自己去拿,现在是需要什么东西就让别人送过来。图2-2以两种场景,形象地说明了使用IoC模式前后的差别。

java 依赖指定目录下的jar_java_02


出门之前得先穿件外套吧?以前,你得自己跑到衣柜前面取出衣服这一依赖对象,然后自己穿上再出门。而现在,你只要跟你的“另一半”使个眼色或说一句“Honey,衣服拿来。”她就会心领神会地到衣柜那里为你取出衣服,然后再给你穿上。现在,你就可以出门了。(此时此刻,你心里肯定窃喜,“有人照顾的感觉真好!”)对你来说,到底哪种场景比较惬意,我想已经不言自明了吧?

2. 依赖注入的方式有几种?

问题:依赖注入的方式有哪几种?哪种更好?

“伙计,来杯啤酒!”当你来到酒吧,想要喝杯啤酒的时候,通常会直接招呼服务生,让他为你送来一杯清凉解渴的啤酒。同样地,作为被注入对象,要想让IoC Service Provider为其提供服务,并将所需要的被依赖对象送过来,也需要通过某种方式通知对方。

  • 如果你是酒吧的常客,或许你刚坐好,服务生已经将你最常喝的啤酒放到了你面前;
  • 如果你是初次或偶尔光顾,也许你坐下之后还要招呼服务生,“Waiter,Tsingdao, please.”;
  • 还有一种可能,你根本就不知道哪个牌子是哪个牌子,这时,你只能打手势或干脆画出商标
    图来告诉服务生你到底想要什么了吧!

不管怎样,你终究会找到一种方式来向服务生表达你的需求,以便他为你提供适当的服务。那么,在IoC模式中,被注入对象又是通过哪些方式来通知IoC Service Provider为其提供适当服务的呢?

IoC模式最权威的总结和解释,应该是Martin Fowler的那篇文章“Inversion of Control Containers and the Dependency Injection pattern”,其中提到了三种依赖注入的方式,即构造方法注入(constructor injection)、setter方法注入(setter injection)以及接口注入(interface injection)。

2.1 构造方法注入

顾名思义,构造方法注入,就是被注入对象可以通过在其构造方法中声明依赖对象的参数列表,让外部(通常是IoC容器)知道它需要哪些依赖对象。

对于前面例子中的FXNewsProvider来说,只要声明如下构造方法即可支持构造方法注入。

public class FXNewsProvider {
   private IFXNewsListener newsListener;
   private IFXNewsPersister newPersistener;

   // 构造方法注入
   public FXNewsProvider(IFXNewsListener newsListner,IFXNewsPersister newsPersister) {
      this.newsListener = newsListner;
      this.newPersistener = newsPersister;
   }

   public void getAndPersistNews() {
      String[] newsIds = newsListener.getAvailableNewsIds();
      if(ArrayUtils.isEmpty(newsIds)) {
         return;
      }
      for(String newsId : newsIds) {
         FXNewsBean newsBean = newsListener.getNewsByPK(newsId);
         newPersistener.persistNews(newsBean);
         newsListener.postProcessIfNecessary(newsId);
      }
   }
}

IoC Service Provider会检查被注入对象的构造方法,取得它所需要的依赖对象列表,进而为其注入相应的对象。同一个对象是不可能被构造两次的,因此,被注入对象的构造乃至其整个生命周期,应该是由IoC Service Provider来管理的。

构造方法注入方式比较直观,对象被构造完成后,即进入就绪状态,可以马上使用。这就好比你刚进酒吧的门,服务生已经将你喜欢的啤酒摆上了桌面一样。坐下就可马上享受一份清凉与惬意。

2.2 setter 方法注入

public class FXNewsProvider {
   private IFXNewsListener newsListener;
   private IFXNewsPersister newPersistener;
   
   // setter方法注入
   public void setNewsListener(IFXNewsListener newsListener) {
      this.newsListener = newsListener;
   }

   // setter方法注入
   public void setNewPersistener(IFXNewsPersister newPersistener) {
      this.newPersistener = newPersistener;
   }

   public void getAndPersistNews() {
      String[] newsIds = newsListener.getAvailableNewsIds();
      if(ArrayUtils.isEmpty(newsIds)) {
         return;
      }
      for(String newsId : newsIds) {
         FXNewsBean newsBean = newsListener.getNewsByPK(newsId);
         newPersistener.persistNews(newsBean);
         newsListener.postProcessIfNecessary(newsId);
      }
   }
}

这样,外界就可以通过调用setNewsListener和setNewPersistener方法为FXNewsProvider对象注入所依赖的对象了。

setter方法注入虽不像构造方法注入那样,让对象构造完成后即可使用,但相对来说更宽松一些,可以在对象构造完成后再注入。这就好比你可以到酒吧坐下后再决定要点什么啤酒,可以要百威,也可以要大雪,随意性比较强。如果你不急着喝,这种方式当然是最适合你的。

2.3 接口注入

相对于前两种注入方式来说,接口注入没有那么简单明了。被注入对象如果想要IoC Service Provider为其注入依赖对象,就必须实现某个接口。这个接口提供一个方法,用来为其注入依赖对象。IoC Service Provider最终通过这些接口来了解应该为被注入对象注入什么依赖对象。

FXNewsProvider为了让IoC Service Provider为其注入所依赖的IFXNewsListener,首先需要实现
IFXNewsListenerCallable接口,这个接口会声明一个injectNewsListner方法(方法名随意),该方法的参数,就是所依赖对象的类型。这样,InjectionServiceContainer对象,即对应的IoC Service Provider就可以通过这个接口方法将依赖对象注入到被注入对象FXNewsProvider当中。

接口注入方式最早并且使用最多的是在一个叫做Avalon的项目中,相对于前两种依赖注入方式,接口注入比较死板和烦琐。如果需要注入依赖对象,被注入对象就必须声明和实现另外的接口。这就好像你同样在酒吧点啤酒,为了让服务生理解你的意思,你就必须戴上一顶啤酒杯式的帽子,看起来有点多此一举。

通常情况下,这有些让人不好接受。不过,好在这种方式也可以达到目的。

3. 你建议使用哪种依赖注入方式?

问题:你建议使用哪种依赖注入方式?构造器注入还是Setter注入?

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

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