プロバイダをインジェクトする
通常の依存性注入では、各タイプはただ一つのインスタンスを受け取る。 つまり、RealBillingServiceには一つのCreditCardProcessorと一つのTransactionLogである。 しかし、よりフレキシビリティが必要な場合にはGuiceはプロバイダをバインドする。 プロバイダはそのget()メソッドが呼び出される度に一つの値を返す。
public interface Provider<T> { T get(); }
プロバイダのタイプはパラメトライズされており、Provider<TransactionLog>とProvider<CreditCardProcessor>を区別することができる。 値の注入可能なところでは、その代わりにプロバイダを注入することができる。
public class RealBillingService implements BillingService { private final Provider<CreditCardProcessor> processorProvider; private final Provider<TransactionLog> transactionLogProvider; @Inject public RealBillingService(Provider<CreditCardProcessor> processorProvider, Provider<TransactionLog> transactionLogProvider) { this.processorProvider = processorProvider; this.transactionLogProvider = transactionLogProvider; } public Receipt chargeOrder(PizzaOrder order, CreditCard creditCard) { CreditCardProcessor processor = processorProvider.get(); TransactionLog transactionLog = transactionLogProvider.get(); /* use the processor and transaction log here */ } }
アノテーションされていようがいまいが、すべてのバインディングはそのプロバイダへのビルトインバインディングを作成する。
複数インスタンスのためのプロバイダ
一つの型について複数のインスタンスが必要な場合にプロバイダを使うことができる。 例えば、ピザのチャージが失敗したときに、アプリケーションはサマリエントリと詳細を保存することにしよう。 プロバイダを使うと、必要な時にはいつでも新しいエントリを作成することができる。
public class LogFileTransactionLog implements TransactionLog { private final Provider<LogFileEntry> logFileProvider; @Inject public LogFileTransactionLog(Provider<LogFileEntry> logFileProvider) { this.logFileProvider = logFileProvider; } public void logChargeResult(ChargeResult result) { LogFileEntry summaryEntry = logFileProvider.get(); summaryEntry.setText("Charge " + (result.wasSuccessful() ? "success" : "failure")); summaryEntry.save(); if (!result.wasSuccessful()) { LogFileEntry detailEntry = logFileProvider.get(); detailEntry.setText("Failure result: " + result); detailEntry.save(); } }
遅延ローディングのためのプロバイダ
もし、あるタイプの依存(実装)を作成するのが特に高価(遅い、メモリを食うなど)な場合、プロバイダを使ってその作業を遅らせることができる。 これは、必ずしも依存が必要でないことがある場合に有用である。
public class DatabaseTransactionLog implements TransactionLog { private final Provider<Connection> connectionProvider; @Inject public DatabaseTransactionLog(Provider<Connection> connectionProvider) { this.connectionProvider = connectionProvider; } public void logChargeResult(ChargeResult result) { /* only write failed charges to the database */ if (!result.wasSuccessful()) { Connection connection = connectionProvider.get(); } }
スコープミキシングのためのプロバイダ
より狭いスコープのオブジェクトに依存することは間違いである。 例えば、シングルトンのトランザクションログオブジェクトがリクエストスコープのcurrent userを要求するとする。 userを直接的に注入してしまうと滅茶苦茶になってしまう、なぜならuserはリクエストごとに異なるからである。 プロバイダは値をオンデマンドで作成するため、スコープを安全にミックスすることができる。
@Singleton public class ConsoleTransactionLog implements TransactionLog { private final AtomicInteger failureCount = new AtomicInteger(); private final Provider<User> userProvider; @Inject public ConsoleTransactionLog(Provider<User> userProvider) { this.userProvider = userProvider; } public void logConnectException(UnreachableException e) { failureCount.incrementAndGet(); User user = userProvider.get(); System.out.println("Connection failed for " + user + ": " + e.getMessage()); System.out.println("Failure count: " + failureCount.incrementAndGet()); }