Guice--Java依赖注入框架

面向接口编程

没有面向接口编程就没有依赖注入(Dependency Injection),所以讲依赖注入之前先重温一下面向接口编程。

ps: 依赖注入(DI,Dependency Injection)和控制反转(IoC,Inversion of Control)的关系

public interface Vehicle {
    public String run();
}
public class Horse implements Vehicle{
    @Override
    public String run() {
        return "Horse Run.";
    }
}
public class Soldier implements Hero {
  Vehicle vehicle;
  public Soldier(Vehicle vehicle) {
    this.vehicle = vehicle;
  }
  @Override
  public String fight() {
    return vehicle.run() + " charge";
  }
}
  1. 上面就是一种依赖注入式的编程,即Soldier的fight方法依赖一个Vehicle,我们直接把一个Vehicle注入到Soldier类的内部,而不是在fight方法内临时创建一个Vehicle。
  2. 上面是一种面向接口的编程方式。假设Vehicle是我们依赖的一个外部的服务,在测试时我们可以自己实现一个简单的Vehicle,这样对 Soldier类fight方法的测试就不再依赖于那个真实的外部服务了。当然如果不使用面向接口的编程方式,而是使用mock也可以实现同样的功能,但 是个人感觉使用mock晦涩一些,容易出错。

下面正式进入Guice的使用介绍。

先贴一张UML图,展示我的demo程序的类结构。

用Module绑定接口和实现,从Injector中获取实现类

public interface Energy {
    public String getName();
}
public class Fuel implements Energy{
    @Override
    public String getName() {
        return "Fule";
    }
}
@ImplementedBy(Horse.class)
public interface Vehicle {
    public String run();
}
@Singleton
public class Horse implements Vehicle{
  public static int instanceNum=0;
  @Inject
  Energy energy;
  public Horse(){
    instanceNum++;
  }
  @Override
  public String run() {
    return "Horse Run with " + energy.getName();
  }
}
public interface Hero {
    public String fight();
}
public class Soldier implements Hero {
  Vehicle vehicle;
  
  public Soldier(Vehicle vehicle) {
    this.vehicle = vehicle;
  }
  @Override
  public String fight() {
    return vehicle.run() + " charge";
  }
}
 1 public class MyModule implements Module {
 2     @Override
 3     public void configure(Binder binder) {
 4         binder.bind(Vehicle.class).annotatedWith(Fast.class).to(Copter.class).in(Scopes.SINGLETON);
 5         binder.bind(Vehicle.class).annotatedWith(Names.named("Speed")).to(AirShip.class);
 6         binder.requestStaticInjection(AirShip.class);
 7
 8         binder.bind(Energy.class).to(Fuel.class);
 9         binder.bind(Energy.class).annotatedWith(LightEnergy.class).to(Gas.class);
10         binder.bind(Energy.class).annotatedWith(Names.named("CleanEnergy")).to(Solar.class);
11
12         binder.bind(Hero.class).toProvider(HeroProvider.class);
13
14         binder.bind(String.class).annotatedWith(Names.named("point1")).toInstance("0.4");
15         InputStream stream = MyModule.class.getResourceAsStream("app.properties");
16         Properties appProperties = new Properties();
17         try {
18             appProperties.load(stream);
19             Names.bindProperties(binder, appProperties);
20         } catch (IOException e) {
21             binder.addError(e);
22         }
23     }
24 }
public class TestSoldier {
  @Test
  public void test() {
    Injector injector = Guice.createInjector(new MyModule());
    Vehicle vehicle = injector.getInstance(Vehicle.class);
    for (int i = 0; i < 3; i++) {
      Soldier hero = new Soldier(vehicle);
      Assert.assertEquals("Horse Run with Fule charge", hero.fight());
      Assert.assertEquals("inject.Soldier",hero.getClass().getCanonicalName());
    }
    Assert.assertTrue(1 == Horse.instanceNum);
  }
}

贴了一堆代码,现在开始解释。

在Module中指定了接口和实现的绑定方式,在创建Injector时需要把一个Module传进去。当我们想获取一个Vehicle的实例 时,直接从Injector中取就可以了。在接口Vehicle上有一个注解@ImplementedBy(Horse.class),它告诉 Injector:Vehicle的默认实现是Horse。而Horse又依赖Energy,应该使用Energy的哪种实现呢?我们注意到Horse的 energy成员上有一个@Inject注解,这种情况下Energy的实现类从Injector中获取,Injector会去询问Module,在 MyModule的第8行指明了Energy的默认实现类是Fuel。

这里展示了绑定接口和实现的两种方法:在接口上使用@ImplementedBy;在Module中写binder.bind(interface).to(implementation)。一个接口的默认实现不能有两个,即如果在代码中出现:

binder.bind(Energy.class).to(Fuel.class);
binder.bind(Energy.class).to(Gas.class);

运行时会抛异常。

Horse类上面有个@Singleton注解,这告诉Injector只创建Horse的一个实例,任何人从Injector是获得的 Horse都是同一个实体,即它们的hashCode都是相同的。我们故意设置了一个静态变量instanceNum来记录Horse的默认构造函数被调 用的次数,试验证明该构造函数只被调用了1次。@Singleton注解这种方式跟在bind中使用in(Scopes.SINGLETON)效果是一样 的,参见MyModule的第4行。

从Injector中创建的实例是正常的实例,没有做代理和转换。如果是代理的话输出实例的CanonicalName时将是:com.sun.proxy.$Proxy0

MyModule中还有好多语法下文会逐一解释。

public class Solar implements Energy {
    @Override
    public String getName() {
        return "Solar";
    }
}
public class AirShip implements Vehicle{
  @Inject
  @Named("CleanEnergy")
  static Energy energy;
  @Override
  public String run() {
    return "AirShip Fly with " + energy.getName();
  }
}
public class SuperHero implements Hero {
  @Inject
  @Named("Speed")
  Vehicle vehicle;
  @Override
  public String fight() {
    return vehicle.run() + " boom";
  }
}
public class TestSuperHero {
  @Test
  public void test(){
    Injector injector = Guice.createInjector(new MyModule());
    SuperHero hero = injector.getInstance(SuperHero.class);
    Assert.assertEquals("AirShip Fly with Solar boom", hero.fight());
  }
}

Injector.getInstance()方法的参数不仅可以是接口,也可以是一个具体的实现类(此时不需要在Module中指明绑定)。从 Injector中获取SuperHero的实例时,SuperHero中的@Inject属性也会要求从Injector中获得实例。那么 SuperHero使用的是哪一个Vehicle呢?是Horse吗?(因为Horse是Vehicle的默认实现)这里使用了@Named注解,参考 MyModule的第5行我们知道SuperHero使用的Vehicle实际上是AirShip。同理类推,AirShip中使用的Energy是 Solar,参见MyModule的第10行。

注意AirShip的energy属性是静态变量,所以在MyModule中必须写明binder.requestStaticInjection(AirShip.class); 那从AirShip到Energy的依赖链才可以传递下去。

Module中不能自己绑定到自己,但可以绑定到子类。

binder.bind(Implementation).to(Implementation) 错误

binder.bind(Implementation).to(SubImplementation) 正确

public class Gas implements Energy {
    @Override
    public String getName() {
        return "Gas";
    }
}
public class Copter implements Vehicle {
  public static int instanceNum = 0;
  Energy energy;
  public Copter() {
    energy = new Gas();
  }
  @Inject
  private void init(@LightEnergy Energy energy) {
    this.energy = energy;
    instanceNum++;
  }
  @Override
  public String run() {
    return "Copter Fly with " + energy.getName();
  }
}
public class WeaselGirl implements Hero {
  @Inject
  @Fast
  Vehicle vehicle;
  @Override
  public String fight() {
    return vehicle.run() + " shoot";
  }
}
public class TestWeaselGirl {
  @Test
  public void test() {
    Injector injector = Guice.createInjector(new MyModule());
    for (int i = 0; i < 3; i++) {
      WeaselGirl hero = injector.getInstance(WeaselGirl.class);
      Assert.assertEquals("Copter Fly with Gas shoot", hero.fight());
    }
    Assert.assertTrue(1 == Copter.instanceNum);
  }
}

WeaselGirl中依赖的Vehicle带@Fast注解,MyModule的第4行告诉我们此时Vehicle的实现类应该取 Copter。Injectot在创建Copter实例时会优先去调用Copter的带@Inject注解的非私有构造函数,如果没有这种构造函数则去调 用空构造函数,且调用完构造函数后Injector会立即去执行所有@Inject方法。Copter中有个@Inject方法叫init,该方法中的参 数带@LightEnergy注解,MyModule的第9行告诉我们应该创建一个Gas实例传给init()方法。由于MyModule的第4行告诉 Injector:Copter是单例的,所以在test中尽管我们从Injector中获取了3次WeaselGirl(每次创建WeaselGirl 都会去请求一个Copter),但实际上Injetor只创建了一个Copter实例。

至此我们已经学习了绑定接口和实现的3种方式:@ImplementedBy默认绑定;binder时使用 annotatedWith(Annotation)实现条件绑定;binder时使用 annotatedWith(Names.named("str"))实现条件绑定。其实我们也可以自己实现一个Provider,当一个类中有依赖项时 (依赖项是一个接口),由Provider来提供具体的实现类。

public class Mobile implements Vehicle {
  Energy energy;
  public Mobile() {
    energy = new Gas();
  }
  @Inject
  public Mobile(Energy energy) {
    this.energy = energy;
  }
  @Override
  public String run() {
    return "Mobile Run with " + energy.getName();
  }
}
public class VehicleProvider implements Provider<Vehicle> {
  double point1;
  double point2;
  @Inject
  public VehicleProvider(@Named("point1") String p1, @Named("point2") String p2) {
    this.point1 = Double.parseDouble(p1);
    this.point2 = Double.parseDouble(p2);
  }
  @Override
  public Vehicle get() {
    Injector injector = Guice.createInjector(new MyModule());
    double rnd = Math.random();
    if (rnd < point1) {
      return injector.getInstance(Mobile.class);
    } else if (rnd < point2) {
      return injector.getInstance(AirShip.class);
    } else {
      return injector.getInstance(Mobile.class);
    }
  }
}
public class FrogMan implements Hero {
  Vehicle vehicle;
  public FrogMan(Provider<Vehicle> provider) {
    this.vehicle = provider.get();
  }
  @Override
  public String fight() {
    return vehicle.run()+" hack";
  }
}
public class TestFrogMan {
  @Test
  public void test(){
    Injector injector = Guice.createInjector(new MyModule());
    Provider<Vehicle> provider = injector.getInstance(VehicleProvider.class);
    for (int i = 0; i < 20; i++) {
      FrogMan hero = new FrogMan(provider);
      System.out.println(hero.fight());
    }
    System.out.println(Copter.instanceNum);
  }
}

FrogMan的构造函数中需要一个Provider,我们传进去的是一个VehicleProvider。VehicleProvider的 get方法中随机返回Mobile和AirShip两种实体,这里的随机算法又依赖两个参数point1和point2。由于 VehicleProvider是从Injector中获取的,所以Injector在创建VehicleProvider实例时会去调用 VehicleProvider的@Inject构造函数。@Inject构造函数中用到的参数也全部由Injector来提供。MyModule的第 14行告诉我们point1等于0.4,这是一个将常量绑定到PrimitiveType的例子。其实还可以借助于外部的配置文件将常量绑定到一个 String变量,比如MyModule的第15行到第22行就是从一个peoperties文件中读取配置将常量值绑定到String变量,我们的 point2就是通过这种方式赋值的。

public class HeroProvider implements Provider<Hero> {
  @Override
  public Hero get() {
    Injector injector = Guice.createInjector(new MyModule());
    Provider<Vehicle> provider = injector.getInstance(VehicleProvider.class);
    Hero hero = null;
    double d = Math.random();
    if (d < 0.3) {
      hero = new FrogMan(provider);
    } else {
      hero = injector.getInstance(SuperHero.class);
    }
    return hero;
  }
}
public class TestHeroProvider {
  @Test
  public void test(){
    Injector injector = Guice.createInjector(new MyModule());
    for (int i = 0; i < 20; i++) {
      Hero hero = injector.getInstance(Hero.class);
      System.out.println(hero.fight());
    }
    System.out.println(Copter.instanceNum);
  }
}

MyModule的第12行指明了当向Injector请求Hero时,由HeroProvider决定产生哪个具体的Hero。

下载本文的所有代码

maven项目中引入依赖:

<dependency>

<groupId>com.google.inject</groupId>

<artifactId>guice</artifactId>

<version>3.0</version>

</dependency>

时间: 2024-10-04 02:25:52

Guice--Java依赖注入框架的相关文章

史上最好用的依赖注入框架Google Guice【转】

Guice是Google开发的一个轻量级,基于Java5(主要运用泛型与注释特性)的依赖注入框架(IOC).Guice非常小而且快. (其他的依赖注入框架还有Dagger,Spring) Spring框架的依赖注入是家喻户晓的,但是在实际的开发中我们想使用便捷的依赖注入功能,但是又不想引入Spring框架的复杂性,该怎么办呢? 有了Google Guice,这个问题便简单了,首先在你的maven项目里引入 <dependency> <groupId>com.google.injec

依赖注入及AOP简述(四)——“好莱坞原则”和依赖注入框架简介 .

3.2.    “好莱坞原则” 看了前面关于依赖注入概念的描述,我们来提炼出依赖注入的核心思想.如果说传统的组件间耦合方式,例如new.工厂模式等,是一种由开发者主动去构建依赖对象的话,那么依赖注入模式则是其反向的,即被动地等待别人做好一个依赖对象提供给我. 在美国好莱坞众多电影工厂在寻找演员的时候通常奉行着这么一个原则:不要找我,需要的时候我去找你(“Don’tcall us; we’ll call you!”),把相同的思路运用到我们的软件工程里通常也被称作“好莱坞原则”(Hollywood

Spring.NET依赖注入框架学习--入门

Spring.NET依赖注入框架学习--入门 在学些Spring.net框架之前,有必要先脑补一点知识,比如什么是依赖注入?IOC又是什么?控制反转又是什么意思?它们与Spring.net又有什么关系 带着问题,我们一起来看看下面内容(适合刚刚学习或者对依赖注入还太懂的小神看---大神直接飘过) 对以上几个问题都滚瓜烂熟的直接跳下一篇 这里我找到一篇我认为比较好的博文   原地址:http://www.cnblogs.com/jhli/p/6019895.html ---感谢博主分享 1. Io

Dagger——Android上的依赖注入框架

* 你也可以去Github查看这片文章 简介 在开发程序的时候,会用到各种对象,很多对象在使用之前都需要进行初始化.例如你要操作一个SharedPreference,你需要调用getSharedPreferences(String name,int mode)来获取一个对象,然后才能使用它.而如果这个对象会在多个Activity中被使用,你就需要在每个使用的场景中都写下同样的代码.这不仅麻烦,而且增加了出错的可能.dagger的用途就是:让你不需要初始化对象.换句话说,任何对象声明完了就能直接用

Spring.NET依赖注入框架学习--概述

Spring.NET依赖注入框架学习--Spring.NET简介 概述 Spring.NET是一个应用程序框架,其目的是协助开发人员创建企业级的.NET应用程序.它提供了很多方面的功能,比如依赖注入.面向方面编程(AOP).数据访问抽象及ASP.NET扩展等等.Spring.NET以Java版的Spring框架为基础,将Spring.Java的核心概念与思想移植到了.NET平台上. 企业级应用一般由多个物理层组成,每个物理层也经常划分为若干功能层.不同层次之间需要相互协作,例如,业务服务层一般需

浅析依赖注入框架Autofac的使用

Autofac是一款IOC框架,比起Spring.NET,Unity,Castle等等框架,它很轻量级且性能也很高,下面小编给大家介绍下依赖注入框架Autofac的使用. 下面通过代码给大家分享下依赖注入框架Autofac的使用,具体如下所示:  Autofac是一款IOC框架,比较于其他的IOC框架,如Spring.NET,Unity,Castle等等所包含的,它很轻量级性能上也是很高的. 1)解压它的压缩包,主要看到Autofac.dll,Autofac.Configuration.dll,

快如闪电、超轻量级的基于.Net平台的依赖注入框架Ninject

一.为什么要使用依赖注入框架 依赖注入框架也叫IoC容器.它的作用使类与类之间解耦 我们看看为什么要用依赖注入框架,举个几个梨子: 1,高度耦合的类 有一个Order类,Order类是用于订单操作的,DataAccess使用的sqlserver的方式查询订单.看看代码: public class Order { private DataAccess dataAccess = new DataAccess(); public string QueryOrder() { return dataAcc

Android Dagger依赖注入框架浅析

今天接触了Dagger这套android的依赖注入框架(DI框架),感觉跟Spring 的IOC差不多吧.这个框架它的好处是它没有采用反射技术(Spring是用反射的),而是用预编译技术,因为基于反射的DI非常地耗用资源(空间,时间) 由于现在开发都是用Android Studio了,所以我这里大概讲下配置Dagger框架的开发环境,需要怎么做. (由于Android Studio中用Gradle,所以跟传统我们用Eclipse配置的话,直接导入jar包,有点不一样.) 在开始看我的博文前,希望

iOS依赖注入框架系列(二):设置Typhoon

在循环的最后一部分,我们遇到了依赖注入框架为iOS - 台风,并审查其项目Rambler.Pochta使用的基本示例. 这一次,我们进入它的内部结构的研究. 周期"在iOS中右依赖驱动的应用程序" 熟悉台风 该器件台风 模块化台风 台风技巧与诀窍 台风替代品 (可选)Rambler.iOS#3. 依赖注入的iOS. 幻灯片 (可选)Rambler.iOS#3. 依赖注入的iOS. 视频 介绍 要开始分析一个小字典将被广泛使用在这篇文章中的术语: 大会(读作[essembli]). 最近

安卓开发 第一篇 关于依赖注入框架dagger2的使用和理解

(这篇博客真是磨难重重啊,写到一半电脑蓝屏了,还好markdown编辑器保持了部分类容) 最近开始重构项目,在重构项目中用到了依赖注入框架dagger2,发现它确实很方便,能大大加快我们编写代码的速度,同时也很方便我们对于功能模块的解耦.在这里就不过多介绍dagger2了,大家谷歌 百度一下就能得到很多关于dagger2的介绍.学习dagger2是需要一定的学习成本的,我自己开始学习的时候也差不多花了一周的时间才弄明白怎样使用dagger2,下面就说说自己对dagger2的理解和使用方法. da