Guice这高级货

Guice在大部分时间都是很方便的,简单易用。Guice和Spring等其他容器的最大区别是,Guice相信注入大部分都是根据类型的,而不是根据名字的。Guice在使用上的方便,很大部分都来自于按类型注入。

[b][size=large]Bind多个实现[/size][/b]

但是,如果对于一个类型,我有多个实现怎么办?最常见的问题是,有两个数据库。

bind(sqlMapClient.class).toInstance(createSqlMapClientForDB1());

如果有两个数据库的话,这binding能怎么写呢?

bind(sqlMapClient.class).toInstance(createSqlMapClientForDB1());
bind(sqlMapClient.class).toInstance(createSqlMapClientForDB2());

这样写话,Guice会报告sqlMapClient已经存在一个binding了。而且在使用的地方

public class Service1 {
@Inject
SqlMapClient sqlMapClient;
}

到底是注入了db1,还是db2?最直观的解决方案是给DB1和DB2不同的key做binding。有三个选择

[b]选择一:不同的interface[/b]

public class Db1SqlMapClient extends SqlMapClient {
private final SqlMapClient delegate;
// delegate all methods of sql map client
}
public class Service1 {
@Inject
Db1SqlMapClient sqlMapClient;
}


[b]选择二:Binding Annotation[/b]

@BindingAnnotation
@Retention(RetentionPolicy.RUNTIME
public @interface DB1 {
}

bind(SqlMapClient.class)
.annotatedWith(DB1.class)
.with(createSqlMapClientForDB1());

public class Service1 {
@Inject @DB1
SqlMapClient sqlMapClient;
}


[b]选择三:Named binding[/b]

bind(SqlMapClient.class)
.annotatedWith(Names.named("DB1"))
.with(createSqlMapClientForDB1());

public class Service1 {
@Inject @Named("DB1")
SqlMapClient sqlMapClient;
}


三种方案,没一种是完美的。第一种显然是非常繁琐的,而且使用的地方需要知道具体的类型。方案二要好很多,但是需要额外定义一个annotation,同时使用的时候需要知道自己需要的依赖是被什么annotation标记的。这就有一点serviceLocator.locateService(db1_connection)的味道了。第三种更加糟糕,使用的地方知道自己需要的依赖的名字,而且名字是一个字符串,不再是重构安全的了,也无法查找引用。

在Guice 2.0里,有了第四种方案。

[b]选择四:Child Injector[/b]

db1Injector = injector.createChildInjector(new AbstractModule() {
public void configure() {
bind(SqlMapClient.class).toInstance(createSqlMapClientForDB1());
}
});
db2Injector = injector.createChildInjector(new AbstractModule() {
public void configure() {
bind(SqlMapClient.class).toInstance(createSqlMapClientForDB2());
}
});


使用不同的数据库,需要用不同的injector做注入。但是这种方式的限制其实更大,不是所有的系统都可以自然地分成多个分区,每个分区用不同的容器的。
综上,Guice有四种方式支持bind多个实现,Guice推荐的方式是使用bindingAnnotation。

[b][size=large]Bind Set[/size][/b]

这看起来是一个很简单问题,不就是这么写吗?

bind(Set.class).toInstance(new HashSet());

但是,我需要Integer和String两个不同的Set如何?也简单

bind(new TypeLiteral<Set<String>>(){}).toInstance(new HashSet<String>(){{
add("Hello");
add("World");
}});

{{}}
是啥?呃,自己复习java语法去吧。
或者可以利用Guice2里增加的Types这个类

bind(Types.setOf(String.class)).toInstance(new HashSet<String>(){{
add("Hello");
add("World");
}});

看起来也挺简单的。但是如果这个Set的成员不是简单的一个String呢?比如我有一个接口叫OrderProcessor。

public interface OrderProcessor {
void processOrder(Order order);
}

我需要不同的OrderProcessor来对Order做不同的处理(发邮件,存数据库)。分别对应的有:

public class MailOrderProcessor implements OrderProcessor {
@Inject
EmailSender emailSender
// send mail
}


public class DbOrderProcessor implements OrderProcessor {
@Inject
SqlMapClient sqlMapClient;
// save order to database
}

这个时候,这个Set<OrderProcessor>怎么bind呢?我们不能这么做:

bind(Types.of(Set.class)).toInstance(new HashSet<OrderProcessor>(){{
add(new MailOrderProcessor());
add(new DbOrderProcessor());
}});

这么做的问题是new MailOrderProcessor()无法给它注入MailSender。而在Module内又无法用injector.getInstance(MailOrderProcessor.class)。对于这种情况,有四种选择:

[b]选择一:用injectMemebers手工注入依赖[/b]

@Inject
Injector injector;
for (OrderProcessor orderProcessor : orderProcessors) {
injector.injectMemebers(orderProcess);
}


[b]选择二:包装Set[/b]

public class OrderProcessors {
private final Set<OrderProcessor> processors = new HashSet<OrderProcessor>();
@Inject
public void setDbOrderProcessor(DbOrderProcessor processor) {
processors.add(processor);
}
@Inject
public void setMailOrderProcessor(MailOrderProcessor processor) {
processors.add(processor);
}
}


[b]选择三:getProvider[/b]

bind(new TypeLiteral<Set<Provider<OrderProcessor>>>(){}).toInstance(new HashSet<Provider<OrderProcessor>>(){{
add(getProvider(DbOrderProcessor.class);
add(getProvider(MailOrderProcessor.class);
}});

这里利用的特性是在AbstractModule内提供了一个方法叫getProvider。这样虽然在Module内无法调用injector.getInstance,但是可以获得instance的provider。这样,我们得到的是order processor的provider的set,而不直接拿到order processor的set。

[b]选择四:getProvider + ProvidedOrderProcessor[/b]
[code]
public class ProvidedOrderProcessor implements OrderProcessor {
private final Provider<OrderProcessor> provider;
public ProvidedOrderProcessor(Provider<OrderProcessor> provider) {
this.provider = provider;
}
public void processOrder(Order order) {
provider.get().processOrder(order);
}
}
[/code]
这样,就可以把上面的Set<Provider<OrderProcessor>>变成Set<OrderProcessor>了。
[code]
bind(Types.setOf(OrderProcessor.class)).toInstance(new HashSet<OrderProcessor>(){{
add(getLazyInstance(DbOrderProcessor.class));
add(getLazyInstance(MailOrderProcessor.class));
}});
OrderProcessor getLazyInstance(Class<? extends OrderProcessor> clazz) {
return new ProvidedOrderProcessor(getProvider(clazz));
}
[/code]

[b][size=large]多Module共同Bind一个Collection[/size][/b]

要是Set<OrderProcessor>是由多个Module共同添加的又该怎么做?只是用Guice的Core,一个Binding必须在一个Module内完成。但是依赖于Guice的扩展Multibindings,就可以做到多个Module共同Bind一个Set。

public class Module1 extends AbstractModule {
public void configure() {
Multibinder<OrderProcessor> multibinder
= Multibinder.newSetBinder(binder(), OrderProcessor.class);
multibinder.addBinding().to(MailOrderProcessor.class);
}
}
public class Module2 extends AbstractModule {
public void configure() {
Multibinder<OrderProcessor> multibinder
= Multibinder.newSetBinder(binder(), OrderProcessor.class);
multibinder.addBinding().to(DbOrderProcessor.class);
}
}

似乎问题解决了?而且Multibindings扩展还支持Map。

[b][size=large]限制[/size][/b]

但是List怎么办?至今仍然没有官方的支持来解决这个问题。另外一个问题是如何Bind一个Chain,也就是Decorator模式。

public class DecoratedOrderProcessor implements OrderProcessor {
private final OrderProcessor decorated;
public OrderProcessor(OrderProcessor decorated) {
this.decorated = decorated;
}
public void processOrder(Order order) {
try {
decorated.processOrder(order);
} finally {
// do something;
}
}
}

如果有多个Decorator,形成了一条互相嵌套的Chain的话,就比较复杂了。对于一个Module的情况下,可以利用前面所说的ProvidedOrderProcessor的方式。但是如果要过个Module个共同来构建这个Chain的话,就没有官方做法了。

[b][size=large]用可插拔的Module做为扩展点[/size][/b]

理想状态是应用程序有XModule,YModule,ZModule。当没有ZModule的时候,应用程序仍然可以工作。但是插上了ZModule之后,一些行为就可以被扩展。这样就可以通过添加不同的模块来达到扩充的目的。怎么做呢?Guice提供了以下能力可以支持这样的效果。

[b]方法一:@ImplementedBy, @ProvidedBy[/b]
[code]
@ImplementedBy(MailOrderProcessor.class)
public interface OrderProcessor {
}
[/code]
这样当所有的Module都没有指定OrderProcessor的实现的时候,就会默认使用MailOrderProcessor。如果bind(OrderProcessor.class).to(DbOrderProcessor.class),则会把使用DbOrderProcessor。这个特性非常方便,尤其是有一些Service只会在测试时被改变的时候。
[code]
@ImplementedBy
public interface CurrentTimeProvider {
DateTime getNow();
public static class DefaultImpl implements CurrentTimeProvider {
public DateTime getNow() {
return new DateTime();
}
}
}
[/code]
在所有的产品代码里,CurrentTimeProvider都会是DefaultImpl。但是在测试里,我们可以指定bind(CurrentTimeProvider.class).toInstance(new FixedTimeProvider(2008,5,12));这样时间就定格在那一刻了。

[b]方法二:@Inject(optional = true)[/b]

public class ProcessOrderService {
@Inject(Optional = true)
OrderProcessor processor = new DummyOrderProcessor();
}

这种方式适用于需要依赖的地方能够自己提供一个缺省实现的情况。如果标记processor为optional的,那么processor就变成了ProcessOrderService的一个扩展点。如果用户愿意,可以随时通过添加Module的方式,改变ProcessOrderService行为的目的。

[b]方法三:Multibindings[/b]
前面我们已经看到了,通过Multibinder,可以用多个Module共同Bind一个Set。利用这个特性,就可以实现添加Module,添加不同的实现的目的。也是一个非常的实现扩展点的方式。

[b]方法四:Override[/b]
这是Guice2新增的一个功能。使用很简单

Module finalModule = Modules
.override(new DefaultModule())
.with(new CustomizationModule());

这样,如果都是同样的key的话,DefaultModule里定义的binding就会被CustomizationModule给改写。这个特性非常暴力,把本来已经很复杂的binding解析过程变得更加的复杂了。要知道最终使用的binding是从哪个Module来的就更加困难了。一般来说不推荐使用。但是由于有了如此暴力的特性,使得多个Module共同Bind同一个List或者Decorator Chain变成可能了:

Key CUSTOMIZABLE_KEY = Key.get(OrderProcessor.class, new Before(MailOrderProcessor.class));
bind(Types.listOf(OrderProcessor.class)).toInstance(new ArrayList<OrderProcessor>(){{
add(new ProvidedOrderProcessor(getProvider(CUSTOMIZABLE_KEY));
add(getLazyInstance(MailOrderProcessor.class);
}});
bind(CUSTOMIZABLE_KEY).toInstance(new DummyOrderProcessor());

Before是一个Binding Annotation。在另外一个Module中只要重复bind CUSTOMIZABLE_KEY就可以了
[code]
bind(CUSTOMIZABLE_KEY).to(getLazyInstance(DbOrderProcessor.class));
[/code]
使用的时候用Modules.override把缺省实现(DummyOrderProcessor)替换就可以了。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值