关注微信公众号,获取更多技术干货!
Google Guice(读音:juice)是从Google AdWords项目开源出来的一款轻量级的依赖注入(DI,Dependency Injection)框架。说到依赖注入框架,首先想到的应该就是Spring,也自然会有好事者,将Google Guice与Spring进行比较。其实比较的意义不大,因为Spring在国内基本上已经一统天下了,开始新写个业务模块,一般第一件事就是拷贝Spring的配置文件,拷完之后拷DAO层,拷完DAO层拷Service,最后是Controller层。外加Spring Boot、Spring Cloud全家桶,开发的确爽歪歪。
要开始比较了,难解难分呢,不知道如何做出选择呢!
好吧,好吧,还是比较一下吧!要不,也对不起封面图片。
- Guice支持Java&Annotation的配置方式,Spring支持XML&Annotation的配置方式。都会用到注解,Java和XML没有本质的区别,都是一门变成语言,不要歧视XML。╮(╯_╰)╭,毕竟PHP才是最好的编程语言。Spring的注入更加自动一些,有很多默认注入方式。
第一局Spring胜!
- 单说依赖注入方面的学习难度,Guice还是挺烦,主要是因为资料少,宣传不够啊,可见,技术都是炒作出来的。除了官网,其他的中文资料都是入门级别,包括这篇文字。哎,其实Guice哪里特么有官网,就是GitHub。Guice,你确定你是Google开源的吗?你特么是不是弃婴啊!
第二局Spring胜!
在与其他组件的集成方面,Spring 全家桶直接吊打Guice!我只见过一个程序员用Guice写业务代码,最后他屎了,被接手他代码的人砍屎了。
第三局,Spring胜!
果然,Spring吊打了Guice几十年!!!
“那你还写这篇文章干啥?浪费劳资时间,关了,退了!”
“劳资也不想写啊!是农药不好喝?是吃鸡不好玩?还是直播小姐姐不好看?要不是Druid、Elastic Search、Play2里面都用了Guice作为他们的DI框架,劳资是万万不会看Google Guice的!“
Guice一时爽,接手死全家!Don’t bibi,show me code!
好吧,举个例子!现在有一个Service接口,其实现类是DefaultService,在另一个类(这里是DogEgg)需要一个Service实例,这个时候就需要注入了!代码如下:
public static void main(String[] args) {
Injector injector = Guice.createInjector(new BindModule());
DogEgg dogEgg = injector.getInstance(DogEgg.class);
System.out.println(dogEgg.service);
}
interface Service {
}
public static class DefaultService implements Service {
}
public static class DogEgg {
@Inject // 告诉Guice,这里要注入东西,具体的注入规则从Module里找吧
public Service service;
}
public static class BindModule implements Module {
@Override
public void configure(Binder binder) {
// 在注入的时候,遇到Service接口类型,全部注入成DefaultService实例
binder.bind(Service.class).to(DefaultService.class);
}
}
是不是很溜?是不是很Easy呢?
”谁家接口只有一个实现?全TM是套路!!!“
”Hello World嘛,必须是套路啊!吓得小编黑边都没截掉!!!“
Guice怎么解这个情况呢?自定义一个注解啊,代码如下:
@BindingAnnotation
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD, ElementType.METHOD, ElementType.TYPE})
public @interface DefaultService {
}
public static void main(String[] args) {
Injector injector = Guice.createInjector(new BindModule());
DogEgg dogEgg = injector.getInstance(DogEgg.class);
System.out.println(dogEgg.service);
System.out.println(dogEgg.service2);
}
interface Service {}
public static class DefaultService implements Service {}
public static class DefaultService2 implements Service {}
public static class DogEgg {
@Inject
@DefaultAnnotation // 使用自定义注解标识该字段需要DefaultService实例
public Service service;
@Inject // 没有自定义注解,那就注入DefaultService2实例
public Service service2;
}
public static class BindModule implements Module {
@Override
public void configure(Binder binder) {
binder.bind(Service.class).annotatedWith(DefaultAnnotation.class)
.to(DefaultService.class).in(Scopes.SINGLETON);
binder.bind(Service.class).to(DefaultService2.class);
}
}
除了直接使用@DefaultAnnotation这个注解,还可以有更作死的注入方式:
// 这里的Key告诉Guice两件事,一个是需要一个Service接口的实例,另一个是需要DefaultAnnotation注解
// 你说Guice知道了会实例化哪个实现?!傻子都知道!
Service service3 = injector.getInstance(Key.get(Service.class,DefaultAnnotation.class));
System.out.println(service3);
// 这里的Key告诉Guice一件事,需要一个Service接口的实例,Guice自然实例化DefaultService2实例
Service service4 = injector.getInstance(Key.get(Service.class));
System.out.println(service4);
一个接口多实现的问题虽然解了,估计读者一定很想给小编一些”点心“。
”要是一个接口有4个实现,我要定义4个注解?靠,溜了溜了。“
”大爷别走啊!来来来,高潮还没到呢!“
Guice可以通过“名字”进行注入,代码如下:
public static void main(String[] args) {
Injector injector = Guice.createInjector(new BindModule());
DogEgg dogEgg = injector.getInstance(DogEgg.class);
System.out.println(dogEgg.service);
System.out.println(dogEgg.service2);
System.out.println(dogEgg.service3);
}
interface Service {}
public static class DefaultService implements Service {}
public static class DefaultService2 implements Service {}
public static class DogEgg {
@Inject
@Named("service1") // 注意这里的@Named注解
public Service service;
@Inject
@Named("service2") // 注意这里的@Named注解
public Service service2;
@Inject
public Service service3; // 注意这里的没有@Named注解!!!
}
public static class BindModule implements Module {
@Override
public void configure(Binder binder) {
// 被@Named("service1")修饰的,给注入DefaultService实例
binder.bind(Service.class).annotatedWith(Names.named("service1"))
.to(DefaultService.class);
// 被@Named("service2")修饰的,给注入DefaultService2实例
binder.bind(Service.class).annotatedWith(Names.named("service2"))
.to(DefaultService2.class);
// 默认给注入DefaultService实例
binder.bind(Service.class).to(DefaultService.class);
}
}
上面这个栗子的输出瞅一眼:
DefaultService@fad74ee
DefaultService2@1a1d6a08
DefaultService@37d31475 // 和第一行的DefaultService实例不是一个?!
人家Spring可以配置单例还是原型,Guice也可以,在bind的时候指定一下就行,像上面那样不指定任何Scope的时候,每次注入时都会创建新实例!
public static class DogEgg {
@Inject
@Named("service1")
public Service service;
@Inject
@Named("service1")
public Service service5;
@Inject
@Named("service2")
public Service service2;
@Inject
public Service service3;
@Inject
public Service service4;
}
public static class BindModule implements Module {
@Override
public void configure(Binder binder) {
binder.bind(Service.class).annotatedWith(Names.named("service1"))
.to(DefaultService.class).in(Scopes.SINGLETON); // 这里指定了Scope
binder.bind(Service.class).annotatedWith(Names.named("service2"))
.to(DefaultService2.class);
binder.bind(Service.class).to(DefaultService.class).in(Scopes.SINGLETON); // 这里也指定了Scope
}
}
输出次序为service1、service5、service2、service3、service4如下:
DefaultService@ed9d034
DefaultService@ed9d034 // service1和service5是一个对象
DefaultService2@6121c9d6
DefaultService@87f383f
DefaultService@87f383f // service3和service4是一个对象,但是与service1和service2不相同
上面这波输出也告诉我们,Guice的单例是按照binding划分的。
如果要全局单例,可以使用@Singleton注解,代码如下:
@Singleton // 这个注解告诉Guice,该实现全局单例
public static class DefaultService implements Service {
}
public static class DefaultService2 implements Service {
}
public static class DogEgg {
@Inject
@Named("service1")
public Service service;
@Inject
@Named("service1")
public Service service5;
@Inject
@Named("service2")
public Service service2;
@Inject
public Service service3;
@Inject
public Service service4;
}
public static class BindModule implements Module {
@Override
public void configure(Binder binder) {
binder.bind(Service.class).annotatedWith(Names.named("service1"))
.to(DefaultService.class); // 这里没有再指定Scope
binder.bind(Service.class).annotatedWith(Names.named("service2"))
.to(DefaultService2.class);
binder.bind(Service.class).to(DefaultService.class); // 这里没有再指定Scope
}
}
这里的Singleton到底是懒汉模式还是饿汉模式呢?这个也是可以配置的,默认是懒汉模式,就是延迟加载!!!其实懒汉和饿汉模式在代码跑起来之后,没啥太大差别,毕竟该创建的都创建了。要是想详细理解的话,翻翻文档吧,这个不是特别重要。
除了Singleton之外,还有Scope,例如ServletScopes.REQUEST,也去看文档吧,这个也不是特别重要。
除了上面介绍的字段注入,Guice还可以通过构造注入:
public static void main(String[] args) {
Injector injector = Guice.createInjector(new BindModule());
DogEgg dogEgg = injector.getInstance(DogEgg.class);
System.out.println(dogEgg.service);
System.out.println(dogEgg.service5);
System.out.println(dogEgg.service2);
System.out.println(dogEgg.service3);
System.out.println(dogEgg.service4);
}
interface Service {}
@Singleton
public static class DefaultService implements Service {
private String name;
private TestArg testArg;
public DefaultService(String name, TestArg testArg) { // 构造函数
this.name = name;
this.testArg = testArg;
}
public DefaultService() {} // 无参构造函数
@Override
public String toString() { // 这里专门加了一个hashCode(),用于区分是不是一个对象
return hashCode() +" DefaultService{" +
"name='" + name + '\'' +
", testArg=" + testArg +
'}';
}
}
public static class DefaultService2 implements Service {}
public static class DogEgg {
@Inject
@Named("service1")
public Service service;
@Inject
@Named("service1")
public Service service5;
@Inject
@Named("service2")
public Service service2;
@Inject
public Service service3;
@Inject
public Service service4;
}
public static class TestArg {
}
public static class BindModule implements Module {
@Override
public void configure(Binder binder) {
// 碰到@Named("service1")注解修饰的注入,继续走无参构造函数
binder.bind(Service.class).annotatedWith(Names.named("service1")).to(DefaultService.class);
binder.bind(Service.class).annotatedWith(Names.named("service2"))
.to(DefaultService2.class);
// binder.bind(Service.class).to(DefaultService.class); 这与下面走构造函数的binding冲突,Guice不知道该走哪个构造方法
try {
binder.bind(Service.class) // 没有@Named("service1")注解修饰的注入,走构造方法
.toConstructor(DefaultService.class
.getConstructor(String.class, TestArg.class));
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
}
}
看一下输出,service1、5是同一个对象,都走了无参构造函数。service3、4为同一个对象,都走了构造方法。而且DefaultService上有@Singleton注解作妖。
926370398 DefaultService{name='null', testArg=null}
926370398 DefaultService{name='null', testArg=null}
DefaultService2@4671e53b
767010715 DefaultService{name=' ', testArg=TestArg@6950e31}
767010715 DefaultService{name=' ', testArg=TestArg@6950e31}
Guice入门的上篇就到这里了,下篇会介绍一个Provider之类的东西,这玩意类似于Spring的工厂。还可能介绍一个Apache Druid对Guice的使用。随缘介绍。。。。。。。。。。