使用依赖注入框架Google Guice替代new和工厂类
Posted in framework, java on 六月 25th, 2010 by kafka0102
1、使用依赖注入
在新系统中使用了Guice来统一了对象注入和创建方式,也不是刻意为之,而是由两个经典问题引起的:
1)对于对象的创建,可以使用new、静态工厂、抽象工厂等方式,使得对象创建方式不统一,而使用哪种方式最好也很难有定论,并且随着代码的变化,创建方式也可能会需要有更好的变化。
2)对于类成员的设置,最直接的是在类内部直接创建,但这样硬编码会影响灵活的测试性,解决的方法是通过外部注入成员,好处自不必说,但在一个复杂的系统中,类之间的关联及层次关系,使得一个上层对象的创建往往需要依赖多个下层的类,使得对象创建代码变的冗余而复杂。
解决上面两个问题,我就想到了依赖注入(DI)框架(也被成为IOC容器,取控制反转之意),我需要选择一个得手的依赖注入框架来满足需求。
2、选择google guice
说到依赖注入框架,最大牌的就是Spring了。我很久之前也搞过一点Spring,使用上也很简单,但我最终选择了google的Guice。如果非要说个理由,一是Guice使用也很简单,二是Spring有点大并且它原生是需要个配置文件而我不想要配置文件(Spring2引入注释后,配置方面也可以很简化,应该也可以抛弃配置文件直接代码绑定,但这种非主流做法我也不曾验证过其有多麻烦),三是我想搞搞没搞过的。
Guice是google的”疯狂的Bob“开发的,开源后也有不错的活跃度。Bob当初没有选择Spring而是另造轮子,原因也主要是Spring配置的繁琐(代码和配置的分离会对测试维护等造成麻烦)、Spring相对的低性能(对大多数应用来说,Spring的性能是可以接受的说,尽管Guice性能确实比Spring好很多)。但轮子既然造出来并发布出来,自然会有人将其和Spring做对比。这方面我不想牵涉更多精力,其实两个选择都不错,了解它们各自的特点,就自己的应用需求,选择更合适的即可。就大多数应用系统(尤其是SSH配套系统),Spring还是不错的选择。再有,从发展前景来说,火爆成熟并有专业公司推动的Spring无疑会比形单影只的Guice会更有发展。如果真就同类DI框架间比较,倒可以将Guice和老牌的PicoContainer做些比较(PicoContainer和Spring在同一时期诞生,到现在也是不温不火,但还在持续更新中)。
3、使用google guice
3.1、基本使用
好吧,你不要嫌我罗嗦,我只能假定你对Guice是个新手并真的对它有些兴趣,所以亲手写出示例代码来说明Guice的使用及其特点。先上代码,看下很简单的示例代码:
public interface Foo { void foo(); } public interface Bar { void bar(); } public class FooImpl1 implements Foo { public void foo() { System.out.println("FooImpl1"); } } public class BarImpl1 implements Bar { public void bar() { System.out.println("BarImpl1"); } } public class BeanService1 { private Foo foo; private Bar bar; @Inject public BeanService1(Foo foo, Bar bar) { this.foo = foo; this.bar = bar; } @Override public String toString() { return "BeanService1 [bar=" + bar + ", foo=" + foo + "]"; } } public class BeanService1Module implements Module { public void configure(Binder binder) { binder.bind(Foo.class).to(FooImpl1.class); binder.bind(Bar.class).to(BarImpl1.class); } }
Injector injector = Guice.createInjector(new BeanService1Module()); BeanService1 bs = injector.getInstance(BeanService1.class); System.out.println(bs);
上面的示例没什么逻辑可言,BeanService1有两个成员变量,通过构造函数注入。这里使用Guice来创建BeanService1对象的工作有3个:
1)使用注释@Inject来标识BeanService1中需要被注入的成员,这里使用构造函数方式注入。
2)创建实现了Module接口的BeanService1Module,Module接口只有一个void configure(Binder binder),在这个函数中可以做绑定操作,比如将Foo接口和FooImpl1绑定起来,这使得Guice在运行时动态创建BeanService1对象时,当调用其被标识为@Inject的构造函数时,会查找参数列表成员是否有绑定的类型。
3)使用Guice.createInjector(Module… module)函数来创建Injector,Injector就相当于Factory,后续就可以调用其getInstance创建对象。
很简单吧,使用Guice不会比自己写工厂方法麻烦多少,下面再具体介绍其注入和绑定的其他方式。
3.2、注入和绑定的方式
除了上面提到的使用@Inject注入构造函数的方式,Guice还支持另两种常用的方式:
2)@Inject到类的成员变量,上面的例子就可修改成:
public class BeanService1 { @Inject private Foo foo; @Inject private Bar bar; public BeanService1(Foo foo, Bar bar) { this.foo = foo; this.bar = bar; } public BeanService1() { } }
当然,这次需要个默认构造函数的。
3)@Inject到类的方法,上面的例子就可修改成:
public class BeanService1 { private Foo foo; private Bar bar; public BeanService1() { } @Inject public void setFoo(Foo foo) { this.foo = foo; } @Inject public void setBar(Bar bar) { this.bar = bar; } }
注释完了,就需要一种绑定关系,以使Guice能确定该如何注入。上面示例中的绑定方式是最常见的,就是将一个接口绑定到一个实现类上。Guice还支持的绑定如下:
2)绑定自身。像BeanService1是个实现类而没有实现什么接口,它当然也可能被其他类注入,可以使用 binder.bind(BeanService1.class);绑定自身,尽管这样做没什么意义,对于注入的类参数,Guice识别出来后会直接创建。
3)绑定注释和实例。如果被注入的是如String、int这样的基本类型,需要做两件事情:一是对被注入的参数加上名称注释@Named,如下所示:
@Inject public void setName(@Named("beanService1Name") String name) { this.name = name; }
二是调用 bind(String.class).annotatedWith(Names.named(“beanService1Name”)).toInstance(“kafka0102”); 来将实例值和注释绑定上。对于@Named,它可作用于任何类型的变量,所以,如果某个被注入的参数需要指定为特定的对象,可以使用该方式。而对于基本类型,最好都指定@Named,以避免之间的冲突。
4)绑定Provider。有些时候对象的创建是不适合使用@Injectt注入的,比如被创建的对象的构造依赖于复杂的外部环境,再比如需要被构造的对象来自于第三方库。此时可以有两种解决方法,一是在AbstractModule的子类中提供@Provides方法,示例如下:
public class BeanService1Module implements Module { public void configure(Binder binder) { binder.bind(Bar.class).to(BarImpl1.class); } @Provides public Foo provideFoo() { //create... return null; } }
另一种方法是提供实现了接口
public interface Provider<T> { T get(); }
的实现类,并通过诸如binder.bind(Foo.class).toProvider(FooProvider.class);来绑定。
对于构造函数注入、成员变量注入、成员方法注入这三种经典的注入方式,选择上来说,构造函数注入最直接明了,推荐使用,有时构造函数中需要做些初始化操作,这时其他两种注入方式就不能胜任;对于成员变量注入,这有点反模式的味道,尤其是对于私有成员来说,不建议使用;使用成员方法注入也是很好的方式,在一些情况下(比如被注入的方法在父类中存在),使用方法注入会更合适。
3.3、其他
1、如果被注入的是范型类型的类(比如List>() {}).toInstance(new ArrayList
2、Guice对被被注入的参数要求不能为null,如果有可接受null的需求,可以对参数提供注释@Nullable解决。
3、对创建的实例,Guice默认都是new一个新的,可以对类指定如@Singleton、@SessionScoped、@RequestScoped这样的Scope来表示创建什么范围的实例(也可以通过binder.bind(Bar.class).to(BarImpl1.class).in(Singleton.class);这样来解决)。
4、Guice在2.0版本中提供了对AOP的支持,就像其他IOC容器做的那样。但是,我觉得AOP和IOC没什么联系,当初Spring做了两者,结果后来的IOC容器都多多少少提供了对AOP的支持。简单看了Guice的AOP介绍,只是实现了粗糙的拦截器模式。如果有对AOP的需要,选择Spring或者AspectJ才是王道。
5、目前Guice的最新版本是2.0,其网站http://code.google.com/p/google-guice有着较为详实的使用文档并且还有一些原理方面的介绍。并且,在其wiki给出的链接中竟然有关于Guice的图书,会有人买吗?
3.4、实践经验
对Guice的使用,除了散落在各类中的注释,和Guice有关联的代码就是实现Moudle和使用Injector。对于实现Moudle,我原以为不同Moudle中的bind具有独立的作用域,但实践的效果是各Module configure的Binder应该是全局一个的(原理上可能并不如此)。所以,只需要实现一个Module绑定所有类型即可。这样,完全可以定义一个工厂,其只提供一个方法:
=============================== 华丽的终止符 ================================
相关日志
Leave a Comment