Guice(pronounced “juice”)发布了1.0版本.
首先看看Guice的定位:an ultra-lightweight, next-generation dependency injection container for Java 5 and later.
这是一个基于Java 5及后续版本的轻量级依赖注入容器。Martin Fowler关于依赖注入的精彩讨论参见:http://www.martinfowler.com/articles/injection.html#InversionOfControl
在这篇讨论中涉及到几种注入方式:Constructor Injection、Setter Injection、Interface Injection;
实际应用开发中依赖的注入问题自始至终都伴随着我们,当我们走过了最简单直接的手动注入、运用模式进行注入,到依赖于框架,当前成熟的框架众多,例如:Spring IOC、PicoContainer、Hivemind等;那么Guice作为一个依赖注入的framework,又是怎么定位自己的呢?其special的地方又在哪儿呢?我们首先看一个非常简单的例子:
对于注入描述的类如下:
public class MyModule implements Module {
public void configure(Binder binder) {
binder.bind(Service.class).to(ServiceImpl.class).in(Scopes.SINGLETON);
}
}
依赖注入发生的地方:
public class Client {
private final Service service;
@Inject
public Client(Service service) {
this.service = service;
}
public void go() {
service.go();
}
}
从这个例子我们可以清晰看出,Guice的依赖注入已经不同于以往的三种注入模式了,Okey,你已经看到了Guice充分使用了Java的新特性annotation来进行依赖注入的,已经完全跨越了Constructor Injection、Setter Injection、Interface Injection的概念了。这样,依赖注入只是需要一个或者多个annotation而已,一切搞定。
下一步我看看Guice这个Framework是要如何进行依赖注入的?
我们首先看看Guice的Architecture.
public class MyModule implements Module {
public void configure(Binder binder) {
// Bind Foo to FooImpl. Guice will create a new
// instance of FooImpl for every injection.
binder.bind(Foo.class).to(FooImpl.class);
// Bind Bar to an instance of Bar.
Bar bar = new Bar();
binder.bind(Bar.class).toInstance(bar);
}
}
该部分代码的sequence diagram如下:
运行时Guice的依赖注入的绑定器structure如下:
很明显的看到,一个注入的绑定关联主键Key、Scope、Provider;其中Key包含了依赖的Type和Annotation.
看到了Guice的简单绑定,一定会有一个疑问:就是一个类型的多个绑定怎么实现?Guice是运行Annotation binding来解决这个问题的。
例如绑定代码如下:
public class BindingModule implements Module {
public void configure(Binder binder) {
binder.bind(IService.class).annotatedWith(Blue.class).to(BlueService.class);
}
}
注入代码如下:
public class BindingClient {
// @Inject
// @Blue
// private IService service;
private IService service;
@Inject
public void injectService(@Blue IService service) {
this.service = service;
}
public void go() {
this.service.go();
}
}
annotation部分代码:
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD, ElementType.PARAMETER})
@BindingAnnotation
public @interface Blue {
}
如果再复杂一些,Guice还支持有属性的annotation:
绑定代码
public class NamedModule implements Module {
/*
* (non-Javadoc)
*
* @see com.google.inject.Module#configure(com.google.inject.Binder)
*/
public void configure(Binder binder) {
binder.bind(IPerson.class).annotatedWith(new NamedAnnotation("Bob")).to(Bob.class);
}
}
注入代码:
public class NamedClient {
@Inject
@Named("Bob")
private IPerson person;
public void say(){
this.person.say();
}
}
annotation代码:
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD, ElementType.PARAMETER})
@BindingAnnotation
public @interface Named {
String value();
}
public class NamedAnnotation implements Named {
private String value;
public NamedAnnotation(String value){
this.value = value;
}
/*
* (non-Javadoc)
*
* @see com.neusoft.anno.Named#value()
*/
public String value() {
return value;
}
public int hashCode() {
return 127 * "value".hashCode() ^ value.hashCode();
}
public boolean equals(Object o) {
if (!(o instanceof Named))
return false;
Named other = (Named) o;
return value.equals(other.value());
}
public String toString() {
return "@" + Named.class.getName() + "(value=" + value + ")";
}
/*
* (non-Javadoc)
*
* @see java.lang.annotation.Annotation#annotationType()
*/
public Class annotationType() {
return Named.class;
}
}
通过这几个例子,可以清晰看到,Guice应用annotation进行注入的关联的,用module将依赖关系组装起来,提供给客户端代码进行使用;而且annotation是可以自由定制的,支持灵活扩展的。
Guice本身也支持实现类的直接绑定:
public class MixerModule implements Module {
/*
* (non-Javadoc)
*
* @see com.google.inject.Module#configure(com.google.inject.Binder)
*/
public void configure(Binder binder) {
binder.bind(Concrete.class);
}
}
Guice有一种隐式绑定,其方法是这样,针对一个接口,如果缺少显式绑定,Guice 会寻找一个指向具体实现的@ImplementedBy标注。
@ImplementedBy(GoodNightImpl.class)
public interface IGoodNight {
void goodNight();
}
@Singleton
public class GoodNightImpl implements IGoodNight {
/*
* (non-Javadoc)
*
* @see com.neusoft.nobinding.IGoodNight#goodNight()
*/
public void goodNight() {
System.out.println("Good night!");
}
}
那么,Provider是怎么回事呢?“有时对于每次注入,客户代码需要某个依赖的多个实例。其它时候,客户可能不想在一开始就真地获取对象,而是等到注入后的某个时候再获取。对于任意绑定类型 T,你可以不直接注入 T 的实例,而是注入一个 Provider,然后在需要的时候调用 Provider.get()”因此,可以使用Provider去真正的来创建依赖的对象实例,直到需要的时候才创建客户代码需要的实例。
Provider等绑定代码:
public class WidgetModule implements Module {
/*
* (non-Javadoc)
*
* @see com.google.inject.Module#configure(com.google.inject.Binder)
*/
public void configure(Binder binder) {
binder.bind(IService.class).to(WidgetService.class).in(Scopes.SINGLETON);
binder.bind(Widget.class).toProvider(WidgetProvider.class);
}
}
Provider的注入:
public class Widget {
private IService service;
@Inject Provider provider;
public Widget(IService service){
this.service = service;
}
public void go(){
this.service.go();
}
}
Provider的实现:
public class WidgetProvider implements Provider {
final IService service;
@Inject
WidgetProvider(IService service) {
this.service = service;
}
public Widget get() {
return new Widget(service);
}
}
Service的接口和实现:
public interface IService {
void go();
}
public class WidgetService implements IService {
/* (non-Javadoc)
* @see com.neusoft.service.IService#go()
*/
public void go() {
System.out.println("Widget Service Hello.");
}
}
客户代码:
public class WidgetApplication {
/**
* @param args
*/
public static void main(String[] args) {
Injector injector = Guice.createInjector(new WidgetModule());
Widget widget = injector.getInstance(Widget.class);
widget.go();
}
}
这个例子完全表达出了Provider的价值所在。
另外,Guice也提供了Constant Values以及Converting Stings的支持:
Guice会针对Primitive types、primitive wrapper types、Strings、Enums、Classes类型进行类型的自动判断绑定机制。对于Primitive types、primitive wrapper types,如果 Guice 仍然无法找到一个的显式绑定,它会去找一个拥有相同绑定标注的常量 String 绑定,并试图将字符串转换到相应的值。
这就是一个Guice如何进行依赖注入的一个基本的思路,可以看到Guice的代码量十分少,是一个不错的依赖注入的方案,也将会推动依赖注入的进步。
参考:
Guice项目:http://code.google.com/p/google-guice/
Guice中文文档:http://docs.google.com/View?docid=dqp53rg_3hjf3ch