依赖注入在java中的使用

依赖注入的概念以及作用

  依赖注入的概念不是Java所独有的。但这篇文章将从Java的角度来学讨论依赖注入。其实依赖注入不过是控制反转的一种实现方式而已(另外一种是依赖查找)。别看控制反转这个词语很高大上,其实这只是许多轻量级的容器在连接各个模块所采取的一个普遍的模式。它其实就是实现了控制权的转移,把控制权从程序代码本身移交到外部容器而已。

  现在我们看一个很naive的例子,

class MovieLister

1
2
3
4
5
6
7
8
public Movie[] moviesDirectedBy(String arg) {
List allMovies = finder.findAll();
for (Iterator it = allMovies.iterator(); it.hasNext();) {
Movie movie = (Movie) it.next();
if (!movie.getDirector().equals(arg)) it.remove();
}
return (Movie[]) allMovies.toArray(new Movie[allMovies.size()]);
}

  这个查找器是跟movies如何存储,如何排序一点关系都没有,是完全独立的。所以我们再创建一个这个查找器所使用的MovieFinder的接口。

interface MovieFinder

1
2
3
public interface MovieFinder {
List findAll();
}

  到这儿为止,实现是很完美的解耦的。但我们应该如何进行填充movies呢??下面是一个构造器。

class MovieLister…

1
2
3
4
private MovieFinder finder;
public MovieLister() {
finder = new ColonDelimitedMovieFinder("movies1.txt");
}

  但这样我们的MovieLister就产生了对ColonDelimitedMovieFinder这个类的依赖。这就导致了MovieLister无法独立的测试和使用。有人把这种依赖称为硬依赖。依赖关系图如下:

  但这个依赖图就意味着我在编写MovieLister的时候,我要知道Movies到底是怎么存储的,用数据库还是用的文件,甚至要知道存储数据的txt文件名叫啥。What fuck!!

  宝宝心里苦,不想知道这么多啊,毕竟我就写个如何查找movie的接口,何必知道这么多!!那么如何进行进一步解耦呢?于是我们就需要一种类似插件一样的东西。这个插件能使我们的MovieLister类只依赖于findAll这个接口。这就要求在编译的时候,finder的具体实现并不用连接到这个程序。毕竟我不知道它是如何实现的,假如存储数据我们不用文件而是采用了数据库呢??

  那什么时候进行连接呢??有个很有趣的原则。它被称作好莱坞原则————不要给我们打电话,我们会给你打电话(don’t call us, we’ll call you)。这个也一样。我们将在所需要使用到它的具体实现时进行连接。也就是说它的具体是实现是滞后的,是不由我这程序代码控制的。而控制它的框架,我们称之为外部容器。它们能分析出这些类之间的依赖关系,这样就能在类实例化的过程中,通过Java的反射机制把所需要的依赖注入到这些对象中。这些容器有很多。例如偏爱构造器注入的PicoContainer框架,偏爱setter注入的Spring框架,偏爱接口注入的Avalon框架等等。

利用注释实现依赖注入

  实现注入的方法有很多,用XML,注释,DSL,甚至Java代码都能实现。最终Java力推的是注释实现,在JSR330中有整个介绍。

1
2
3
4
5
6
7
8
class Stopwatch {
final TimeSource timeSource;
@Inject Stopwatch(TimeSource TimeSource) {
this.TimeSource = TimeSource;
}
void start() { ... }
long stop() { ... }
}
1
2
3
4
5
/** GUI for a Stopwatch */
class StopwatchWidget {
@Inject StopwatchWidget(Stopwatch sw) { ... }
...
}

  这是一个很简单的例子。当程序需要实例化StopWatchWidget这个组件时,会执行以下步骤:

  • 寻找TimeSource
  • 用这个TimeSource建构StopWatch
  • 用这个StopWatch建构StopWatchWidget组件

@Inject的使用说明

  @Injects的适用性很广,能修饰构造器,方法和变量。下面简单介绍一下它的格式和注意事项

修饰构造器

@Inject ConstructorModifiers opt  SimpleTypeName(FormalParameterList opt) Throws opt  ConstructorBody

  • ConstructorModifiers是指private,public等访问权限的修饰符,这是可选的
  • SimpleTypeName是指构造器名字
  • FormalParameterList构造器所需要传进去的参数,也是可选项
  • ConstructorBody构造器内容

注意:如果没有其他构造器,那么就会选择一个public的无参构造器
@Injectopt Annotations opt   public SimpleTypeName() Throws opt  ConstructorBody

修饰变量

@Inject FieldModifiers opt  Type VariableDeclarators

  • FieldModifiers指private,public等访问权限的修饰符,这是可选的
  • Type 变量类型
  • VariableDeclarators 变量的名字

注意该变量不能为final

修饰方法

@Inject MethodModifiers opt  ResultType Identifier(FormalParameterList opt) Throws opt   MethodBody

  • MethodModifiers指private,public等访问权限的修饰符,这是可选的
  • ResultType 返回类型
  • Identifier 方法名
  • FormalParameterList 形参,可以为空
  • MethodBody方法体

注意该方法不能为abstract,可以无参传入,返回值也可以为void形

一个简单的例子

1
2
3
4
5
6
7
8
9
10
public class Car {
// Injectable constructor
@Inject public Car(Engine engine) { ... }
// Injectable field
@Inject private Provider<Seat> seatProvider;
// Injectable package-private method
@Inject void install(Windshield windshield, Trunk trunk) { ... }
}