Google Guice 系列教程 - 基础实践

转载:http://www.cnblogs.com/youngC/archive/2012/12/21/2828419.htmlhtml

 

前言java


Google Guice 是一个轻量级的依赖注入框架,它支持Java 5或者更高版本的JDK,得利于Java 5中提供的泛型 (Generics) 和注释 (Annotations) ,它可使得代码类型安全 (type-safe) 。那么什么时候使用在代码中使用 Guice 进行注入呢?通常来讲,若是在你的应用代码中业务对象 (Business Objects) 之间的关系或者依赖须要手动维护的话,你就可使用Guice 进行注入。数据库

该文章中,首先我将经过一些例子来初步的认识一下 Guice 框架,而后我将介绍下 依赖注入框架的理论知识 以及在应用程序中使用依赖注入的好处,一样我也会和你们探讨一下 Guice 提供的用于简化代码的 API (包括Annotations) 。最后经过大量使用 Guice API的例子来使你们更好地理解这些API。编程

依赖注入(Dependency Injection)缓存


因为Gucie 是一个依赖注入框架 (Dependency Injection Framework) ,所以咱们首先要很清楚依赖注入 (Dependency Injection) 是什么概念。这些年来,依赖注入变得愈来愈流行,变得愈来愈重要,在不少典型的应用中它甚至变成了一个必需的机制,好比 J2EE 5.0, Spring, JBoss Seam就是使用依赖注入的很好的例子。如今咱们来使用一个简单的例子来讲明使用依赖注入框架的必要性。安全

请看如下代码:app

interface Storage{ public void store(String uniqueId, Data data); public Data retrieve(String uniqueId); }

上面的接口 Storage 提供了存储 (store) 和获取 (retrieve) 数据的机制,因为数据能够存储在数据库中也能够存储在一个文件中,所以上面接口 Storage 的实现能够以下。框架

复制代码
class FileStorage implements Storage{ public void store(String uniqueId, Data data){ // Store the object in a file using Java Serialization mechanism.  } public Data retrieve(String uniqueId){ // Code to retrieve the object.  } }
复制代码

实现类 FileStorage 能够将数据存储到硬盘文件中,一样也能够从硬盘文件中获取存储数据。接下来是 Storage 接口的另外一种实现,它用于将数据存储到数据库中。ide

复制代码
class DatabaseStorage implements Storage{ public void store(String uniqueId, Data data){ // Open a connection and store the data.  } public Data retrieve(String uniqueId){ // Get the data from the Database.  } }
复制代码

如今,咱们来看一个 Storage 应用客户端的例子。下面的 StorageClient 代码片断中,首先初始化一个 FileSorage,而后在转向 DatabaseStorage 实现。工具

复制代码
public class StorageClient { public static void main(String[] args) { // Making use of file storage. Storage storage = new FileStorage(); storage.store("123", new Data()); // Making use of the database. storage = new DatabaseStorage(); storage.store("456", new Data()); } }
复制代码

仔细看下 StorageClient 模块中的代码,尽管接口 (Storage) 和实现类 (FileStorage/DatabaseStorage) 松耦合,可是 客户端 (StorageClient) 模块须要手动地去建立实现类的实例对象 (instance) ,一样接口和实现类之间的关系 (Relationship) 是直接在客户端代码中写死的,然而在大多数状况下,在代码编译的时候,客户端应用程序已经知道须要绑定哪种接口实现类,若是只绑定某一个具体的实现类, 确定比上面的代码中同时实现两个类 (某一个是不必的) 更有用。Google Guice 就是干这个工做的,它在应用程序的客户端代码中建立不一样形式服务 (Services) 实例, 并且客户端和服务之间的依赖是经过一些简单的配置机制 (Configuration Mechanism) 自动注入的。

接下来我将提供一个简单使用 Guice Framework 的例子。

一个简单的 Guice 例子


在这个简单的例子中,让咱们看一下 Guice 在维护不一样对象之间的关系/依赖时如何简化开发的。让咱们看一下下面的代码片断,咱们建立了一个 Add 接口,而且在里面定义了一个 add() 方法。

public interface Add { public int add(int a, int b); }

下面是接口 Add 的一个实现类

复制代码
public class SimpleAdd implements Add{ public int add(int a, int b) { return a + b; } }
复制代码

接着咱们定义一个 Module 类,这个类用于使用 Guice API 在一个应用程序中建立 Bindings。Module 和 Bindings 理论方面的详细介绍在后面章节。如今,你只须要明白经过 Binder 类,你能够将一些 Bindings 配置到某个 Module中。在 Guice 条目中,Binding 提供了一种方式将接口 (interface) 和实现类相关联。

复制代码
import com.google.inject.Binder; import com.google.inject.Module; public class AddModule implements Module{ public void configure(Binder binder) { binder.bind(Add.class).to(SimpleAdd.class); } }
复制代码

在上面的代码中,咱们告诉 Guice 将 SimpleAdd 实现类绑定到 Add 接口上,也就是说在客户端调用Add.add() 方法时,实际会去执行 SimpleAdd.add() 方法。下面给出了一个客户端例子用户使用 Add 接口。

复制代码
import com.google.inject.Guice; import com.google.inject.Injector; public class AddClient { public static void main(String[] args) { Injector injector = Guice.createInjector(new AddModule()); Add add = injector.getInstance(Add.class); System.out.println(add.add(10, 54)); } }
复制代码

更多关于 Injector, Guice 的理论知识将会在后面的章节介绍。injector.getInstance(Add.class) 将会建立并返回一个 SimpleAdd 类型的实例。其实是经过 AddModule.configure() 方法来获取具体的绑定信息的。

Guice API 探讨


 让咱们探讨一下实现 Guice 依赖注入不一样的 API。特别会涉及如下的 接口/实现类。

  • Binder
  • Injector
  • Module
  • Guice

1. Binder

Binder 接口主要是由与 Bindings 相关的信息组成的。一个 Binding 其实就是一个接口和其相应的实现类的映射关系。例如,回想一下上面的例子,咱们建立了一个由接口 Add 指向 实现类 SimpleAdd 的映射关系。

从程序角度来讲,能够经过如下代码方式实现。注意的是不管是接口 (interface) 仍是实现类 (implementation classes),都是经过 bind() 和 to()方法实现映射的。

binder.bind(Add.class).to(SimpleAdd.class)

一样也能够将一个接口直接映射到一个具体的实例对象,代码以下。

binder.bind(Add.class).to(new SimpleAdd())

第三种方式是将一个接口绑定到一个相应的 Provider 类。默认状况下,Guice 框架会建立并返回应用程序须要的实例对象。可是,若是须要定制化一个对象建立流程(Object Creation Process),该怎么办? Providers 能够很简单的实现这种定制化。 你只须要遵循传统的工厂模式(Factory Pattern)建立对象的方式使用 Providers,例以下面的代码。

binder.bind(Add.class).to(new AddProvider<Add>())

后面我将会经过一些例子讲解如何建立 Provider 对象。不过如今,你只须要知道在 AddProvider 类中提供了一种工厂方法,它会返回具体的 Add 实现类的实例对象。后面我一样会讲解到如何将一个接口绑定到多个具体实现上。

2. Injector

Injectors 一般会在客户端 (Clients) 使用,它只关心如何建立 (Creating)和维护 (Maintaining) 对象(生命周期)。Injectors 会去维护一组默认的 Bindings (Default Bindings),这里咱们能够获取建立和维护不一样对象间关系的配置信息 (Configuration information)。如下代码将会返回 Add 的实现类对象。

Add addObject = injector.getInstance(Add.class)

你能够简单地经过 Injector.getBindings() 方法获取与 Injector 相关的 Bindings信息,getBindings() 方法会返回一个 Map。

Map<Key, Binding> allBindings = injector.getBindings()

这里须要注意的是每个 Binding 一般有一个对应的 Key 对象,该对象是由 Guice 自动建立并维护的。若是你想要获取于Injector相关的 Providers 的话,你能够经过如下方法获取。

Provider<SomeType> provider = injector.getProvider(SomeType.class)

3. Module

Module 对象会去维护一组 Bindings。在一个应用中能够有多个 Module  。反过来 Injectors 会经过 Module 来获取可能的 Bindings。Module 是经过一个包含须要被重写 override 的 Module.configure() 方法的接口去管理 Bindings。 简单地说,就是你要继承一个叫作 AbstractModule的类,这个类实现了 Module 接口,而且重写 configure() 方法, 代码以下。

复制代码
class MyModule extends AbstractModule{ public void configure(Binder binder){ // Code that binds information using the various // flavours of bind method.  } }
复制代码

4. Guice

客户端 (Clients) 是经过 Guice 类直接和其余 Objects 进行交互的。Injector 和不一样的 Modules 之间的联系是经过 Guice 创建的。例以下面的代码。

MyModule module = new MyModule(); Injector injector = Guice.createInjector(module);

这里须要注意的是 Guice.createInjector() ,该方法接受一个 Module 对象做为参数。 Module 类必须要重写 configure() 方法, 该方法是用于传递一个 默认 Binder 对象, 该 Binder 对象为应用程序用于填充特定的 Bindings (to Classes, Objects and Providers)。 当客户端调用 Injector 类的 getInstance() 方法建立一个实例的时候,Injector 会从 Binder 对象维护的各类 Bindings 中获取原来的对象。

Guice 注释 (Annotations)


Guice 提供了一些十分有用的 Annotations ,这些 Annotations 能够用来在应用程序中添加 元数据 (meta-data)。 这一章节我将要讲如下几个注释。

  • Implemented By
  • Inject
  • Provided By
  • Singleton

1. Implemented By

该 Annotation 用于指向接口的实现类。例如,若是 Add 接口有多个实现类,可是咱们但愿 SimpleAdd 是 Add 的默认实现类,因而咱们能够像下面同样处理。

@ImplementedBy(SimpleAdd.class) interface Add{ public int add(int a, int b); }

2. Inject

咱们可使用 Inject Annotation 来直接将实例注入到客户端的代码中。该注释能够用于某个类的构造方法上,代码以下。

class Client{ @Inject public Client(MyService service){ } }

上面的代码,咱们是基于构造方法层次 (Constrcctor-level)的 注入,而且假设 MyService 接口的具体实现已经在应用程序的 Module 中定义映射好了。一样你也能够在方法层次 (Method-level) 和 字段层次 (Field-level) 使用注释。

3. Provided By

假设咱们想要为一些接口定制化对象建立的流程 (Object creation process),那么咱们须要依赖 Guice Provider 机制, 对于接口 Add 来讲,咱们须要使用 AddProvider 来建立并返回 SimpleAdd 对象。在这个案例中,咱们能够直接在接口声明处使用 ProvidedBy 注释来指定该接口的 Provider 类型, 代码以下。

@ProvidedBy(AddProvider.class) public interface Add{ }

4. Singleton

默认状况下,客户端能够屡次使用 Injector.getInstance() 来调用对象,每个都会返回一个新建立的对象。若是咱们想要使用单例模式(Singleton Pattern)来获取对象,即 One Instance in the application,你能够在实现类上使用 Singleton 注释去标记。

复制代码
@Singleton
public class MyConnection{ public void connect(){ } public void disconnect(){ } }
复制代码


例子 (Samples)


 

这一章节将会提过更多的例子帮助你理解和使用 Guice API ,我将会更加详细的解析。

1. 简单的例子

在这个简单的例子中咱们没有使用接口编程,即将接口和实现分离。咱们只有一个实现类 Player 和一个依赖它的客户端 PlayerTest, 这里 Guice 没有作什么,只是提供了一个映射。

首先来看一下 Player 类。

复制代码
public class Player { public String name; public Player(){ } public String toString(){ return name; } }
复制代码

下面是客户端代码的例子,用于使用 Player 类。这里须要注意的是咱们没有在 Guice.createInjector() 方法里面传递 Module ,由于咱们不须要在程序代码中绑定对象。

复制代码
import com.google.inject.Guice; import com.google.inject.Injector; public class PlayerTest { public static void main(String[] args) { Injector injector = Guice.createInjector(); Player player = injector.getInstance(Player.class); player.name = "David Boon"; System.out.println(player); } }
复制代码

2. 处理多个依赖 (Multiple Dependencies)

这一小节里面,咱们将探讨如何是用 @Inject 注释来处理多个依赖。 比方说有一个对象直接依赖其它两个或者多个对象。这里咱们建立一个简单的 Case ,一我的有一台笔记和一个手机。

首先咱们给出 Mobile 类和 Laptop 类。

复制代码
public class Laptop { private String model; private String price; public Laptop(){ this.model = "HP 323233232"; this.price = "$545034"; } public String toString(){ return "[Laptop: " + model + "," + price + "]"; } }
复制代码
复制代码
public class Mobile { private String number; public Mobile(){ this.number = "988438434"; } public String toString(){ return "[Mobile: " + number + "]"; } }
复制代码

接下来咱们将会在 Person 类中使用 @Inject 注释来直接引用 Laptop 和 Mobile 对象。注意咱们这儿使用的是构造方法层次上的注入。

复制代码
import com.google.inject.Inject; public class Person { private Mobile mobile; private Laptop laptop; @Inject public Person(Mobile mobile, Laptop laptop){ this.mobile = mobile; this.laptop = laptop; } public void diplayInfo(){ System.out.println("Mobile:" + mobile); System.out.println("Laptop:" + laptop); } }
复制代码

最后是客户端的代码,这段代码用于使用这个例子。因为咱们没有使用到 Bindings, 咱们没有在 Guice.createInject() 方法中传递 Module 对象。

复制代码
import com.google.inject.Guice; import com.google.inject.Injector; public class MultipleDependencyTest { public static void main(String[] args) { Injector injector = Guice.createInjector(); Person person = injector.getInstance(Person.class); person.diplayInfo(); } }
复制代码

上面程序的运行结果以下:

Mobile:[Mobile: 988438434]
Laptop:[Laptop: HP 323233232,$545034]

3. 使用 Binding 注释

在 Guice 中,一个类型不能绑定多个实现,以下,代码会抛 Runtime Error.

binderObject.bind(SomeType.class).to(ImplemenationOne.class); binderObject.bind(SomeType.class).to(ImplemenationTwo.class);

因为 Guice 并不知道客户端究竟要绑定哪个实现类,所以抛出了异常。可是在相似 Java 的语言中,一个类能够实现多个接口,基于这个思想,Guice 提供了一种依赖 Binding 注释的方式来实现一个类型绑定多个实现。例如,接口 Player 定义以下,

public interface Player { public void bat(); public void bowl(); }

接着咱们提供了 Player 的两种实现类, GoodPlayer 和 BadPlayer。

复制代码
public class GoodPlayer implements Player{ public void bat() { System.out.println("I can hit any ball"); } public void bowl() { System.out.println("I can also bowl"); } }
复制代码
复制代码
public class BadPlayer implements Player{ public void bat() { System.out.println("I think i can face the ball"); } public void bowl() { System.out.println("I dont know bowling"); } }
复制代码

如今咱们开始介绍 Guice ,对于接口 Player 而言,有两个实现类 GoodPlayer 和 BadPlayer。不管如何,最终客户端只会使用其中一个具体的实现类,不管它使用GoodPlayer 实现类仍是 BadPlayer 实现类,经过一些注释机制 (Annotaion mechanisms) 咱们能够指示 Guice 使用不一样的实现。代码实现以下。

复制代码
 1 import com.google.inject.*;  2  3 public class PlayerModule implements Module{  4  5 public void configure(Binder binder) {  6  7 binder.bind(Player.class).annotatedWith(Good.class).to(  8 GoodPlayer.class);  9 binder.bind(Player.class).annotatedWith(Bad.class).to( 10 BadPlayer.class); 11  } 12 }
复制代码

注意第7行和第9行代码,咱们分别使用了.annotatedWith(Good.class) 和 .annotatedWith(Bad.class), 这两处代码指明了若是使用Good注释,那么就绑定GoodPlayer实现类,若是使用了Bad注释,那么就绑定BadPlayer实现类。

上面的代码中咱们使用了两个自定义的 Annotation,Good 和 Bad。下面咱们给出 Good annotation 和 Bad annotation 的代码。

复制代码
import java.lang.annotation.*; import com.google.inject.BindingAnnotation; @Retention(RetentionPolicy.RUNTIME) @BindingAnnotation @Target(ElementType. LOCAL_VARIABLE) public @interface Good {}
复制代码
复制代码
import java.lang.annotation.*; import com.google.inject.BindingAnnotation; @Retention(RetentionPolicy.RUNTIME) @BindingAnnotation @Target(ElementType. LOCAL_VARIABLE) public @interface Bad {}
复制代码

接下来是上面程序的客户端代码。这里须要注意的是当在客户端代码中请求某一个接口的具体实现的时候,能够直接经过指定不一样的 Annotation 来指定返回不一样的实现类。

复制代码
 1 import com.google.inject.Guice;  2 import com.google.inject.Injector;  3 import com.google.inject.Module;  4  5 public class PlayerClient {  6  7 public static void main(String[] args) {  8  9 PlayerModule module = new PlayerModule(); 10 Injector injector = Guice.createInjector(new Module[]{module}); 11 12 @Good Player player = (Player)injector.getInstance(Player.class); 13  player.bat(); 14  player.bowl(); 15  } 16 }
复制代码

此处注意第10行和第12行。 第12行代码 @Good 告诉 Guice 去 Playe Moduler 中获取一个 GoodPlayer实例对象。

4. Named 注释

像上面例子中,若是只是为了标记实现类以便于客户端使用,而为每个实现类建立新的 Annotation ,那么是彻底没有必要的。咱们可使用 @Named 注释来命名这些 entities。这儿有一个工具方法 - Names.named() ,当你给它一个命名,它会返回好一个命名好的 Annotation。例如上面的例子中,在 Player Module 中可使用 Names.named() 来完成一些相同的事情。

复制代码
import com.google.inject.Binder; import com.google.inject.Module; public class PlayerModule implements Module{ public void configure(Binder binder) { binder.bind(Player.class).annotatedWith(Names.named("Good")).to( GoodPlayer.class); binder.bind(Player.class).annotatedWith(Names.named("Bad")).to( BadPlayer.class); } }
复制代码

如今在客户端代码中,咱们将使用 @Named() annotation来获取注释。

@Named("Good") Player goodPlayer = (Player)injector.getInstance(Player.class); @Named("Bad") Player badPlayer = (Player)injector.getInstance(Player.class);

5. 一个简单的 Provider

在 Guice 中 Providers 就像 Factories 同样建立和返回对象。在大部分状况下,客户端能够直接依赖 Guice 框架来为服务(Services)建立依赖的对象。可是少数状况下,应用程序代码须要为一个特定的类型定制对象建立流程(Object creation process),这样能够控制对象建立的数量,提供缓存(Cache)机制等,这样的话咱们就要依赖 Guice 的 Provider 类。

例如,咱们须要为 MockConnection 建立一个对象建立和销毁的流程,代码以下。

复制代码
public class MockConnection { public void connect(){ System.out.println("Connecting to the mock database"); } public void disConnect(){ System.out.println("Dis-connecting from the mock database"); } }
复制代码

如今咱们来写一个简单的 Provider 类来实现 Guice 的 Provider 接口,使用它建立并返 MockConnection对象,代码以下。

复制代码
public class ConnectionProvider implements Provider<MockConnection>{ @Override public MockConnection get() { // Do some customization mechanism here. MockConnection connection = new MockConnection(); // Do some customization mechanism here too. return connection; } }
复制代码

须要注意的是,全部的自定义 Provider 类必需实现 Provider 接口,而且重写里面的 get() 方法。如今 Module 须要留意这个自定义的 Provider 类,它须要请求 ConnectionProvider 来建立对象,而不是直接建立对象,实现的代码以下。

复制代码
 1 import com.google.inject.*;  2  3 public class ConnectionTest {  4  5 public static void main(String args[]){  6 Injector injector = Guice.createInjector(  7 new Module(){  8  @Override  9 public void configure(Binder binder) { 10 binder.bind(MockConnection.class).toProvider( 11 ConnectionProvider.class); 12  } 13  } 14  ); 15 16 MockConnection connection = 17 injector.getInstance(MockConnection.class); 18  connection.connect(); 19  connection.disConnect(); 20  } 21 }
复制代码

注意第10行,咱们使用 toProvider() 方法将 MockConnection.class 绑定到一个 Provider 上。

小结


这篇文章简要的讲解了一些 Guice 相关的内容,有时间我将讲讲Guice的一些高级应用,还有 Robo Guice的使用。
本文参考连接

http://code.google.com/p/google-guice/

http://www.javabeat.net/2007/08/introduction-to-google-guice/

相关文章
相关标签/搜索