Google Guice学习
学习动力:公司项目使用
官方文档:https://github.com/google/guice/wiki/Motivation
学习阶段:入门
主要部份:
简介
Guice通过注解方式提供以来注入,一种方式是通过@Injuct注入构造函数
class BillingService { private final CreditCardProcessor processor; private final TransactionLog transactionLog; @Inject BillingService(CreditCardProcessor processor, TransactionLog transactionLog) { this.processor = processor; this.transactionLog = transactionLog; } public Receipt chargeOrder(PizzaOrder order, CreditCard creditCard) { ... } }
然后通过Module来绑定接口与实现类
public class BillingModule extends AbstractModule { @Override protected void configure() { /* * This tells Guice that whenever it sees a dependency on a TransactionLog, * it should satisfy the dependency using a DatabaseTransactionLog. */ bind(TransactionLog.class).to(DatabaseTransactionLog.class); /* * Similarly, this binding tells Guice that when CreditCardProcessor is used in * a dependency, that should be satisfied with a PaypalCreditCardProcessor. */ bind(CreditCardProcessor.class).to(PaypalCreditCardProcessor.class); } }
最后通过Injector.getInstance(),通过接口类和Module获得实现类,减少了和具体类之间的耦合,让代码结构更加稳定,易于测试。
public static void main(String[] args) { /* * Guice.createInjector() takes your Modules, and returns a new Injector * instance. Most applications will call this method exactly once, in their * main() method. */ Injector injector = Guice.createInjector(new BillingModule()); /* * Now that we've got the injector, we can build objects. */ BillingService billingService = injector.getInstance(BillingService.class); ... }
Bindings方式
1.Linked Bindings方式
Linked Bindings将一个类型与它的实现类绑定。甚至可以将一个具体类型与它的子类绑定。绑定关系可以串联。
public class BillingModule extends AbstractModule { @Override protected void configure() { bind(TransactionLog.class).to(DatabaseTransactionLog.class); bind(DatabaseTransactionLog.class).to(MySqlDatabaseTransactionLog.class); } }
2.Binding Annotations方式
有时候我们需要将多个实现类绑定到同一个类型上面。比如在BillingService中,我们需要将PayPal credit card processor和Google Checkout processor 绑定到同一个CreditCardProcessor类型上。 Binding Annotations提供几种方式来实现,项目中使用比较多的是这种@Named。我们在需要注入的地方这样写:
public class RealBillingService implements BillingService { @Inject public RealBillingService(@Named("Checkout") CreditCardProcessor processor, TransactionLog transactionLog) { ... }
然后在Module中这样绑定:
bind(CreditCardProcessor.class) .annotatedWith(Names.named("Checkout")) .to(CheckoutCreditCardProcessor.class);
3.Instance Bindings
对于无法继承的类(比如String和Integer),可以使用toInstance()来绑定。
bind(String.class) .annotatedWith(Names.named("JDBC URL")) .toInstance("jdbc:mysql://localhost/pizza"); bind(Integer.class) .annotatedWith(Names.named("login timeout seconds")) .toInstance(10);
4.@Provides Methods
使用@Provides可以通过创建一个方法来提供所需类型的实现,当需要这个类型的时候,injector就会调用这个方法。
public class BillingModule extends AbstractModule { @Override protected void configure() { ... } @Provides TransactionLog provideTransactionLog() { DatabaseTransactionLog transactionLog = new DatabaseTransactionLog(); transactionLog.setJdbcUrl("jdbc:mysql://localhost/pizza"); transactionLog.setThreadPoolSize(30); return transactionLog; } }
5.Provider Bindings
当@Provides 方法越来越复杂的时候,你会考虑使用一个Provider类来管理。Guice提供了Provider这个接口。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()将Provider和需要注入的类型绑定。
public class BillingModule extends AbstractModule { @Override protected void configure() { bind(TransactionLog.class) .toProvider(DatabaseTransactionLogProvider.class); }
6.Untargeted Bindings
当需要绑定具体类,或是使用@ImplementedBy和@ProvidedBy的类型时,可以直接bind(),不加上to()
bind(MyConcreteClass.class); bind(AnotherConcreteClass.class).in(Singleton.class);
7.Constructor Bindings
当需要注入的对象来自第三方库,或是有多个构造函数的时候,我们不能使用@Injuct,这时可以使用@Provides可以解决问题!
但是因为一个什么原因(我没看懂),这也提供了一种toConstructor()来绑定。
public class BillingModule extends AbstractModule { @Override protected void configure() { try { bind(TransactionLog.class).toConstructor( DatabaseTransactionLog.class.getConstructor(DatabaseConnection.class)); } catch (NoSuchMethodException e) { addError(e); } } }
Scopes设定
依赖注入框架既然帮忙注入对象,自然也要管理对象的生命周期Scope。Guice中通过注解来设定对象的Scope。
Guice中典型的生命周期有for the lifetime of an application (@Singleton), a session (@SessionScoped), or a request (@RequestScoped)
可以在相应的实现类上标注。
@Singleton public class InMemoryTransactionLog implements TransactionLog { /* everything here should be threadsafe! */ }
或是通过跟在bind()后的in()来设定。
bind(TransactionLog.class).to(InMemoryTransactionLog.class).in(Singleton.class);
或是在@Provides方法上标注。
@Provides @Singleton
TransactionLog provideTransactionLog() {
...
}
当类型上的Scope和bind()方法后in()的Scope冲突时,bind()的Scope会被使用。
还有一些比较复杂的概念,延迟加载,需要深入理解的时候在再看吧。
Injecting Providers
我理解这一章的内容是使用Provider来替代接口作为注入的入口的好处。
当你需要多次获得一个新的注入对象时,使用Provider
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(); } }
当你需要延迟加载一些重要对象,特别是它并不总是需要加载时,使用Provider
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(); } }
如果你不希望对象的生命周期混杂,比如一个Singleton的Logger和一个RequestScope的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()); }
最佳实践
1.尽量注入不可变对象
如果可以,我们使用构造方法来注入不可变的对象,它们简单,易于分享,可以被组合。
public class RealPaymentService implements PaymentService { private final PaymentQueue paymentQueue; private final Notifier notifier; @Inject RealPaymentRequestService( PaymentQueue paymentQueue, Notifier notifier) { this.paymentQueue = paymentQueue; this.notifier = notifier; } ...
2.仅注入直接依赖的对象
避免注入一个对象,仅仅是为了使用它获得另一个对象。
public class ShowBudgets { private final Account account; @Inject ShowBudgets(Customer customer) { account = customer.getPurchasingAccount(); }
更好的方法是,在Module中使用@Provides 通过Customer创建一个Account对象
public class CustomersModule extends AbstractModule { @Override public void configure() { ... } @Provides Account providePurchasingAccount(Customer customer) { return customer.getPurchasingAccount(); }
这样,需要注入的地方代码会更简单
public class ShowBudgets { private final Account account; @Inject ShowBudgets(Account account) { this.account = account; }
3.解决循环依赖
下面这样的就是循环依赖,Store -> Boss -> Clerk -> Store ...
public class Store { private final Boss boss; //... @Inject public Store(Boss boss) { this.boss = boss; //... } public void incomingCustomer(Customer customer) {...} public Customer getNextCustomer() {...} } public class Boss { private final Clerk clerk; @Inject public Boss(Clerk clerk) { this.clerk = clerk; } } public class Clerk { private final Store shop; @Inject Clerk(Store shop) { this.shop = shop; } void doSale() { Customer sucker = shop.getNextCustomer(); //... } }
文档里有解决的办法。https://github.com/google/guice/wiki/CyclicDependencies
4.避免在Module写入具体的逻辑
public class FooModule { private final String fooServer; public FooModule() { this(null); } public FooModule(@Nullable String fooServer) { this.fooServer = fooServer; } @Override protected void configure() { if (fooServer != null) { bind(String.class).annotatedWith(named("fooServer")).toInstance(fooServer); bind(FooService.class).to(RemoteFooService.class); } else { bind(FooService.class).to(InMemoryFooService.class); } } }
解决方法是将FooModule分成RemoteFooModule 和 InMemoryFooModule。