Guice是Google开发的一个开源轻量级的依赖注入框架,运行速度快,使用简单。
项目地址:https://github.com/google/guice/
最新的版本是4.1,本文基于此版本。
0. 什么是依赖注入?
依赖注入(Dependency Injection)是一种思想。
在一般的编程中,如果我们想要使用一个class的实例,那么必须要调用构造方法new class()手动将其实例化,如果这个class中又有对其他class属性的引用,那么在构造方法中,又要调用其他class的构造方法将其实例化。
这就是“依赖”。
Guice这种依赖注入框架的作用就是接管class之间的依赖关系,如果我们想要使用一个class的实例,Guice会自动生成实现类的实例并交给我们。
这个过程就叫做“注入”。
1. 利用Module的依赖注入
//定义Dog接口 public interface Dog { void bark(); } //定义BlackDog实现类 public class BlackDog implements Dog{ @Override public void bark() { System.out.println("i am black dog"); } } //依赖注入 public class GuiceTest { public static void main(String[] args) { Injector injector = Guice.createInjector(new Module() { @Override public void configure(Binder binder) { binder.bind(Dog.class).to(BlackDog.class); } }); Dog dog = injector.getInstance(Dog.class); dog.bark(); } }
输出结果如下
i am black dog
这个例子非常简单,先在自定义的Module中将Dog接口与BlackDog实现类关联起来。此时如果调用getInstance方法想要获取Dog接口的实例,就自动创建一个BlackDog的实例并返回。
2. 绑定到实例的依赖注入
public static void main(String[] args) { Injector injector = Guice.createInjector(new Module() { @Override public void configure(Binder binder) { binder.bind(Dog.class).toInstance(new BlackDog()); } }); Dog dog = injector.getInstance(Dog.class); dog.bark(); }
可以看到Dog接口已经与某个BlackDog的实例绑定起来了。
3. 利用Provider的依赖注入
public static void main(String[] args) { Injector injector = Guice.createInjector(new Module() { @Override public void configure(Binder binder) { binder.bind(Dog.class).toProvider(new Provider<Dog>() { Dog dog = new BlackDog(); @Override public Dog get() { return dog; } }); } }); Dog dog = injector.getInstance(Dog.class); dog.bark(); }
换了一种方法的绑定,在自定义的Provider类中,我们可以执行一些复杂的操作。
4. 基于注解的依赖注入
可以在接口上添加@ImplementedBy完成绑定操作
@ImplementedBy(BlackDog.class) public interface Dog { void bark(); } public class BlackDog implements Dog{ @Override public void bark() { System.out.println("i am black dog"); } } public class GuiceTest { public static void main(String[] args) { Injector injector = Guice.createInjector(); Dog dog = injector.getInstance(Dog.class); dog.bark(); } }
可以看到没有为Guice指定任何Module,也完成了依赖注入的操作
当然,也可以用@ProvidedBy注解完成绑定操作
@ProvidedBy(BlackDogProvider.class) public interface Dog { void bark(); } public class BlackDogProvider implements Provider<Dog> { @Override public Dog get() { return new BlackDog(); } } public class GuiceTest { public static void main(String[] args) { Injector injector = Guice.createInjector(); Dog dog = injector.getInstance(Dog.class); dog.bark(); } }
基于注解的依赖注入其实与基于Module的依赖注入差别不大,只是口味上的区别
但是Module里的配置会覆盖注解里的配置
5. 单例
在默认情况下,每次调用getInstance方法,都会创建一个新的对象并返回(Provider注入与实例注入除外)
@ImplementedBy(BlackDog.class) public interface Dog { void bark(); } public class BlackDog implements Dog{ @Override public void bark() { System.out.println("i am black dog"); } } public class GuiceTest { public static void main(String[] args) { Injector injector = Guice.createInjector(); for (int i = 0; i < 10; i++) { System.out.println(injector.getInstance(Dog.class).hashCode()); } } }
调用10次getInstance方法,分别计算得到对象的hashCode,结果如下
1709366259
1335298403
1643691748
2068434592
143110009
2142003995
1535634836
1846412426
1539805781
1206883981
可以看出,每次调用getInstance方法,都会生成一个全新的对象。
如果我们希望返回的是同一个对象(单例模式),那么只需要在实现类上加一个@Singleton注解
@ImplementedBy(BlackDog.class) public interface Dog { void bark(); } @Singleton public class BlackDog implements Dog{ @Override public void bark() { System.out.println("i am black dog"); } } public class GuiceTest { public static void main(String[] args) { Injector injector = Guice.createInjector(); for (int i = 0; i < 10; i++) { System.out.println(injector.getInstance(Dog.class).hashCode()); } } }
运行结果如下:
1632492873
1632492873
1632492873
1632492873
1632492873
1632492873
1632492873
1632492873
1632492873
1632492873
可以看到@Singleton注解已经生效,调用getInstance方法返回的对象已经是单例的了
6. 属性注入
Guice可以在某个类初始化时,为它的属性自动注入实例,无需我们手动赋值
@ImplementedBy(BlackDog.class) public interface Dog { void bark(); } public class BlackDog implements Dog{ @Override public void bark() { System.out.println("i am black dog"); } } public class GuiceTest { @Inject private Dog dog; public static void main(String[] args) { Injector injector = Guice.createInjector(); GuiceTest guiceTest = injector.getInstance(GuiceTest.class); guiceTest.dog.bark(); } }
程序的输出结果为:
i am black dog
可以看到,通过getInstance方法创建出来的GuiceTest实例中的dog属性,已经被自动注入了一个BlackDog实例。
这就是@Inject注解的效果,如果dog属性没有这个@Inject注解,程序会抛出NullPointerException
7. 构造函数注入
如果在构造函数上添加@Inject注解,那么可以实现在对象初始化的时候自动提供参数,代码如下
public class GuiceTest { private Dog dog; @Inject public GuiceTest(Dog dog) { this.dog = dog; } public static void main(String[] args) { Injector injector = Guice.createInjector(); GuiceTest guiceTest = injector.getInstance(GuiceTest.class); guiceTest.dog.bark(); } }
在调用getInstance获取GuiceTest实例的时候,会自动调用唯一的GuiceTest构造方法并传入参数
需要注意的是,此时必须有且只有一个带有@Inject注解的构造方法,否则会抛出类似如下的异常:
GuiceTest has more than one constructor annotated with @Inject. Classes must have either one (and only one) constructor annotated with @Inject or a zero-argument constructor that is not private.
8. Setter注入
与之前提到的属性注入非常类似,只需要在setter方法上添加一个@Inject注解即可。
public class GuiceTest { private Dog dog; public Dog getDog() { return dog; } @Inject public void setDog(Dog dog) { this.dog = dog; } public static void main(String[] args) { Injector injector = Guice.createInjector(); GuiceTest guiceTest = injector.getInstance(GuiceTest.class); guiceTest.dog.bark(); } }
9. 静态变量注入
上面的注入都是针对某个类的非静态属性进行注入,如果我们对某个类的静态属性进行注入,会发生什么呢?
public class GuiceTest { @Inject private static Dog dog; public static void main(String[] args) { Injector injector = Guice.createInjector(); GuiceTest guiceTest = injector.getInstance(GuiceTest.class); guiceTest.dog.bark(); } }
程序的执行结果是:
Exception in thread "main" java.lang.NullPointerException
很显然,注入失败了。因为getInstance方法是针对某个类的实例进行注入,而类的静态属性是被这个类的所有对象全局共享的,所以直接注入会失败。
正确的注入姿势如下:
public class GuiceTest { @Inject private static Dog dog; public static void main(String[] args) { Injector injector = Guice.createInjector(new Module() { @Override public void configure(Binder binder) { binder.requestStaticInjection(GuiceTest.class); } }); GuiceTest.dog.bark(); } }
程序的执行结果如下:
i am black dog
注入成功!
虽然目前还不知道具体的实现原理,但是可以猜出Guice一开始就直接往GuiceTest的静态变量里注入了对象。
10. 直接注入属性
还有一种简单暴力的方式来完成属性的依赖注入
public class GuiceTest { @Inject private Dog dog; public static void main(String[] args) { Injector injector = Guice.createInjector(); GuiceTest guiceTest = new GuiceTest(); injector.injectMembers(guiceTest); guiceTest.dog.bark(); } }
对GuiceTest的实例直接调用injectMembers方法,完成对实例的属性的依赖注入
11. 单接口多实例
如果一个接口有多个实现类,Guice提供了一种非常灵活的注入方案
public interface Dog { void bark(); } public class BlackDog implements Dog{ @Override public void bark() { System.out.println("i am black dog"); } } public class WhiteDog implements Dog { @Override public void bark() { System.out.println("i am white dog"); } } @BindingAnnotation @Target({FIELD, PARAMETER, METHOD}) @Retention(RUNTIME) public @interface Black { } @BindingAnnotation @Target({FIELD, PARAMETER, METHOD}) @Retention(RUNTIME) public @interface White { } public class GuiceTest { @Inject @Black private Dog dog; public static void main(String[] args) { Injector injector = Guice.createInjector(new Module() { @Override public void configure(Binder binder) { binder.bind(Dog.class).annotatedWith(White.class).to(WhiteDog.class); binder.bind(Dog.class).annotatedWith(Black.class).to(BlackDog.class); } }); GuiceTest guiceTest = new GuiceTest(); injector.injectMembers(guiceTest); guiceTest.dog.bark(); } }
运行结果如下:
i am black dog
额外定义了@Black与@White注解,然后在Module里定义了两条规则,这样被@Black注解修饰的dog属性就被注入了BlackDog的依赖。
非常灵活,在特定场景下十分有用。
比方说可能实现类分为Debug与Release两类,调试的时候所有待注入的属性都用@Debug注解修饰,发布前将所有@Debug替换成@Release即可。
12. @Named注解
如果懒得额外定义注解,那么可以直接使用Guice提供的@Named注解完成相似的功能。
public class GuiceTest { @Inject @Named("Black") private Dog dog; public static void main(String[] args) { Injector injector = Guice.createInjector(new Module() { @Override public void configure(Binder binder) { binder.bind(Dog.class).annotatedWith(Names.named("White")).to(WhiteDog.class); binder.bind(Dog.class).annotatedWith(Names.named("Black")).to(BlackDog.class); } }); GuiceTest guiceTest = new GuiceTest(); injector.injectMembers(guiceTest); guiceTest.dog.bark(); } }
可以看到@Named注解也能完成指定实现类的关联工作
总结
本文简单介绍了Guice这个轻量级的依赖注入框架的基本使用方法。
虽然简单,但是也展现了依赖注入思想的精华之处:由容器来解决依赖对象的构建问题,程序员只需要提供属性的关联关系即可。使得程序松耦合,减少代码之间的依赖关系,便于后续的修改。
更详细的信息,请参见官方Wiki