【Guice】Guice入門


學習地址

官方指導文檔

慕課網使用Google Guice實現依賴注入

注入

構造函數注入和成員變量注入

注意:在構造函數中注入的參數並不一定都必須在AbstractModule中綁定,Guice會每次新建一個實例注入

綁定(入口)-->Guice-->@Inject(出口)

主要使用構造函數注入,也可以給成員變量注入但是一般不用。使用@Inject構造器注入如下所示:

// 先綁定再注入
// 1、綁定
public class ServerModule extends AbstractModule {
	@Override
	protected void configure() {
	
		bind(OrderService.class).to(OrderServiceImpl.class);
		bind(PaymentService.class).to(PaymentServiceImpl.class);
		bind(PriceService.class).to(PriceServiceImpl.class);
}
// 2、注入
public class OrderServiceImpl implements OrderService {

	// Dependencies,使用構造器注入后不再改變,使用final修飾
	private final PriceService priceService;
	private final PaymentService paymentService;
	private final SessionManager sessionManager;
    private final NoBindService nobindService;

	// States 這種成員變量會被改變,不能添加final修飾
	private Long ordersPaid = 0L;

    // 下面的NoBindService並未在Module中綁定,但是是可以正常注入的,綁定和注入並不是強相關
	@Inject
	public OrderServiceImpl(PriceService priceService,
			PaymentService paymentService,
			SessionManager sessionManager, NoBindService nobindService) {
		super();
		this.priceService = priceService;
		this.paymentService = paymentService;
		this.sessionManager = sessionManager;
        this.nobindService = nobindService;
	}
}

構造函數注入:

  • 使用final來區分dependency和狀態
  • 注入時不需要考慮如何實現/綁定

Provider注入

Guice 使用Provider 表示創建對象的工廠以滿足依賴要求。Provider是只包含一個方法的接口:

interface Provider<T> {
  /** Provides an instance of T.**/
  T get();
}

應用場景

1、懶加載,如數據庫連接比較耗時,可以在需要的時候通過Provider獲取,而不是直接使用構造器注入指定

  • databaseConnectionProvider.get()

2、多個實例,使用構造器注入只能選擇固定的日志輸出器

  • logEntryProvider.get()

注入Provider方式

我們可以向Guice請求Provider,不論如何綁定(注入和綁定是完全分開的過程)

DataBaseConnection dbConn

-->Provider<DataBaseConnection> dbConnProvider

一般無需自己實現Provider,Guice已經提供,且會考慮生命對象周期,但是需要時可以自己實現Provider。

通過@Provides 綁定Provider之后,就可以通過其唯一的get方法獲取想要的值。

// 先綁定,再注入
// 1、綁定
public class ServerModule extends AbstractModule {

	@Provides Long generateSesssionId() {
		return System.currentTimeMillis();
	}
}
// 2、注入
public class SessionManager {
	private final Provider<Long> sessionIdProvider;

	@Inject
	public SessionManager(Provider<Long> sessionIdProvider) {
		super();
		this.sessionIdProvider = sessionIdProvider;
	}

	public Long getSessionId() {
		return sessionIdProvider.get();
	}

}

命名注入

場景,如果綁定了多個同類型的實例,如String等且未進行標識,則注入時就會發生沖突,此時需要使用命名的方式加以區分。

有以下兩種注入形式:

@Inject @Named("dbSpec") private String dbSpec;
@Inject @LogFileName private String logFileName;

使用@Named

  • 參數來自配置文件,命令行
  • 為了開發方便

使用屬性

  • 通常采用此方法

下面是示例,獲取名為@SessionId的Long類型

//先定義注解、再綁定、最后注入
// 1、定義注解
@Retention(RetentionPolicy.RUNTIME)
@BindingAnnotation
public @interface SessionId {

}
// 2、綁定
public class ServerModule extends AbstractModule {
	@Provides @SessionId Long generateSesssionId() {
		return System.currentTimeMillis();
	}
}
// 3、注入
public class SessionManager {
	private final Provider<Long> sessionIdProvider;

	@Inject
	public SessionManager(
			@SessionId Provider<Long> sessionIdProvider) {
		super();
		this.sessionIdProvider = sessionIdProvider;
	}

	public Long getSessionId() {
		return sessionIdProvider.get();
	}

}

綁定

結構

注入和綁定是分開的,

分類

一般通過繼承AbstractModule,實現其configure方法進行綁定。

  • 類名綁定
  • 實例綁定 Instance Bindings
  • 連接綁定 Linked Bindings
  • Provider綁定 Provider Bindings
  • 命名綁定 Binding Annotations
  • 泛型綁定
  • 集合綁定

類名綁定和實例綁定

類名綁定是最常見的綁定方式,將類的實現綁定到接口類上;

實例綁定除了可以綁定具體類到它上,還可以直接使用new的實例綁定。


public class ServerModule extends AbstractModule {

	@Override
	protected void configure() {
        // 類名綁定 將實現類OrderServiceImpl綁定到接口OrderService上
        // 表示需要OrderService時,注入OrderServiceImpl的一個實例
		bind(OrderService.class).to(OrderServiceImpl.class);
        // 實例綁定 直接將具體的實例綁定
        // 表示需要PriceService時,注入PriceServiceImpl實例
        bind(PriceService.class).toInstance(new PriceServiceImpl());

	}
}

連接綁定

連接綁定可以不斷將子類綁定到父類(接口)上。可以是鏈式結構。下面的示例中,當請求PriceService時,最終會返回new 出來的PriceServiceImpl實例。可以按下面的綁定持續綁定,只要不形成環,最終Guice會找到最后綁定的值。

public class ServerModule extends AbstractModule {

	@Override
	protected void configure() {
        // 類名綁定 將實現類PriceServiceImpl綁定到接口PriceService上
        // 表示需要PriceService時,注入PriceServiceImpl的一個實例
		bind(PriceService.class).to(PriceServiceImpl.class);
        // 實例綁定 直接將具體的實例綁定
        // 表示需要PriceServiceImpl時,注入PriceServiceImpl實例
        bind(PriceServiceImpl.class).toInstance(new PriceServiceImpl());
	}
}

Provider綁定

當你創建對象時,使用@Provides方法。該方法必須定義在一個module中,同時要使用@Provides注解。返回值是綁定類型。,當injector需要該類型實例時,會調用該方法。

方式一、在module中像上面的幾種綁定一樣綁定,然后直接使用Provider 即可,如下示例

// 1、先綁定
public class ServerModule extends AbstractModule {

	@Override
	protected void configure() {
        // 類名綁定 將實現類PriceServiceImpl綁定到接口PriceService上
        // 表示需要PriceService時,注入PriceServiceImpl的一個實例
				bind(PriceService.class).to(PriceServiceImpl.class);
	}
}
// 2、直接注入Provider
public class OrderServiceImpl implements OrderService {

	// Dependencies,使用構造器注入后不再改變,使用final修飾
	private final Provider<PriceService> priceServiceProvider;
	
	@Inject
	public OrderServiceImpl(Provider<PriceService> priceServiceProvider,) {
		super();
		this.priceServiceProvider = priceServiceProvider;
	}
}

方式二、使用@Provides注解,這種方式比較常用 ,可以帶參數或不帶參數

//1、先綁定(在AbstarctModule中)
public class ServerModule extends AbstractModule {
    
    @Override
  protected void configure() {
    bind(PriceService.class).to(PriceServiceImpl.class);
  }

  @Provides
  TransactionLog provideTransactionLog() {
    DatabaseTransactionLog transactionLog = new DatabaseTransactionLog();
    transactionLog.setJdbcUrl("jdbc:mysql://localhost/pizza");
    transactionLog.setThreadPoolSize(30);
    return transactionLog;
  }
    // 可以帶命名綁定
	@Provides @SessionId Long generateSesssionId() {
		return System.currentTimeMillis();
	}
    // 可以帶參數,Guice會自動注入參數,默認每次是個新的實例
	@Provides List<String>  getCurrencyList(PriceService priceService) {
		return priceService.getCurrencyList();
	}
    
}
// 2、注入
public class SessionManager {
	private final Provider<Long> sessionIdProvider;
    // 不使用Provider包裹也可以直接注入List<String>
	private final Provider<List<String>> currencyListProvider;
	@Inject
	public SessionManager(
			@SessionId Provider<Long> sessionIdProvider, Provider<List<String>> currencyListProvider) {
		super();
		this.sessionIdProvider = sessionIdProvider;
        this.currencyList = currencyList;

	}

	public Long getSessionId() {
		return sessionIdProvider.get();
	}
    public List<String> getCurrencyList() {
		return currencyListProvider.get();
	}

}

當@Provides方法變得復雜,可以考慮將他們移動到自己的類中。provider類實現了Guice的Provider接口。

Provider的實現類包含它的所有依賴,通過使用@Inject注解構造器接收依賴參數。通過實現Provider接口定義完全類型安全的返回類型。

public class DatabaseTransactionLogProvider implements Provider<TransactionLog> {
  private final Connection connection;

  @Inject
  public DatabaseTransactionLogProvider(Connection connection) {
    this.connection = connection;
  }

  public TransactionLog get() {
    DatabaseTransactionLog transactionLog = new DatabaseTransactionLog();
    transactionLog.setConnection(connection);
    return transactionLog;
  }
}

最后,通過.toProvider進行綁定,這樣就可以在TransactionLog實例中獲取DatabaseTransactionLogProvider。

public class BillingModule extends AbstractModule {
  @Override
  protected void configure() {
    bind(TransactionLog.class)
        .toProvider(DatabaseTransactionLogProvider.class);
  }
}

命名綁定

當想要指定同一類型的不同綁定時需要使用可以指定名稱。有兩種方式:一是自定義注解命名,二是使用@Named注解

//1、先綁定(在AbstarctModule中)
public class ServerModule extends AbstractModule {
    
    @Override
  protected void configure() {
    bind(PriceService.class).to(PriceServiceImpl.class);
  }

  @Provides
  TransactionLog provideTransactionLog() {
    DatabaseTransactionLog transactionLog = new DatabaseTransactionLog();
    transactionLog.setJdbcUrl("jdbc:mysql://localhost/pizza");
    transactionLog.setThreadPoolSize(30);
    return transactionLog;
  }
    // 可以帶命名綁定
	@Provides @SessionId Long generateSesssionId() {
		return System.currentTimeMillis();
	}
    // 可以帶參數
	@Provides @Named("currencyList") List<String>  getCurrencyList(PriceService priceService) {
		return priceService.getCurrencyList();
	}
    
}
// 2、注入
public class SessionManager {
	private final Provider<Long> sessionIdProvider;
    // 不使用Provider包裹也可以直接注入List<String>
	private final Provider<List<String>> currencyListProvider;
	@Inject
	public SessionManager(
			@SessionId Provider<Long> sessionIdProvider,  @Named("currencyList") Provider<List<String>> currencyListProvider) {
		super();
		this.sessionIdProvider = sessionIdProvider;
        this.currencyList = currencyList;

	}

	public Long getSessionId() {
		return sessionIdProvider.get();
	}
    public List<String> getCurrencyList() {
		return currencyListProvider.get();
	}

}

如果不通過@Provides添加命名綁定@SessionId配置也可以通過在module的configure的bind方式綁定,但是這種方式一旦實例化就不會改變,注意與@Provides方式的區別。

public class ServerModule extends AbstractModule {
    
    @Override
  protected void configure() {
        bind(Long.class).annotateWith(SessionId.class).toInstance(System.currentTimeMillis());
 bind(Long.class).annotateWith(Nameds.named("appId")).toInstance(123L);
  }
}

泛型綁定

public class ServerModule extends AbstractModule {
    
    @Override
  protected void configure() {
      // 也可以添加命名綁定,使用annotateWith
        bind(new TypeLiteral<List<String>>(){}).toInstance(Arrays.asList("CNY","USD"));
  }
}

集合綁定

上面泛型綁定的例子也是集合綁定,集合綁定還可以使用Multibinder在不同的module中配置到同一個集合中。

// 1、不同的module中使用Multibinder綁定set集合
public class GlobalModule extends AbstractModule {

	@Override
	protected void configure() {
		// Adds EUR, USD support
        // 此處也可以不單獨提取currencyBinder,直接使用Multibinder.newSetBinder(binder(), String.class).addBinding...的形式,也是可以根據類型獲取到set<String>
		Multibinder<String> currencyBinder =
				Multibinder.newSetBinder(binder(), String.class);
		currencyBinder.addBinding().toInstance("EUR");
		currencyBinder.addBinding().toInstance("USD");
	}

}
public class ChinaModule extends AbstractModule {

	@Override
	protected void configure() {
		// Adds CNY support
		Multibinder.newSetBinder(binder(), String.class)
			.addBinding().toInstance("CNY");

	}

}
// 組合 module
public class ServerModule extends AbstractModule {

	@Override
	protected void configure() {
		install(new ChinaModule());
		install(new GlobalModule());
	}
}
// 2、注入Set
public class PriceServiceImpl implements PriceService {
	private final Set<String> supportedCurrencies;
	@Inject
	public PriceServiceImpl(
			Set<String> supportedCurrencies) {
		super();
		this.supportedCurrencies = supportedCurrencies;

	}

	@Override
	public Set<String> getSupportedCurrencies() {
		return supportedCurrencies;
	}
}

Module的組織

Module主要有以下三種關系

1、並列

Guice,createInjector(module1,module2,...)

2、嵌套

通過如下語句將一個module嵌套另一個module中

install(module)

3、覆蓋

一個module中的綁定可能覆蓋另一個module的綁定。下面的意思是如果遇到沖突綁定,則用后面module的配置

Modules.override(module1).with(module2)

Guice的啟動

Guice通過如下方式啟動。

Injector injector = Guice.createInjector(new ServerModule());

OrderService orderService = injector.getInstance(OrderService.class);

Module中配置的各種bind是何時被運行的?

  • Module里存放了很多表達式,啟動時並不會將bind的實例初始化
  • Module不被”運行“
  • Guice.createInjector()時記錄所有表達式

系統何時初始化?

  • Guice中沒有”初始化“概念,沒有Spring 的Configuration Time
  • injector.getInstance()時根據表達式調用構造函數

因此在執行 Guice.createInjector時會比較快,真正花時間的是調用injector.getInstance()方法調用構造函數,要善於使用Provider,將比較耗時的操作使用Provider進行懶加載,即使用時再調用。

生命周期/作用域

如果不顯式指定Scope,Guice默認為每次注入生成新的實例。

但是像數據庫連接實例,通常只需要一個,需要顯式配置其為Singleton。

作用域分類

默認作用域

一般實例,無狀態,構造速度快,通常使用默認即可

Singleton作用域

  • 有狀態的實例
  • 構造速度慢的實例
  • 需要線程安全的實例
  • 如:數據庫,網絡連接

Session/Request作用域

  • 含有session/request信息的實例
  • 有狀態的實例
  • 如SessionState等

綁定方式

有三種方式配置作用域,以下三種選其一即可。

方式一、可以通過在類上添加@Singleton注解聲明作用域為單例,其他兩種類似@SessionScoped/@RequestScoped,后兩種需要servlet環境

@Singleton
public class SingletonDemo{}

方式二、通過在module的configure方法的bind中,使用in語句

public class ServerModule extends AbstractModule {

	@Override
	protected void configure() {
		
		// 綁定map
			bind(new TypeLiteral<Cache<String, String>>(){})
			.to(GuiceDemoCache.class).in(Singleton.class);
	}
}

方式三、通過@Provides方法屬性指定

@Provides
@Singleton
Cache<String,String> getCache(){
    return new GuiceDemoCache();
}

實例

// 1、類定義
@Singleton
public class GuiceDemoCache
	extends AbstractCache<String, String> {

	private final Map<String, String> keyValues =
			new ConcurrentHashMap<>();

	@Override
	public String getIfPresent(Object key) {
		return keyValues.get(key);
	}

	@Override
	public void put(String key, String value) {
		keyValues.put(key, value);
	}
}
// 2、綁定
public class ServerModule extends AbstractModule {

	@Override
	protected void configure() {
		install(new ChinaModule());
		install(new GlobalModule());

		bind(OrderService.class).to(OrderServiceImpl.class);
		bind(PaymentService.class).to(PaymentServiceImpl.class);
		bind(PriceService.class).to(PriceServiceImpl.class);
		// 綁定map
		bind(new TypeLiteral<Cache<String, String>>(){})
			.to(GuiceDemoCache.class);

	}

}
// 3、注入
public class PaymentServiceImpl implements PaymentService {
	private final Cache<String, String> cache;

	@Inject
	public PaymentServiceImpl(Cache<String, String> cache) {
		super();
		this.cache = cache;
	}

	@Override @Logged
	public void pay(long orderId, long price, Long sessionId) {
		// TODO Auto-generated method stub
	}

	void putCache(String key, String value) {
		cache.put(key, value);
	}
}

public class PriceServiceImpl implements PriceService {
	private final Set<String> supportedCurrencies;
	private final Cache<String, String> cache;

	@Inject
	public PriceServiceImpl(
			Set<String> supportedCurrencies,
			Cache<String, String> cache) {
		super();
		this.supportedCurrencies = supportedCurrencies;
		this.cache = cache;
	}

	@Override
	public Set<String> getSupportedCurrencies() {
		return supportedCurrencies;
	}

	@Override
	public long getPrice(long orderId) {
		throw new UnsupportedOperationException();
	}

	String getCachedValue(String key) {
		return cache.getIfPresent(key);
	}
}

AOP

直接學習視頻 Guice AOP

Guice vs Spring

1、Guice不是Spring的重制版本

2、Spring綁定

  • 配置文件體現完整裝配結構
  • 大量使用字符串:實例名:屬性名
  • 在Config Time完成組裝

3、Guice綁定

  • Java代碼描述綁定規則
  • 每個注入/綁定處僅描述局部依賴
  • 沒有Config Time

4、Guice的優點

  • 代碼量少
  • 性能優異
  • 支持泛型
  • Constructor綁定:Immutable objects,不再需要getter/setter
  • 強類型
  • 易於重構

5、Guice的缺點

  • Module和綁定規則不易於理解
  • 文檔教程少,社區資源少
  • 無法方便搭出特殊結構:如循環依賴
  • Guice僅僅是依賴注入框架,而Spring更重量級,涵蓋更多

6、從Spring 遷移到Guice?

  • 不建議這么做
  • Spring 與Guice整合,兩者可以兼容

7、新項目需要選擇依賴注入方案?

  • 可以嘗試Guice
  • 與其他組件/解決方案整合:注意對象生命周期


免責聲明!

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



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