Google 开源的这个库,性能快到让程序员飞起来!

来自| 开发者技术前线  编辑 | 可可

作者:GinoBeFunny

来源:https://url.cn/5cb6Lkw

Google开源的一个依赖注入类库Guice,相比于Spring IoC 来说更小更快。Elasticsearch大量使用了Guice,本文简单的介绍下Guice的基本概念和使用方式。

学习目标

  • 概述:了解Guice是什么,有什么特点;
  • 快速开始:通过实例了解Guice;
  • 核心概念:了解Guice涉及的核心概念,如绑定(Binding)、范围(Scope)和注入(Injection);
  • 最佳实践:官方推荐的最佳实践;

Guice概述

  • Guice是Google开源的依赖注入类库,通过Guice减少了对工厂方法和new的使用,使得代码更易交付、测试和重用;
  • Guice可以帮助我们更好地设计API,它是个轻量级非侵入式的类库;
  • Guice对开发友好,当有异常发生时能提供更多有用的信息用于分析;

快速开始

假设一个在线预订Pizza的网站,其有一个计费服务接口:


  1. public interface BillingService {
  2. /**
  3. * 通过信用卡支付。无论支付成功与否都需要记录交易信息。
  4. *
  5. * @return 交易回执。支付成功时返回成功信息,否则记录失败原因。
  6. */
  7. Receipt chargeOrder(PizzaOrder order, CreditCard creditCard);
  8. }

使用new的方式获取信用卡支付处理器和数据库交易日志记录器:


  1. public class RealBillingService implements BillingService {
  2. public Receipt chargeOrder(PizzaOrder order, CreditCard creditCard) {
  3. CreditCardProcessor processor = new PaypalCreditCardProcessor();
  4. TransactionLog transactionLog = new DatabaseTransactionLog();
  5. try {
  6. ChargeResult result = processor.charge(creditCard, order.getAmount());
  7. transactionLog.logChargeResult(result);
  8. return result.wasSuccessful()
  9. ? Receipt.forSuccessfulCharge(order.getAmount())
  10. : Receipt.forDeclinedCharge(result.getDeclineMessage());
  11. } catch (UnreachableException e) {
  12. transactionLog.logConnectException(e);
  13. return Receipt.forSystemFailure(e.getMessage());
  14. }
  15. }
  16. }

使用new的问题是使得代码耦合,不易维护和测试。比如在UT里不可能直接用真实的信用卡支付,需要Mock一个CreditCardProcessor。相比于new,更容易想到的改进是使用工厂方法,但是工厂方法在测试中仍存在问题(因为通常使用全局变量来保存实例,如果在用例中未重置可能会影响其他用例)。更好的方式是通过构造方法注入依赖:


  1. public class RealBillingService implements BillingService {
  2. private final CreditCardProcessor processor;
  3. private final TransactionLog transactionLog;
  4. public RealBillingService(CreditCardProcessor processor,
  5. TransactionLog transactionLog) {
  6. this.processor = processor;
  7. this.transactionLog = transactionLog;
  8. }
  9. public Receipt chargeOrder(PizzaOrder order, CreditCard creditCard) {
  10. try {
  11. ChargeResult result = processor.charge(creditCard, order.getAmount());
  12. transactionLog.logChargeResult(result);
  13. return result.wasSuccessful()
  14. ? Receipt.forSuccessfulCharge(order.getAmount())
  15. : Receipt.forDeclinedCharge(result.getDeclineMessage());
  16. } catch (UnreachableException e) {
  17. transactionLog.logConnectException(e);
  18. return Receipt.forSystemFailure(e.getMessage());
  19. }
  20. }
  21. }

对于真实的网站应用可以注入真正的业务处理服务类:


  1. public static void main(String[] args) {
  2. CreditCardProcessor processor = new PaypalCreditCardProcessor();
  3. TransactionLog transactionLog = new DatabaseTransactionLog();
  4. BillingService billingService
  5. = new RealBillingService(processor, transactionLog);
  6. ...
  7. }

而在测试用例中可以注入Mock类:


  1. public class RealBillingServiceTest extends TestCase {
  2. private final PizzaOrder order = new PizzaOrder(100);
  3. private final CreditCard creditCard = new CreditCard("1234", 11, 2010);
  4. private final InMemoryTransactionLog transactionLog = new InMemoryTransactionLog();
  5. private final FakeCreditCardProcessor processor = new FakeCreditCardProcessor();
  6. public void testSuccessfulCharge() {
  7. RealBillingService billingService
  8. = new RealBillingService(processor, transactionLog);
  9. Receipt receipt = billingService.chargeOrder(order, creditCard);
  10. assertTrue(receipt.hasSuccessfulCharge());
  11. assertEquals(100, receipt.getAmountOfCharge());
  12. assertEquals(creditCard, processor.getCardOfOnlyCharge());
  13. assertEquals(100, processor.getAmountOfOnlyCharge());
  14. assertTrue(transactionLog.wasSuccessLogged());
  15. }
  16. }

那通过Guice怎么实现依赖注入呢?首先我们需要告诉Guice如果找到接口对应的实现类,这个可以通过模块来实现:


  1. public class BillingModule extends AbstractModule {
  2. @Override
  3. protected void configure() {
  4. bind(TransactionLog.class).to(DatabaseTransactionLog.class);
  5. bind(CreditCardProcessor.class).to(PaypalCreditCardProcessor.class);
  6. bind(BillingService.class).to(RealBillingService.class);
  7. }
  8. }

这里的模块只需要实现Module接口或继承自AbstractModule,然后在configure方法中设置绑定(后面会继续介绍)即可。然后只需在原有的构造方法中增加@Inject注解即可注入


  1. public class RealBillingService implements BillingService {
  2. private final CreditCardProcessor processor;
  3. private final TransactionLog transactionLog;
  4. @Inject
  5. public RealBillingService(CreditCardProcessor processor,
  6. TransactionLog transactionLog) {
  7. this.processor = processor;
  8. this.transactionLog = transactionLog;
  9. }
  10. public Receipt chargeOrder(PizzaOrder order, CreditCard creditCard) {
  11. try {
  12. ChargeResult result = processor.charge(creditCard, order.getAmount());
  13. transactionLog.logChargeResult(result);
  14. return result.wasSuccessful()
  15. ? Receipt.forSuccessfulCharge(order.getAmount())
  16. : Receipt.forDeclinedCharge(result.getDeclineMessage());
  17. } catch (UnreachableException e) {
  18. transactionLog.logConnectException(e);
  19. return Receipt.forSystemFailure(e.getMessage());
  20. }
  21. }
  22. }

最后,再看看main方法中是如何调用的:


  1. public static void main(String[] args) {
  2. Injector injector = Guice.createInjector(new BillingModule());
  3. BillingService billingService = injector.getInstance(BillingService.class);
  4. ...
  5. }

绑定

连接绑定

连接绑定是最常用的绑定方式,它将一个类型和它的实现进行映射。下面的例子中将TransactionLog接口映射到它的实现类DatabaseTransactionLog。


  1. public class BillingModule extends AbstractModule {
  2. @Override
  3. protected void configure() {
  4. bind(TransactionLog.class).to(DatabaseTransactionLog.class);
  5. }
  6. }

连接绑定还支持链式,比如下面的例子最终将TransactionLog接口映射到实现类MySqlDatabaseTransactionLog。


  1. public class BillingModule extends AbstractModule {
  2. @Override
  3. protected void configure() {
  4. bind(TransactionLog.class).to(DatabaseTransactionLog.class);
  5. bind(DatabaseTransactionLog.class).to(MySqlDatabaseTransactionLog.class);
  6. }
  7. }

注解绑定

通过一个类型可能存在多个实现,比如在信用卡支付处理器中存在PayPal的支付和Google支付,这样通过连接绑定就搞不定。这时我们可以通过注解绑定来实现:


  1. @BindingAnnotation
  2. @Target({ FIELD, PARAMETER, METHOD })
  3. @Retention(RUNTIME)
  4. public @interface PayPal {}
  5. public class RealBillingService implements BillingService {
  6. @Inject
  7. public RealBillingService(@PayPal CreditCardProcessor processor,
  8. TransactionLog transactionLog) {
  9. ...
  10. }
  11. }
  12. // 当注入的方法参数存在@PayPal注解时注入PayPalCreditCardProcessor实现
  13. bind(CreditCardProcessor.class).annotatedWith(PayPal.class).to(PayPalCreditCardProcessor.class);

可以看到在模块的绑定时用annotatedWith方法指定具体的注解来进行绑定,这种方式有一个问题就是我们必须增加自定义的注解来绑定,基于此Guice内置了一个@Named注解满足该场景:


  1. public class RealBillingService implements BillingService {
  2. @Inject
  3. public RealBillingService(@Named("Checkout") CreditCardProcessor processor,
  4. TransactionLog transactionLog) {
  5. ...
  6. }
  7. }
  8. // 当注入的方法参数存在@Named注解且值为Checkout时注入CheckoutCreditCardProcessor实现
  9. bind(CreditCardProcessor.class).annotatedWith(Names.named("Checkout")).to(CheckoutCreditCardProcessor.class);

实例绑定

将一个类型绑定到一个具体的实例而非实现类,这个通过是在无依赖的对象(比如值对象)中使用。如果toInstance包含复杂的逻辑会导致启动速度,此时应该通过@Provides方法绑定。


  1. bind(String.class).annotatedWith(Names.named("JDBC URL")).toInstance("jdbc:mysql://localhost/pizza");
  2. bind(Integer.class).annotatedWith(Names.named("login timeout seconds")).toInstance(10);

@Provides方法绑定

模块中定义的、带有@Provides注解的、方法返回值即为绑定映射的类型。


  1. public class BillingModule extends AbstractModule {
  2. @Override
  3. protected void configure() {
  4. ...
  5. }
  6. @Provides
  7. TransactionLog provideTransactionLog() {
  8. DatabaseTransactionLog transactionLog = new DatabaseTransactionLog();
  9. transactionLog.setJdbcUrl("jdbc:mysql://localhost/pizza");
  10. transactionLog.setThreadPoolSize(30);
  11. return transactionLog;
  12. }
  13. @Provides @PayPal
  14. CreditCardProcessor providePayPalCreditCardProcessor(@Named("PayPal API key") String apiKey) {
  15. PayPalCreditCardProcessor processor = new PayPalCreditCardProcessor();
  16. processor.setApiKey(apiKey);
  17. return processor;
  18. }
  19. }

Provider绑定

如果使用@Provides方法绑定逻辑越来越复杂时就可以通过Provider绑定(一个实现了Provider接口的实现类)来实现。


  1. public interface Provider<T> {
  2. T get();
  3. }
  4. public class DatabaseTransactionLogProvider implements Provider<TransactionLog> {
  5. private final Connection connection;
  6. @Inject
  7. public DatabaseTransactionLogProvider(Connection connection) {
  8. this.connection = connection;
  9. }
  10. public TransactionLog get() {
  11. DatabaseTransactionLog transactionLog = new DatabaseTransactionLog();
  12. transactionLog.setConnection(connection);
  13. return transactionLog;
  14. }
  15. }
  16. public class BillingModule extends AbstractModule {
  17. @Override
  18. protected void configure() {
  19. bind(TransactionLog.class).toProvider(DatabaseTransactionLogProvider.class);
  20. }
  21. }

无目标绑定

当我们想提供对一个具体的类给注入器时就可以采用无目标绑定。


  1. bind(MyConcreteClass.class);
  2. bind(AnotherConcreteClass.class).in(Singleton.class);

构造器绑定

3.0新增的绑定,适用于第三方提供的类或者是有多个构造器参与依赖注入。通过@Provides方法可以显式调用构造器,但是这种方式有一个限制:无法给这些实例应用AOP。


  1. public class BillingModule extends AbstractModule {
  2. @Override
  3. protected void configure() {
  4. try {
  5. bind(TransactionLog.class).toConstructor(DatabaseTransactionLog.class.getConstructor(DatabaseConnection.class));
  6. } catch (NoSuchMethodException e) {
  7. addError(e);
  8. }
  9. }
  10. }

范围

默认情况下,Guice每次都会返回一个新的实例,这个可以通过范围(Scope)来配置。常见的范围有单例(@Singleton)、会话(@SessionScoped)和请求(@RequestScoped),另外还可以通过自定义的范围来扩展。

范围的注解可以应该在实现类、@Provides方法中,或在绑定的时候指定(优先级最高):


  1. @Singleton
  2. public class InMemoryTransactionLog implements TransactionLog {
  3. /* everything here should be threadsafe! */
  4. }
  5. // scopes apply to the binding source, not the binding target
  6. bind(TransactionLog.class).to(InMemoryTransactionLog.class).in(Singleton.class);
  7. @Provides @Singleton
  8. TransactionLog provideTransactionLog() {
  9. ...
  10. }

另外,Guice还有一种特殊的单例模式叫饥饿单例(相对于懒加载单例来说):


  1. // Eager singletons reveal initialization problems sooner,
  2. // and ensure end-users get a consistent, snappy experience.
  3. bind(TransactionLog.class).to(InMemoryTransactionLog.class).asEagerSingleton();

注入

依赖注入的要求就是将行为和依赖分离,它建议将依赖注入而非通过工厂类的方法去查找。注入的方式通常有构造器注入、方法注入、属性注入等。


  1. // 构造器注入
  2. public class RealBillingService implements BillingService {
  3. private final CreditCardProcessor processorProvider;
  4. private final TransactionLog transactionLogProvider;
  5. @Inject
  6. public RealBillingService(CreditCardProcessor processorProvider,
  7. TransactionLog transactionLogProvider) {
  8. this.processorProvider = processorProvider;
  9. this.transactionLogProvider = transactionLogProvider;
  10. }
  11. }
  12. // 方法注入
  13. public class PayPalCreditCardProcessor implements CreditCardProcessor {
  14. private static final String DEFAULT_API_KEY = "development-use-only";
  15. private String apiKey = DEFAULT_API_KEY;
  16. @Inject
  17. public void setApiKey(@Named("PayPal API key") String apiKey) {
  18. this.apiKey = apiKey;
  19. }
  20. }
  21. // 属性注入
  22. public class DatabaseTransactionLogProvider implements Provider<TransactionLog> {
  23. @Inject Connection connection;
  24. public TransactionLog get() {
  25. return new DatabaseTransactionLog(connection);
  26. }
  27. }
  28. // 可选注入:当找不到映射时不报错
  29. public class PayPalCreditCardProcessor implements CreditCardProcessor {
  30. private static final String SANDBOX_API_KEY = "development-use-only";
  31. private String apiKey = SANDBOX_API_KEY;
  32. @Inject(optional=true)
  33. public void setApiKey(@Named("PayPal API key") String apiKey) {
  34. this.apiKey = apiKey;
  35. }
  36. }

辅助注入

辅助注入(Assisted Inject)属于Guice扩展的一部分,它通过@Assisted注解自动生成工厂来加强非注入参数的使用。


  1. // RealPayment中有两个参数startDate和amount无法直接注入
  2. public class RealPayment implements Payment {
  3. public RealPayment(
  4. CreditService creditService, // from the Injector
  5. AuthService authService, // from the Injector
  6. Date startDate, // from the instance‘s creator
  7. Money amount); // from the instance‘s creator
  8. }
  9. ...
  10. }
  11. // 一种方式是增加一个工厂来构造
  12. public interface PaymentFactory {
  13. public Payment create(Date startDate, Money amount);
  14. }
  15. public class RealPaymentFactory implements PaymentFactory {
  16. private final Provider<CreditService> creditServiceProvider;
  17. private final Provider<AuthService> authServiceProvider;
  18. @Inject
  19. public RealPaymentFactory(Provider<CreditService> creditServiceProvider,
  20. Provider<AuthService> authServiceProvider) {
  21. this.creditServiceProvider = creditServiceProvider;
  22. this.authServiceProvider = authServiceProvider;
  23. }
  24. public Payment create(Date startDate, Money amount) {
  25. return new RealPayment(creditServiceProvider.get(),
  26. authServiceProvider.get(), startDate, amount);
  27. }
  28. }
  29. bind(PaymentFactory.class).to(RealPaymentFactory.class);
  30. // 通过@Assisted注解可以减少RealPaymentFactory
  31. public class RealPayment implements Payment {
  32. @Inject
  33. public RealPayment(
  34. CreditService creditService,
  35. AuthService authService,
  36. @Assisted Date startDate,
  37. @Assisted Money amount);
  38. }
  39. ...
  40. }
  41. // Guice 2.0
  42. //bind(PaymentFactory.class).toProvider(FactoryProvider.newFactory(PaymentFactory.class, RealPayment.class));
  43. // Guice 3.0
  44. install(new FactoryModuleBuilder().implement(Payment.class, RealPayment.class).build(PaymentFactory.class));

最佳实践

  • 最小化可变性:尽可能注入的是不可变对象;
  • 只注入直接依赖:不用注入一个实例来获取真正需要的实例,增加复杂性且不易测试;
  • 避免循环依赖
  • 避免静态状态:静态状态和可测试性就是天敌;
  • 采用@Nullable:Guice默认情况下禁止注入null对象;
  • 模块的处理必须要快并且无副作用
  • 在Providers绑定中当心IO问题:因为Provider不检查异常、不支持超时、不支持重试;
  • 不用在模块中处理分支逻辑
  • 尽可能不要暴露构造器

END

原文地址:https://www.cnblogs.com/jpfss/p/12096764.html

时间: 2024-11-05 18:47:45

Google 开源的这个库,性能快到让程序员飞起来!的相关文章

IT观察】网络通信、图片显示、数据库操作……Android程序员如何利用开源框架

每个Android 程序员都不是Android应用开发之路上孤军奋战的一个人,GitHub上浩如烟海的开源框架或类库就是前人为我们发明的轮子,有的轮子能提高软件性能,而有的轮子似乎是以牺牲性能为代价换取编程速度.擅长利用轮子的程序员已经遥遥领先,不擅长利用轮子的程序员总是嫌前人发明的轮子不够圆,自己造个方轮子上路后才发现落后了. 作者:玖哥来源:51CTO|2017-10-19 16:06 移动端 收藏 分享 [51CTO.com原创稿件]每个Android 程序员都不是Android应用开发之

自己总结的 iOS ,Mac 开源项目以及库,知识点------持续更新

自己在 git  上看到一个非常好的总结的东西,但是呢, fork  了几次,就是 fork  不到我的 git 上,干脆复制进去,但是,也是认真去每一个每一个去认真看了,并且也是补充了一些,感觉非常棒,所以好东西要分享,为啥用 CN 博客,有个好处,可以随时修改,可以持续更新,不用每次都要再发表,感觉这样棒棒的 我们 自己总结的iOS.mac开源项目及库,持续更新.... github排名 https://github.com/trending,github搜索:https://github.

iOS、mac开源项目及库汇总

UI 下拉刷新 EGOTableViewPullRefresh – 最早的下拉刷新控件. SVPullToRefresh – 下拉刷新控件. MJRefresh – 仅需一行代码就可以为UITableView或者CollectionView加上下拉刷新或者上拉刷新功能.可以自定义上下拉刷新的文字说明.具体使用看“使用方法”. (国人写) XHRefreshControl – XHRefreshControl 是一款高扩展性.低耦合度的下拉刷新.上提加载更多的组件.(国人写) CBStoreHou

Android开源项目及库搜集

TimLiu-Android 自己总结的Android开源项目及库. github排名 https://github.com/trending,github搜索:https://github.com/search 目录 UI 卫星菜单 节选器 下拉刷新 模糊效果 HUD与Toast 进度条 UI其它 动画 网络相关 响应式编程 地图 数据库 图像浏览及处理 视频音频处理 测试及调试 动态更新热更新 消息推送 完整项目 插件 出名框架 其他 好的文章 收集android上开源的酷炫的交互动画和视觉

iOS开发者必备:自己总结的iOS、mac开源项目及库

UI 下拉刷新 EGOTableViewPullRefresh - 最早的下拉刷新控件. SVPullToRefresh - 下拉刷新控件. MJRefresh - 仅需一行代码就可以为UITableView或者CollectionView加上下拉刷新或者上拉刷新功能.可以自定义上下拉刷新的文字说明.具体使用看“使用方法”. (国人写) XHRefreshControl - XHRefreshControl 是一款高扩展性.低耦合度的下拉刷新.上提加载更多的组件.(国人写) CBStoreHou

59.Android开源项目及库 (转)

转载 : https://github.com/Tim9Liu9/TimLiu-Android?hmsr=toutiao.io&utm_medium=toutiao.io&utm_source=toutiao.io#%E5%8D%AB%E6%98%9F%E8%8F%9C%E5%8D%95 目录 UI UI 卫星菜单 节选器 下拉刷新 模糊效果 HUD与Toast 进度条 UI其它 动画 网络相关 响应式编程 地图 数据库 图像浏览及处理 视频音频处理 测试及调试 动态更新热更新 消息推送

Google官方 详解 Android 性能优化【史诗巨著之内存篇】

尊重博主原创,如需转载,请附上本文链接http://blog.csdn.net/chivalrousman/article/details/51553114#t16 为什么关注性能 对于一款APP,用户首先关注的是 app的性能,而不是APP本身的属性功能,用户不关心你是否是搞社交,是否搞电商,是否是一款强大的美图滤镜app,用户首先关注的是 性能--性能不好,用户会直接卸载,在应用市场给一个恶狠狠得差评,小则影响产品口碑,大则影响公司的品牌和声誉,作为程序员,app的性能更应该作为我们关注的一

值得推荐的C/C++开源框架和库

值得推荐的C/C++开源框架和库 转自:http://www.cnblogs.com/lidabo/p/5514155.html - 1. Webbench Webbench是一个在Linux下使用的非常简单的网站压测工具.它使用fork()模拟多个客户端同时访问我们设定的URL,测试网站在压力下工作的性能,最多可以模拟3万个并发连接去测试网站的负载能力.Webbench使用C语言编写, 代码实在太简洁,源码加起来不到600行. 下载链接:http://home.tiscali.cz/~cz21

【转】值得学习的C语言开源项目和库

- 1. Webbench Webbench是一个在linux下使用的非常简单的网站压测工具.它使用fork()模拟多个客户端同时访问我们设定的URL,测试网站在压力下工作的性能,最多可以模拟3万个并发连接去测试网站的负载能力.Webbench使用C语言编写, 代码实在太简洁,源码加起来不到600行. 下载链接:http://home.tiscali.cz/~cz210552/webbench.html - 2. Tinyhttpd tinyhttpd是一个超轻量型Http Server,使用C