1. 依赖注入
1.1 类依赖注入
所谓的绑定就是将一个接口绑定到具体的类中,这样客户端不用关心具体的实现,而只需要获取相应的接口完成其服务即可。
HelloWorld.java 1 public interface HelloWorld { 2 3 String sayHello(); 4 } 5
然后是具体的实现,HelloWorldImpl.java
1 public class HelloWorldImpl implements HelloWorld { 2 3 @Override 4 public String sayHello() { 5 return "Hello, world!"; 6 } 7 } 8
写一个测试例子看看,HelleWorldTest.java
1 public class HelleWorldTest { 2 3 @Test 4 public void testSayHello() { 5 Injector inj= Guice.createInjector(new Module() { 6 @Override 7 public void configure(Binder binder) { 8 binder.bind(HelloWorld.class).to(HelloWorldImpl.class); 9 } 10 }); 11 HelloWorld hw = inj.getInstance(HelloWorld.class); 12 Assert.assertEquals(hw.sayHello(), "Hello, world!"); 13 } 14 } 15
这个例子非常简单,通俗的将就是将一个HelloWorldImpl的实例与HelloWorld关联起来,当想Guice获取一个HelloWorld实例的时候,Guice就返回一个HelloWorldImpl的实例,然后我们就可以调用HelloWorld服务的方法了。
问题(1)HelloWorld是单例的么?测试下。
1 HelloWorld hw = inj.getInstance(HelloWorld.class); 2 Assert.assertEquals(hw.sayHello(), "Hello, world!"); 3 HelloWorld hw2 = inj.getInstance(HelloWorld.class); 4 System.out.println(hw.hashCode()+"->"+hw2.hashCode()); 5 Assert.assertEquals(hw.hashCode(), hw2.hashCode());
解答(1)测试结果告诉我们,HelloWorld不是单例的,每次都会返回一个新的实例。
问题(2)HelloWorld的实例是HelloWorldImpl么?可以强制转型么?
HelloWorld hw = inj.getInstance(HelloWorld.class); System.out.println(hw.getClass().getName());
解答(2),结果输出cn.imxylz.study.guice.helloworld.HelloWorldImpl,看来确实只是返回了一个正常的实例,并没有做过多的转换和代理。
问题(3),如果绑定多个实现到同一个接口上会出现什么情况?
1 public class HelloWorldImplAgain implements HelloWorld { 2 @Override 3 public String sayHello() { 4 return "Hello world again."; 5 } 6 } binder.bind(HelloWorld.class).to(HelloWorldImpl.class); binder.bind(HelloWorld.class).to(HelloWorldImplAgain.class);
解答(3),很不幸,Guice目前看起来不允许多个实例绑定到同一个接口上了。
com.google.inject.CreationException: Guice creation errors: 1) A binding to cn.imxylz.study.guice.helloworld.HelloWorld was already configured at cn.imxylz.study.guice.helloworld.HelleWorldTest$1.configure(HelleWorldTest.java:28). at cn.imxylz.study.guice.helloworld.HelleWorldTest$1.configure(HelleWorldTest.java:29)
问题(4),可以绑定一个实现类到实现类么?
1 Injector inj= Guice.createInjector(new Module() { 2 @Override 3 public void configure(Binder binder) { 4 binder.bind(HelloWorldImpl.class).to(HelloWorldImpl.class); 5 } 6 }); 7 HelloWorld hw = inj.getInstance(HelloWorldImpl.class); 8 System.out.println(hw.sayHello());
非常不幸,不可以自己绑定到自己。
1) Binding points to itself. at cn.imxylz.study.guice.helloworld.HelleWorldTest$1.configure(HelleWorldTest.java:28)
我们来看看bind的语法。
<T> AnnotatedBindingBuilder<T> bind(Class<T> type); ScopedBindingBuilder to(Class<? extends T> implementation);
也就是说只能绑定一个类的子类到其本身。改造下,改用子类替代。
1 public class HelloWorldSubImpl extends HelloWorldImpl { 2 3 @Override 4 public String sayHello() { 5 return "@HelloWorldSubImpl"; 6 } 7 } 8 1 Injector inj= Guice.createInjector(new Module() { 2 @Override 3 public void configure(Binder binder) { 4 binder.bind(HelloWorldImpl.class).to(HelloWorldSubImpl.class); 5 } 6 }); 7 HelloWorldImpl hw = inj.getInstance(HelloWorldImpl.class); 8 System.out.println(hw.sayHello());
太好了,支持子类绑定,这样即使我们将一个实现类发布出去了(尽管不推荐这么做),我们在后期仍然有办法替换实现类。
使用bind有一个好处,由于JAVA 5以上的泛型在编译器就确定了,所以可以帮我们检测出绑定错误的问题,而这个在配置文件中是无法检测出来的。
这样看起来Module像是一个Map,根据一个Key获取其Value,非常简单的逻辑。
问题(5),可以绑定到我们自己构造出来的实例么?
解答(5)当然可以!看下面的例子。
1 Injector inj= Guice.createInjector(new Module() { 2 @Override 3 public void configure(Binder binder) { 4 binder.bind(HelloWorld.class).toInstance(new HelloWorldImpl()); 5 } 6 }); 7 HelloWorld hw = inj.getInstance(HelloWorld.class); 8 System.out.println(hw.sayHello());
问题(6),我不想自己提供逻辑来构造一个对象可以么?
解答(6),可以Guice提供了一个方式(Provider<T>),允许自己提供构造对象的方式。
1 Injector inj= Guice.createInjector(new Module() { 2 @Override 3 public void configure(Binder binder) { 4 binder.bind(HelloWorld.class).toProvider(new Provider<HelloWorld>() { 5 @Override 6 public HelloWorld get() { 7 return new HelloWorldImpl(); 8 } 9 }); 10 } 11 }); 12 HelloWorld hw = inj.getInstance(HelloWorld.class); 13 System.out.println(hw.sayHello());
问题(7),实现类可以不经过绑定就获取么?比如我想获取HelloWorldImpl的实例而不通过Module绑定么?
解答(7),可以,实际上Guice能够自动寻找实现类。
Injector inj= Guice.createInjector(); HelloWorld hw = inj.getInstance(HelloWorldImpl.class); System.out.println(hw.sayHello());
问题(8),可以使用注解方式完成注入么?不想手动关联实现类。
解答(8),好,Guice提供了注解的方式完成关联。我们需要在接口上指明此接口被哪个实现类关联了。
1 @ImplementedBy(HelloWorldImpl.class) 2 public interface HelloWorld { 3 4 String sayHello(); 5 } 6 Injector inj= Guice.createInjector(); HelloWorld hw = inj.getInstance(HelloWorld.class); System.out.println(hw.sayHello());
事实上对于一个已经被注解的接口我们仍然可以使用Module来关联,这样获取的实例将是Module关联的实例,而不是@ImplementedBy注解关联的实例。这样仍然遵循一个原则,手动优于自动。
问题(9)再回头看问题(1)怎么绑定一个单例?
1 Injector inj = Guice.createInjector(new Module() { 2 3 @Override 4 public void configure(Binder binder) { 5 binder.bind(HelloWorld.class).to(HelloWorldImplAgain.class).in(Scopes.SINGLETON); 6 } 7 }); 8 HelloWorld hw = inj.getInstance(HelloWorld.class); 9 HelloWorld hw2 = inj.getInstance(HelloWorld.class); 10 System.out.println(hw.hashCode() + "->" + hw2.hashCode()); 11
可以看到现在获取的实例已经是单例的,不再每次请求生成一个新的实例。事实上Guice提供两种Scope,com.google.inject.Scopes.SINGLETON和com.google.inject.Scopes.NO_SCOPE,所谓没有scope即是每次生成一个新的实例。
对于自动注入就非常简单了,只需要在实现类加一个Singleton注解即可。
1 @Singleton 2 public class HelloWorldImpl implements HelloWorld { 3 4 @Override 5 public String sayHello() { 6 return "Hello, world!"; 7 } 8 } 9