0
0

使用依赖注入框架Google Guice替代new和工厂类

kafka0102 发表于 2010年06月26日 04:07 | Hits: 2714
Tag: framework | java | di | guice | ioc

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),Guice提供了TypeLiteral来创建绑定,比如示例代码binder.bind(new TypeLiteral>() {}).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绑定所有类型即可。这样,完全可以定义一个工厂,其只提供一个方法:T newInstance(Classc);,然后实现一个依赖于Guice的工厂类。如果将来需要迁移Guice到Spring或者手工创建,只需要换个实现工厂类即可。

原文链接: http://www.kafka0102.com/2010/06/193.html

0     0

我要给这篇文章打分:

可以不填写评论, 而只是打分. 如果发表评论, 你可以给的分值是-5到+5, 否则, 你只能评-1, +1两种分数. 你的评论可能需要审核.

评价列表(0)