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來提供具體的實現類。但是Injector的那些特性(比如@Singleton等)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>


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM