依赖注入框架Google Guice 对象图
GettingStarted · google/guice Wiki https://github.com/google/guice/wiki/GettingStarted
How to start doing dependency injection with Guice.
Getting Started
With dependency injection, objects accept dependencies in their constructors. To construct an object, you first build its dependencies. But to build each dependency, you need its dependencies, and so on. So when you build an object, you really need to build an object graph.
Building object graphs by hand is labour intensive, error prone, and makes testing difficult. Instead, Guice can build the object graph for you. But first, Guice needs to be configured to build the graph exactly as you want it.
To illustrate, we'll start the BillingService
class that accepts its dependent interfaces CreditCardProcessor
and TransactionLog
in its constructor. To make it explicit that the BillingService
constructor is invoked by Guice, we add the @Inject
annotation:
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) {
...
}
}
We want to build a BillingService
using PaypalCreditCardProcessor
and DatabaseTransactionLog
. Guice uses bindings to map types to their implementations. A module is a collection of bindings specified using fluent, English-like method calls:
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);
}
}
The modules are the building blocks of an injector, which is Guice's object-graph builder. First we create the injector, and then we can use that to build the BillingService
:
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);
...
}
By building the billingService, we've constructed a small object graph using Guice. The graph contains the billing service and its dependent credit card processor and transaction log.
Motivation · google/guice Wiki https://github.com/google/guice/wiki/Motivation
Motivation
Wiring everything together is a tedious part of application development. There are several approaches to connect data, service, and presentation classes to one another. To contrast these approaches, we'll write the billing code for a pizza ordering website:
public interface BillingService { /**
* Attempts to charge the order to the credit card. Both successful and
* failed transactions will be recorded.
*
* @return a receipt of the transaction. If the charge was successful, the
* receipt will be successful. Otherwise, the receipt will contain a
* decline note describing why the charge failed.
*/
Receipt chargeOrder(PizzaOrder order, CreditCard creditCard);
}
Along with the implementation, we'll write unit tests for our code. In the tests we need a FakeCreditCardProcessor
to avoid charging a real credit card!
Direct constructor calls
Here's what the code looks like when we just new
up the credit card processor and transaction logger:
public class RealBillingService implements BillingService {
public Receipt chargeOrder(PizzaOrder order, CreditCard creditCard) {
CreditCardProcessor processor = new PaypalCreditCardProcessor();
TransactionLog transactionLog = new DatabaseTransactionLog(); try {
ChargeResult result = processor.charge(creditCard, order.getAmount());
transactionLog.logChargeResult(result); return result.wasSuccessful()
? Receipt.forSuccessfulCharge(order.getAmount())
: Receipt.forDeclinedCharge(result.getDeclineMessage());
} catch (UnreachableException e) {
transactionLog.logConnectException(e);
return Receipt.forSystemFailure(e.getMessage());
}
}
}
This code poses problems for modularity and testability. The direct, compile-time dependency on the real credit card processor means that testing the code will charge a credit card! It's also awkward to test what happens when the charge is declined or when the service is unavailable.
Factories
A factory class decouples the client and implementing class. A simple factory uses static methods to get and set mock implementations for interfaces. A factory is implemented with some boilerplate code:
public class CreditCardProcessorFactory { private static CreditCardProcessor instance; public static void setInstance(CreditCardProcessor processor) {
instance = processor;
} public static CreditCardProcessor getInstance() {
if (instance == null) {
return new SquareCreditCardProcessor();
} return instance;
}
}
In our client code, we just replace the new
calls with factory lookups:
public class RealBillingService implements BillingService {
public Receipt chargeOrder(PizzaOrder order, CreditCard creditCard) {
CreditCardProcessor processor = CreditCardProcessorFactory.getInstance();
TransactionLog transactionLog = TransactionLogFactory.getInstance(); try {
ChargeResult result = processor.charge(creditCard, order.getAmount());
transactionLog.logChargeResult(result); return result.wasSuccessful()
? Receipt.forSuccessfulCharge(order.getAmount())
: Receipt.forDeclinedCharge(result.getDeclineMessage());
} catch (UnreachableException e) {
transactionLog.logConnectException(e);
return Receipt.forSystemFailure(e.getMessage());
}
}
}
The factory makes it possible to write a proper unit test:
public class RealBillingServiceTest extends TestCase { private final PizzaOrder order = new PizzaOrder(100);
private final CreditCard creditCard = new CreditCard("1234", 11, 2010); private final InMemoryTransactionLog transactionLog = new InMemoryTransactionLog();
private final FakeCreditCardProcessor processor = new FakeCreditCardProcessor(); @Override public void setUp() {
TransactionLogFactory.setInstance(transactionLog);
CreditCardProcessorFactory.setInstance(processor);
} @Override public void tearDown() {
TransactionLogFactory.setInstance(null);
CreditCardProcessorFactory.setInstance(null);
} public void testSuccessfulCharge() {
RealBillingService billingService = new RealBillingService();
Receipt receipt = billingService.chargeOrder(order, creditCard); assertTrue(receipt.hasSuccessfulCharge());
assertEquals(100, receipt.getAmountOfCharge());
assertEquals(creditCard, processor.getCardOfOnlyCharge());
assertEquals(100, processor.getAmountOfOnlyCharge());
assertTrue(transactionLog.wasSuccessLogged());
}
}
This code is clumsy. A global variable holds the mock implementation, so we need to be careful about setting it up and tearing it down. Should the tearDown
fail, the global variable continues to point at our test instance. This could cause problems for other tests. It also prevents us from running multiple tests in parallel.
But the biggest problem is that the dependencies are hidden in the code. If we add a dependency on a CreditCardFraudTracker
, we have to re-run the tests to find out which ones will break. Should we forget to initialize a factory for a production service, we don't find out until a charge is attempted. As the application grows, babysitting factories becomes a growing drain on productivity.
Quality problems will be caught by QA or acceptance tests. That may be sufficient, but we can certainly do better.
Dependency Injection
Like the factory, dependency injection is just a design pattern. The core principle is to separate behaviour from dependency resolution. In our example, the RealBillingService
is not responsible for looking up the TransactionLog
and CreditCardProcessor
. Instead, they're passed in as constructor parameters:
public class RealBillingService implements BillingService {
private final CreditCardProcessor processor;
private final TransactionLog transactionLog; public RealBillingService(CreditCardProcessor processor,
TransactionLog transactionLog) {
this.processor = processor;
this.transactionLog = transactionLog;
} public Receipt chargeOrder(PizzaOrder order, CreditCard creditCard) {
try {
ChargeResult result = processor.charge(creditCard, order.getAmount());
transactionLog.logChargeResult(result); return result.wasSuccessful()
? Receipt.forSuccessfulCharge(order.getAmount())
: Receipt.forDeclinedCharge(result.getDeclineMessage());
} catch (UnreachableException e) {
transactionLog.logConnectException(e);
return Receipt.forSystemFailure(e.getMessage());
}
}
}
We don't need any factories, and we can simplify the testcase by removing the setUp
and tearDown
boilerplate:
public class RealBillingServiceTest extends TestCase { private final PizzaOrder order = new PizzaOrder(100);
private final CreditCard creditCard = new CreditCard("1234", 11, 2010); private final InMemoryTransactionLog transactionLog = new InMemoryTransactionLog();
private final FakeCreditCardProcessor processor = new FakeCreditCardProcessor(); public void testSuccessfulCharge() {
RealBillingService billingService
= new RealBillingService(processor, transactionLog);
Receipt receipt = billingService.chargeOrder(order, creditCard); assertTrue(receipt.hasSuccessfulCharge());
assertEquals(100, receipt.getAmountOfCharge());
assertEquals(creditCard, processor.getCardOfOnlyCharge());
assertEquals(100, processor.getAmountOfOnlyCharge());
assertTrue(transactionLog.wasSuccessLogged());
}
}
Now, whenever we add or remove dependencies, the compiler will remind us what tests need to be fixed. The dependency is exposed in the API signature.
Unfortunately, now the clients of BillingService
need to lookup its dependencies. We can fix some of these by applying the pattern again! Classes that depend on it can accept a BillingService
in their constructor. For top-level classes, it's useful to have a framework. Otherwise you'll need to construct dependencies recursively when you need to use a service:
public static void main(String[] args) {
CreditCardProcessor processor = new PaypalCreditCardProcessor();
TransactionLog transactionLog = new DatabaseTransactionLog();
BillingService billingService
= new RealBillingService(processor, transactionLog);
...
}
Dependency Injection with Guice
The dependency injection pattern leads to code that's modular and testable, and Guice makes it easy to write. To use Guice in our billing example, we first need to tell it how to map our interfaces to their implementations. This configuration is done in a Guice module, which is any Java class that implements the Module
interface:
public class BillingModule extends AbstractModule {
@Override
protected void configure() {
bind(TransactionLog.class).to(DatabaseTransactionLog.class);
bind(CreditCardProcessor.class).to(PaypalCreditCardProcessor.class);
bind(BillingService.class).to(RealBillingService.class);
}
}
We add @Inject
to RealBillingService
's constructor, which directs Guice to use it. Guice will inspect the annotated constructor, and lookup values for each parameter.
public class RealBillingService implements BillingService {
private final CreditCardProcessor processor;
private final TransactionLog transactionLog; @Inject
public RealBillingService(CreditCardProcessor processor,
TransactionLog transactionLog) {
this.processor = processor;
this.transactionLog = transactionLog;
} public Receipt chargeOrder(PizzaOrder order, CreditCard creditCard) {
try {
ChargeResult result = processor.charge(creditCard, order.getAmount());
transactionLog.logChargeResult(result); return result.wasSuccessful()
? Receipt.forSuccessfulCharge(order.getAmount())
: Receipt.forDeclinedCharge(result.getDeclineMessage());
} catch (UnreachableException e) {
transactionLog.logConnectException(e);
return Receipt.forSystemFailure(e.getMessage());
}
}
}
Finally, we can put it all together. The Injector
can be used to get an instance of any of the bound classes.
public static void main(String[] args) {
Injector injector = Guice.createInjector(new BillingModule());
BillingService billingService = injector.getInstance(BillingService.class);
...
}
Getting started explains how this all works.
总结:
创建对象,即创建对象的依赖图
Guice中文文档 - 简书 https://www.jianshu.com/p/97e354423612
原文链接:https://github.com/google/guice/wiki/Motivation
总结:
依赖注入:行为和解析依赖分开
Motivation
将所有东西整合到一起是开发中一项乏味的工作,现在有多种方法将不同的数据、服务和展现层互相联系起来。为了对比这些不同的方法, 我们为一个披萨在线订购网站编写了计费代码
public interface BillingService {
/**
* 尝试通过信用卡支付,无论是否成功都将被记录下来
* @return 支付成功时返回成功信息,否则,返回失败原因
*
*/
Receipt chargeOrder(PizzaOrder order, CreditCard creditCard);
}
在实现这个接口之前,我们要写一个单元测试。在测试中我们需要一个FakeCreditCardProcessor,因为我们不能真的从一张信用卡中刷钱=。=
Direct constructor calls
下面展示了如果我们只new一个信用卡processor和transaction logger我们的代码会是神马样
public class RealBillingService implements BillingService {
public Receipt chargeOrder(PizzaOrder order, CreditCard creditCard) {
CreditCardProcessor processor = new PaypalCreditCardProcessor();
TransactionLog transactionLog = new DatabaseTransactionLog();
try {
ChargeResult result = processor.charge(creditCard, order.getAmount());
transactionLog.logChargeResult(result);
return result.wasSuccessful()
? Receipt.forSuccessfulCharge(order.getAmount())
: Receipt.forDeclinedCharge(result.getDeclineMessage());
} catch (UnreachableException e) {
transactionLog.logConnectException(e);
return Receipt.forSystemFailure(e.getMessage());
}
}
}
如上代码耦合性很高,并且不易测试。单元测试里不可能对一张真实的信用卡进行操作。
Factories
一个工厂类解耦了客户端和它的实现类。一个简单的工厂使用静态方法来get和set一个mock实现类。
public class CreditCardProcessorFactory {
private static CreditCardProcessor instance;
public static void setInstance(CreditCardProcessor processor) {
instance = processor;
}
public static CreditCardProcessor getInstance() {
if (instance == null) {
return new SquareCreditCardProcessor();
}
return instance;
}
}
在我们的客户端代码中,我们只是在原先new的地方调用工厂方法。
public class RealBillingService implements BillingService {
public Receipt chargeOrder(PizzaOrder order, CreditCard creditCard) {
CreditCardProcessor processor = CreditCardProcessorFactory.getInstance();
TransactionLog transactionLog = TransactionLogFactory.getInstance();
try {
ChargeResult result = processor.charge(creditCard, order.getAmount());
transactionLog.logChargeResult(result);
return result.wasSuccessful()
? Receipt.forSuccessfulCharge(order.getAmount())
: Receipt.forDeclinedCharge(result.getDeclineMessage());
} catch (UnreachableException e) {
transactionLog.logConnectException(e);
return Receipt.forSystemFailure(e.getMessage());
}
}
}
有了工厂,我们就可以实现一个正确的UT
public class RealBillingServiceTest extends TestCase {
private final PizzaOrder order = new PizzaOrder(100);
private final CreditCard creditCard = new CreditCard("1234", 11, 2010);
private final InMemoryTransactionLog transactionLog = new InMemoryTransactionLog();
private final FakeCreditCardProcessor processor = new FakeCreditCardProcessor();
@Override public void setUp() {
TransactionLogFactory.setInstance(transactionLog);
CreditCardProcessorFactory.setInstance(processor);
}
@Override public void tearDown() {
TransactionLogFactory.setInstance(null);
CreditCardProcessorFactory.setInstance(null);
}
public void testSuccessfulCharge() {
RealBillingService billingService = new RealBillingService();
Receipt receipt = billingService.chargeOrder(order, creditCard);
assertTrue(receipt.hasSuccessfulCharge());
assertEquals(100, receipt.getAmountOfCharge());
assertEquals(creditCard, processor.getCardOfOnlyCharge());
assertEquals(100, processor.getAmountOfOnlyCharge());
assertTrue(transactionLog.wasSuccessLogged());
}
}
这看起来很笨拙。一个全局变量持有mock实现,所以在设置和销毁时我们需要很小心。如果tearDown失败了,这个全局变量将会继续指向我们的测试实例,这将会对其他的UT产生影响,我们也不能并行地运行多个测试用例。
但是,最大的问题在于,依赖被隐藏在代码里。如果我们要添加一个新的依赖CreditCardFraudTracker,我们必须重新运行UT来找到which ones will break(没看懂)
Dependency Injection
跟工厂一样,DI也只是一个设计模式,其核心原则是将行为和解析依赖分离开(separate behaviour from dependency resolution)。在我们的栗子里,RealBillingService不负责寻找TransactionLog和CreditCardProcessor,相反,它们作为构造函数的参数被传递给BillingService
public class RealBillingService implements BillingService {
private final CreditCardProcessor processor;
private final TransactionLog transactionLog;
public RealBillingService(CreditCardProcessor processor,
TransactionLog transactionLog) {
this.processor = processor;
this.transactionLog = transactionLog;
}
public Receipt chargeOrder(PizzaOrder order, CreditCard creditCard) {
try {
ChargeResult result = processor.charge(creditCard, order.getAmount());
transactionLog.logChargeResult(result);
return result.wasSuccessful()
? Receipt.forSuccessfulCharge(order.getAmount())
: Receipt.forDeclinedCharge(result.getDeclineMessage());
} catch (UnreachableException e) {
transactionLog.logConnectException(e);
return Receipt.forSystemFailure(e.getMessage());
}
}
}
我们不再需要任何工厂,而且我们可以扔掉setUp和tearDown方法来简化我们的测试用例。
public class RealBillingServiceTest extends TestCase {
private final PizzaOrder order = new PizzaOrder(100);
private final CreditCard creditCard = new CreditCard("1234", 11, 2010);
private final InMemoryTransactionLog transactionLog = new InMemoryTransactionLog();
private final FakeCreditCardProcessor processor = new FakeCreditCardProcessor();
public void testSuccessfulCharge() {
RealBillingService billingService
= new RealBillingService(processor, transactionLog);
Receipt receipt = billingService.chargeOrder(order, creditCard);
assertTrue(receipt.hasSuccessfulCharge());
assertEquals(100, receipt.getAmountOfCharge());
assertEquals(creditCard, processor.getCardOfOnlyCharge());
assertEquals(100, processor.getAmountOfOnlyCharge());
assertTrue(transactionLog.wasSuccessLogged());
}
}
现在,无论什么时候我们添加或删除依赖,编译器都会提醒我们哪些测试出错了。依赖关系暴露在API接口中。
不过令人不开心的是,现在BillingService需要去寻找它的依赖了。我们可以再次应用这个模式来解决这个问题。可以提供一个BillingService给依赖它的类作为构造器的参数。简而言之,提供一个框架给顶端的类总不是个坏事。
public static void main(String[] args) {
CreditCardProcessor processor = new PaypalCreditCardProcessor();
TransactionLog transactionLog = new DatabaseTransactionLog();
BillingService billingService
= new RealBillingService(processor, transactionLog);
...
}
Dependency Injection with Guice
下面就要介绍到我们的Guice了,DI模式使得代码更加易于测试和可维护,Guice使得代码更容易编写。为了在我们的栗子中使用Guice,首先我们需要建立接口及其实现的映射关系。这个可以在一个实现了Module接口的java类中进行配置:
public class BillingModule extends AbstractModule {
@Override
protected void configure() {
bind(TransactionLog.class).to(DatabaseTransactionLog.class);
bind(CreditCardProcessor.class).to(PaypalCreditCardProcessor.class);
bind(BillingService.class).to(RealBillingService.class);
}
}
我们在RealBillingService的构造函数上加了一个@Inject注解,这个会告诉Guice来使用它。Guice会检查被注解的构造函数,然后找到每个参数的值。
public class RealBillingService implements BillingService {
private final CreditCardProcessor processor;
private final TransactionLog transactionLog;
@Inject
public RealBillingService(CreditCardProcessor processor,
TransactionLog transactionLog) {
this.processor = processor;
this.transactionLog = transactionLog;
}
public Receipt chargeOrder(PizzaOrder order, CreditCard creditCard) {
try {
ChargeResult result = processor.charge(creditCard, order.getAmount());
transactionLog.logChargeResult(result);
return result.wasSuccessful()
? Receipt.forSuccessfulCharge(order.getAmount())
: Receipt.forDeclinedCharge(result.getDeclineMessage());
} catch (UnreachableException e) {
transactionLog.logConnectException(e);
return Receipt.forSystemFailure(e.getMessage());
}
}
}
最后,我们可以把它们放在一起了。
public static void main(String[] args) {
Injector injector = Guice.createInjector(new BillingModule());
BillingService billingService = injector.getInstance(BillingService.class);
...
}
下一章解释了这一切是怎么工作的。
依赖注入框架Google Guice 对象图的更多相关文章
- 史上最好用的依赖注入框架Google Guice【转】
Guice是Google开发的一个轻量级,基于Java5(主要运用泛型与注释特性)的依赖注入框架(IOC).Guice非常小而且快. (其他的依赖注入框架还有Dagger,Spring) Spring ...
- [Android]依赖注入框架google的dagger
分享一下Android依赖注入框架--Google升级版Dagger2框架 Google的Dagger2是对上一版squareup的Dagger改版,话不多说直接上项目代码. Dagger2源码 Da ...
- Android依赖注入:Google Guice on Android的使用及相关资源
本文转自:http://blog.csdn.net/sangming/article/details/8878104 RoboGuice 使用谷歌自己的Guice库,给Android带来了简单和易用的 ...
- 依赖注入及AOP简述(四)——“好莱坞原则”和依赖注入框架简介 .
3.2. “好莱坞原则” 看了前面关于依赖注入概念的描述,我们来提炼出依赖注入的核心思想.如果说传统的组件间耦合方式,例如new.工厂模式等,是一种由开发者主动去构建依赖对象的话,那么依赖注入模 ...
- 依赖注入框架之dagger2
主页: https://github.com/google/dagger 历史 * Dagger1是由Square公司受到Guice(https://github.com/google/guice)启 ...
- Spring.NET依赖注入框架学习--简介
Spring.NET依赖注入框架学习--Spring.NET简介 概述 Spring.NET是一个应用程序框架,其目的是协助开发人员创建企业级的.NET应用程序.它提供了很多方面的功能,比如依赖注入. ...
- Spring.NET依赖注入框架学习--入门
Spring.NET依赖注入框架学习--入门 在学些Spring.net框架之前,有必要先脑补一点知识,比如什么是依赖注入?IOC又是什么?控制反转又是什么意思?它们与Spring.net又有什么关系 ...
- Objection, 一个轻量级的Objective-C依赖注入框架
简介 项目主页:https://github.com/atomicobject/objection 实例下载: https://github.com/ios122/ios122 Objection 是 ...
- [Android]依赖注入框架squareup的dagger
分享一下Android依赖注入框架--Dagger使用 Dagger源码 Dagger1-Demo 希望能给大家的开发带来帮助.
随机推荐
- location 将多级目录下的文件转成一个文件
/ck-135-201-7142.html 指向/ck/135/201/7142.html [root@web01 www]# cat /app/server/nginx/conf/rewrite/d ...
- Nginx 使用中文URL,中文目录路径
Nginx 使用中文URL,中文目录路径 分类: linux2012-05-03 11:04 2672人阅读 评论(0) 收藏 举报 nginxurl服务器translationcentosserve ...
- Windows 只能安装32位虚拟机问题
查了一下相关原因,是因为cpu的虚拟化没有打开的原因 解决方法: 进去bios 里面, 进入 configuration , 将 Intel Virtual Technology 设为Enabled ...
- linux 短信收发
#include <termios.h>#include <stdio.h>#include <stdlib.h>#include <unistd.h> ...
- m4--宏处理器
m4 是 POSIX 标准中的一部分,所有版本的 UNIX 下都可用.虽然这种语言可以单独使用,但大多数人需要 m4 仅仅是因为 GNU autoconf 中的 “configure” 脚本依赖它.宏 ...
- docker Failed to get D-Bus connection 报错 docker run -e MYVAR1 --env MYVAR2=foo --env-file ./env.list ubuntu bash
docker Failed to get D-Bus connection 报错 原创憬薇2016-01-15 11:25:26评论(10)40278人阅读 在centos7的容器里面出现了一个B ...
- php 的rabbitmq 扩展模块amqp安装
php 的rabbitmq 扩展模块amqp安装 2017年10月08日 10:34:22 阅读数:240 使用PHP开发,要使用中间队列rabbitmq, 必须要安装PHP的扩展模块amqp, 服务 ...
- 【BZOJ】1699: [Usaco2007 Jan]Balanced Lineup排队(rmq/树状数组)
http://www.lydsy.com/JudgeOnline/problem.php?id=1699 我是用树状数组做的..rmq的st的话我就不敲了.. #include <cstdio& ...
- Linux心得记录
2014.4.8 linux环境下如何删除一个目录? rm -r linux本身提供删除目录命令——rmdir,但是如果你要删除的目录中含有子目录或者子文件,那么该命令会提示“删除失败:目录非空“也就 ...
- ChemDraw怎么绘制H-点或H-划
ChemDraw软件是一款全球领先的化学绘图工具,能够绘制各类化学结构图形和化学方程式,在基础化学.有机化学和分析化学等领域得到了广泛的应用.H-点和H-划是日常作图过程中使用频率较高的化学符号,必须 ...