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

Guice 4.1教程的更多相关文章

  1. Guice之IOC教程

    Guice 在上一篇博客中, 我们讲解了Spring中的IOC示例与实现, 本文着重介绍Guice注入以及与Spring中的差异. Guice是Google开发的, 一个轻量级的依赖注入框架, 跟Sp ...

  2. java轻量级IOC框架Guice

    Google-Guice入门介绍(较为清晰的说明了流程):http://blog.csdn.net/derekjiang/article/details/7231490 使用Guice,需要添加第三方 ...

  3. Apache Shiro系列教程之三:Shiro的结构

    Shiro的设计目标是简化应用的安全管理工作.软件通常是以用户为基础设计的.也就是说,我们经常是根据用户是怎样和我们的软件交互的来设计相关的用户接口.比如,你可能会说"如果是已经登录的用户与 ...

  4. Guice入门

    参考链接:http://www.cnblogs.com/xd502djj/archive/2012/06/25/2561414.html Google Guice范例解说之使用入门 http://co ...

  5. Java自动化测试框架-04 - 来给你的测试报告化个妆整个形 - (上)(详细教程)

    简介 前边通过宏哥的讲解和分享想必小伙伴们和童鞋们都已经见过testng框架生成的测试报告,是不是它的样子和长相实在是不敢让大家伙恭维.那么今天宏哥就当一回美容师,由宏哥来给它美美容:当一回外科医生, ...

  6. Spark教程——(11)Spark程序local模式执行、cluster模式执行以及Oozie/Hue执行的设置方式

    本地执行Spark SQL程序: package com.fc //import common.util.{phoenixConnectMode, timeUtil} import org.apach ...

  7. Angular2入门系列教程7-HTTP(一)-使用Angular2自带的http进行网络请求

    上一篇:Angular2入门系列教程6-路由(二)-使用多层级路由并在在路由中传递复杂参数 感觉这篇不是很好写,因为涉及到网络请求,如果采用真实的网络请求,这个例子大家拿到手估计还要自己写一个web ...

  8. Angular2入门系列教程6-路由(二)-使用多层级路由并在在路由中传递复杂参数

    上一篇:Angular2入门系列教程5-路由(一)-使用简单的路由并在在路由中传递参数 之前介绍了简单的路由以及传参,这篇文章我们将要学习复杂一些的路由以及传递其他附加参数.一个好的路由系统可以使我们 ...

  9. Angular2入门系列教程5-路由(一)-使用简单的路由并在在路由中传递参数

    上一篇:Angular2入门系列教程-服务 上一篇文章我们将Angular2的数据服务分离出来,学习了Angular2的依赖注入,这篇文章我们将要学习Angualr2的路由 为了编写样式方便,我们这篇 ...

随机推荐

  1. shell脚本中的交互式输入自动化

    shell中有时我们需要交互,但是呢我们又不想每次从stdin输入,想让其自动化,这时我们就要使shell交互输入自动化了. 1    利用重定向     重定向的方法应该是最简单的 例: 以下的te ...

  2. Leetcode 106. 从中序与后序遍历序列构造二叉树

    题目链接 https://leetcode.com/problems/construct-binary-tree-from-inorder-and-postorder-traversal/descri ...

  3. PAT Basic 1084

    1084 外观数列 外观数列是指具有以下特点的整数序列: d, d1, d111, d113, d11231, d112213111, ... 它从不等于 1 的数字 d 开始,序列的第 n+1 项是 ...

  4. java并发面试题-基础

    多线程 java中有几种方法可以实现一个线程? 1.直接继承thread类:2.实现runnable接口: 如何停止一个正在运行的线程?可以使用正在运行的线程,支持线程中断,通常是定义一个volati ...

  5. MySQL之架构与历史(一)

    MySQL架构与历史 和其他数据库系统相比,MySQL有点与众不同,它的架构可以在多种不同的场景中应用并发挥好的作用,但同时也会带来一点选择上的困难.MySQL并不完美,却足够灵活,它的灵活性体现在很 ...

  6. 1 - smart(Maven:Package,Install,&,Log4j2)

    mvn package 时,增加如下命令-Dmaven.test.skip=true 则表示package打包时,不执行也不编译测试用例,mvn package -Dmaven.test.skip=t ...

  7. Linux服务器管理员必备Linux命令TOP5

    Linux桌面环境的界面友好度.图形性能及附件工具已经大幅进化,然而Linux服务器却还没有能达到这一步. 作为系统管理员必须熟练掌握Linux命令.Linux命令的内容很多,其中的一些TOP命令对于 ...

  8. HBase官方文档

    HBase官方文档 目录 序 1. 入门 1.1. 介绍 1.2. 快速开始 2. Apache HBase (TM)配置 2.1. 基础条件 2.2. HBase 运行模式: 独立和分布式 2.3. ...

  9. 相当牛X的java版星际游戏

    分享一款牛人用java写的经典游戏,目录结构如下: 虽然只能算一个Demo,但是用到了很多Java基础技术和算法: Java2D,双缓冲,A星寻路,粒子系统,动画效果,处理图片,Swing ui ,U ...

  10. PHP 自定义二维码生成

    环境:PHP 7.*.* ,Composer 包管理工具.QrCode 效果如下: 使用 Composer 安装 QrCode QrCode 类库基于 php 的 GD 库,用于生成任意尺寸的二维码, ...