學習地址
注入
構造函數注入和成員變量注入
注意:在構造函數中注入的參數並不一定都必須在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
- 與其他組件/解決方案整合:注意對象生命周期