guice入门教程:依赖注入之类依赖注入

这段时间事情比较多,身体有些疲惫,小站就没有怎么打理,趁着周末把很长时间以前自己在网络上收集的一些文章发上来做个备份,也可以供有需求的朋友参考。先发一些关于google guice相关的博文。记得刚刚毕业的时候,有幸参与公司的核心平台的研发,当时公司的那个平台采用了深度定制化的guice做为容器,刚刚毕业的我只知道些SSH相关的知识,并不知道有GUICE这样的神器存在,而当时平台采用GUICE深度定制的原因,开始也并不知道有GUICE的存在,了解 guice还是从guava这个神器开始的。由于当时研发时使用了很多guava中的工具包,我就看看google还有什么好玩的东西,然后发现了guice然后就知道了平台采用了深度定制的类guice的东西,然后就没有然后了。。。言归正传,本系列内容是我当时从http://www.blogjava.net/xylz/这个博客上整理的。 同时记得这个博客上还有关于java并发的一系列文章,当时这个博客对我的成长有很大的帮助,在此一并感谢。

所谓的绑定就是将一个接口绑定到具体的类中,这样客户端不用关心具体的实现,而只需要获取相应的接口完成其服务即可。

     public interface HelloWorld {
         String sayHello();
     }

然后是具体的实现,HelloWorldImpl.java

     public class HelloWorldImpl implements HelloWorld {
          @Override
          public String sayHello() {
              return "Hello, world!";
          }
      }

写一个测试例子看看:

 public class HelleWorldTest {
     @Test
     public void testSayHello() {
       Injector inj=  Guice.createInjector(new Module() {
             @Override
             public void configure(Binder binder) {
                 binder.bind(HelloWorld.class).to(HelloWorldImpl.class);
             }
         });
       HelloWorld hw = inj.getInstance(HelloWorld.class);
       Assert.assertEquals(hw.sayHello(), "Hello, world!");
     }
 }

这个例子非常简单,通俗的将就是将一个HelloWorldImpl的实例与HelloWorld关联起来,当想Guice获取一个HelloWorld实例的时候,Guice就返回一个HelloWorldImpl的实例,然后我们就可以调用HelloWorld服务的方法了。

问题(1HelloWorld是单例的么?测试下。

 HelloWorld hw = inj.getInstance(HelloWorld.class); 
 Assert.assertEquals(hw.sayHello(), "Hello, world!");
 HelloWorld hw2 = inj.getInstance(HelloWorld.class);
 System.out.println(hw.hashCode()+"->"+hw2.hashCode());
 Assert.assertEquals(hw.hashCode(), hw2.hashCode());

解答(1)测试结果告诉我们,HelloWorld不是单例的,每次都会返回一个新的实例。

问题(2HelloWorld的实例是HelloWorldImpl么?可以强制转型么?

HelloWorld hw = inj.getInstance(HelloWorld.class);
 System.out.println(hw.getClass().getName());

 解答(2),结果输出cn.imxylz.study.guice.helloworld.HelloWorldImpl,看来确实只是返回了一个正常的实例,并没有做过多的转换和代理。

问题(3),如果绑定多个实现到同一个接口上会出现什么情况?

 public class HelloWorldImplAgain implements HelloWorld {
      @Override
      public String sayHello() {
          return "Hello world again.";
      }
  }

解答(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),可以绑定一个实现类到实现类么?

 Injector inj=  Guice.createInjector(new Module() {
        @Override
        public void configure(Binder binder) {
            binder.bind(HelloWorldImpl.class).to(HelloWorldImpl.class);
        }
    });
  HelloWorld hw = inj.getInstance(HelloWorldImpl.class);
  System.out.println(hw.sayHello());


 非常不幸,不可以自己绑定到自己。

1) Binding points to itself. 
  at cn.imxylz.study.guice.helloworld.HelleWorldTest$1.configure(HelleWorldTest.java:28)

我们来看看bind的语法。

 AnnotatedBindingBuilder bind(Class type);‍‍‍
‍ ScopedBindingBuilder to(Class implementation);

也就是说只能绑定一个类的子类到其本身。改造下,改用子类替代。

     public class HelloWorldSubImpl extends HelloWorldImpl {
          @Override
          public String sayHello() {
              return "@HelloWorldSubImpl";
          }
      }
 Injector inj=  Guice.createInjector(new Module() {
              @Override
              public void configure(Binder binder) {
                  binder.bind(HelloWorldImpl.class).to(HelloWorldSubImpl.class);
              }
          });
        HelloWorldImpl hw = inj.getInstance(HelloWorldImpl.class);
        System.out.println(hw.sayHello());

太好了,支持子类绑定,这样即使我们将一个实现类发布出去了(尽管不推荐这么做),我们在后期仍然有办法替换实现类。

使用bind有一个好处,由于JAVA 5以上的泛型在编译器就确定了,所以可以帮我们检测出绑定错误的问题,而这个在配置文件中是无法检测出来的。

这样看起来Module像是一个Map,根据一个Key获取其Value,非常简单的逻辑。

问题(5),可以绑定到我们自己构造出来的实例么?

解答(5)当然可以!看下面的例子。

        Injector inj=  Guice.createInjector(new Module() {
              @Override
              public void configure(Binder binder) {
                  binder.bind(HelloWorld.class).toInstance(new HelloWorldImpl());
              }
          });
        HelloWorld hw = inj.getInstance(HelloWorld.class);
        System.out.println(hw.sayHello());

问题(6),我不想自己提供逻辑来构造一个对象可以么?

解答(6),可以Guice提供了一个方式(Provider),允许自己提供构造对象的方式。

Injector inj=  Guice.createInjector(new Module() {
        @Override
        public void configure(Binder binder) {
             binder.bind(HelloWorld.class).toProvider(new Provider() {
                @Override
                public HelloWorld get() {
                    return new HelloWorldImpl();
                }
            });
        }
    });
  HelloWorld hw = inj.getInstance(HelloWorld.class);
  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提供了注解的方式完成关联。我们需要在接口上指明此接口被哪个实现类关联了。

     @ImplementedBy(HelloWorldImpl.class)
      public interface HelloWorld {
          String sayHello();
      }

事实上对于一个已经被注解的接口我们仍然可以使用Module来关联,这样获取的实例将是Module关联的实例,而不是@ImplementedBy注解关联的实例。这样仍然遵循一个原则,手动优于自动。

问题(9)再回头看问题(1)怎么绑定一个单例?

      Injector inj = Guice.createInjector(new Module() {
           @Override
           public void configure(Binder binder) {
              binder.bind(HelloWorld.class).to(HelloWorldImplAgain.class).in(Scopes.SINGLETON);
          }
       });
      HelloWorld hw = inj.getInstance(HelloWorld.class);
      HelloWorld hw2 = inj.getInstance(HelloWorld.class);
      System.out.println(hw.hashCode() + "->" + hw2.hashCode());

可以看到现在获取的实例已经是单例的,不再每次请求生成一个新的实例。事实上Guice提供两种Scopecom.google.inject.Scopes.SINGLETONcom.google.inject.Scopes.NO_SCOPE,所谓没有scope即是每次生成一个新的实例。

对于自动注入就非常简单了,只需要在实现类加一个Singleton注解即可。

     @Singleton
      public class HelloWorldImpl implements HelloWorld {
          @Override
          public String sayHello() {
              return "Hello, world!";
          }
      }