【转】guice 入门教程:依赖注入之高级功能

1.3 更多话题

1.3.1 接口多实现

如果一个接口有多个实现,这样通过@Inject和Module都难以直接实现,但是这种现象确实是存在的,于是Guice提供了其它注入方式来解决此问题。比如下面的自定义注解。

public interface Service {
        void execute();
}
public class HomeService implements Service {
    @Override
    public void execute() {
        System.out.println("home.imxylz.cn");
    }
}
public class WwwService implements Service {
    @Override
    public void execute() {
        System.out.println("www.imxylz.cn");
    }
}
@Retention(RetentionPolicy.RUNTIME)
@Target({FIELD,PARAMETER})
@BindingAnnotation
public @interface Home {
}
@Retention(RetentionPolicy.RUNTIME)
@Target({FIELD,PARAMETER})
@BindingAnnotation
public @interface Www {
}

上面的代码描述的是一个Service服务,有WwwService和HomeService两个实现,同时有Www和Home两个注解(如果对注解各个参数不明白的需要单独去学习JAVA 5注解)。好了下面请出我们的主角。

/**
 * $Id: MultiInterfaceServiceDemo.java 82 2009-12-24 06:55:16Z xylz $
 * xylz study project (www.imxylz.cn)
 */
package cn.imxylz.study.guice.inject.more;
import com.google.inject.Binder;
import com.google.inject.Guice;
import com.google.inject.Inject;
import com.google.inject.Module;
/** a demo with multi interfaces
 * @author xylz (www.imxylz.cn)
 * @version $Rev: 82 $
 */
public class MultiInterfaceServiceDemo {
    @Inject
    @Www
    private Service wwwService;
    @Inject
    @Home
    private Service homeService;
    public static void main(String[] args) {
        MultiInterfaceServiceDemo misd = Guice.createInjector(new Module() {
            @Override
            public void configure(Binder binder) {
                binder.bind(Service.class).annotatedWith(Www.class).to(WwwService.class);
                binder.bind(Service.class).annotatedWith(Home.class).to(HomeService.class);
            }
        }).getInstance(MultiInterfaceServiceDemo.class);
        misd.homeService.execute();
        misd.wwwService.execute();
    }
}

此类的结构是注入两个Service服务,其中wwwService是注入@Www注解关联的WwwService服务,而homeService是注入@Home注解关联的HomeService服务。

同样关于此结构我们要问几个问题。

问题(1)静态注入多个服务怎么写?

其实,参照教程02,我们可以使用下面的例子。

public class StaticMultiInterfaceServiceDemo {
    @Inject
    @Www
    private static Service wwwService;
    @Inject
    @Home
    private static Service homeService;
    public static void main(String[] args) {
       Guice.createInjector(new Module() {
            @Override
            public void configure(Binder binder) {
                binder.bind(Service.class).annotatedWith(Www.class).to(WwwService.class);
                binder.bind(Service.class).annotatedWith(Home.class).to(HomeService.class);
                binder.requestStaticInjection(StaticMultiInterfaceServiceDemo.class);
            }
        });
        StaticMultiInterfaceServiceDemo.homeService.execute();
        StaticMultiInterfaceServiceDemo.wwwService.execute();
    }
}

问题(2):如果不小心一个属性绑定了多个接口怎么办?

非常不幸,你将得到类似一下的错误,也就是说不可以绑定多个服务。


1) cn.imxylz.study.guice.inject.more.StaticMultiInterfaceServiceDemo.wwwService has more than one annotation annotated with @BindingAnnotation: cn.imxylz.study.guice.inject.more.Www and cn.imxylz.study.guice.inject.more.Home

  at cn.imxylz.study.guice.inject.more.StaticMultiInterfaceServiceDemo.wwwService(StaticMultiInterfaceServiceDemo.java:17)

问题(3):我太懒了不想写注解来区分多个服务,怎么办?

程序员都是懒惰的,于是Google帮我们提供了一个Names的模板来生成注解。看下面的例子。

public class NoAnnotationMultiInterfaceServiceDemo {
    @Inject
    @Named("Www")
    private static Service wwwService;
    @Inject
    @Named("Home")
    private static Service homeService;
    public static void main(String[] args) {
       Guice.createInjector(new Module() {
            @Override
            public void configure(Binder binder) {
                binder.bind(Service.class).annotatedWith(Names.named("Www")).to(WwwService.class);
                binder.bind(Service.class).annotatedWith(Names.named("Home")).to(HomeService.class);
                binder.requestStaticInjection(NoAnnotationMultiInterfaceServiceDemo.class);
            }
        });
        NoAnnotationMultiInterfaceServiceDemo.homeService.execute();
        NoAnnotationMultiInterfaceServiceDemo.wwwService.execute();
    }
}

上面的例子中我们使用Named来标注我们的服务应该使用什么样的注解,当然前提是我们已经将相应的服务与注解关联起来了。

1.3.2 Provider注入

在教程第一篇中我们提到了可以通过Provider注入一个服务,这里详细说说这种模式。

首先我们需要构造一个Provider<T>出来。

public class WwwServiceProvider implements Provider<Service> {
    @Override
    public Service get() {
        return new WwwService();
    }
}

上面的Provider的意思很简单,每次新建一个新的WwwService对象出来。

注入的过程看下面的代码。

public class ProviderServiceDemo {
    @Inject
    private Service service;
    public static void main(String[] args) {
        Injector inj=  Guice.createInjector(new Module() {
            @Override
            public void configure(Binder binder) {
                binder.bind(Service.class).toProvider(WwwServiceProvider.class);
            }
        });
        ProviderServiceDemo psd = inj.getInstance(ProviderServiceDemo.class);
        psd.service.execute();
    }
}

很显然如果这东西和线程绑定就非常好了,比如我们可以使用ThreadLocal来做线程的对象交换。

当然如果想自动注入(不使用Module手动关联)服务的话,可以使用@ProviderBy注解。

@ProvidedBy(WwwServiceProvider.class)
public interface Service {
    void execute();
}

这样我们就不必使用Module将Provider绑定到Service上,获取服务就很简单了。

ProviderServiceDemo psd = Guice.createInjector().getInstance(ProviderServiceDemo.class);

psd.service.execute();

除了上述两种方式我们还可以注入Provider,而不是注入服务,比如下面的例子例子中,属性不再是Service,而是一个Provider<Service>。

public class ProviderServiceDemo {
    @Inject
    private Provider<Service> provider;
    public static void main(String[] args) {
        ProviderServiceDemo psd = Guice.createInjector(new Module() {
            @Override
            public void configure(Binder binder) {
                binder.bind(Service.class).toProvider(WwwServiceProvider.class);
            }
        }).getInstance(ProviderServiceDemo.class);
        psd.provider.get().execute();
    }
}

    当然了,由于我们WwwServiceProvider每次都是构造一个新的服务出来,因此在类ProviderServiceDemo中的provider每次获取的服务也是不一样的。

1.3.3 绑定常量

看看下面的例子,演示了一个绑定整数值到实例的例子。

public class ConstantInjectDemo {
    @Inject
    @Named("v")
    private int v;
    public static void main(String[] args) {
        ConstantInjectDemo cid = Guice.createInjector(new Module() {
            @Override
            public void configure(Binder binder) {
                binder.bindConstant().annotatedWith(Names.named("v")).to(12);
            }
        }).getInstance(ConstantInjectDemo.class);
        System.out.println(cid.v);
    }
}

    当然,既然可以使用Named,也就可以使用自己写注解了。但是看起来好像没有多大作用。除了上述写法,也可以用下面的方式实现。

binder.bind(int.class).annotatedWith(Names.named("v")).toInstance(12);

除了可以绑定int外,在ConstantBindingBuilder类中还可以绑定其它的基本类型。

com.google.inject.binder.ConstantBindingBuilder.to(String)

com.google.inject.binder.ConstantBindingBuilder.to(long)

com.google.inject.binder.ConstantBindingBuilder.to(boolean)

com.google.inject.binder.ConstantBindingBuilder.to(double)

com.google.inject.binder.ConstantBindingBuilder.to(float)

com.google.inject.binder.ConstantBindingBuilder.to(short)

com.google.inject.binder.ConstantBindingBuilder.to(char) 

1.3.4 绑定Properties

    除了可以绑定基本类型外,还可以绑定一个Properties到Guice中,当然了,由于Properties本质上时一个Map<String,String>,因此Guice也允许绑定一个Map<String,String>。

@Inject
@Named("web")
private String web;
public static void main(String[] args) {
    ConstantInjectDemo cid = Guice.createInjector(new Module() {
        @Override
        public void configure(Binder binder) {
            Properties properties= new Properties();
            properties.setProperty("web", "www.imxylz.cn");
            Names.bindProperties(binder, properties);
        }
    }).getInstance(ConstantInjectDemo.class);
    System.out.println(cid.web);
}

本章节继续讨论依赖注入的其他话题,包括作用域(scope,这里有一个与线程绑定的作用域例子)、立即初始化(Eagerly Loading Bindings)、运行阶段(Stage)、选项注入(Optional Injection)等等。 

1.3.5 Scope(作用域)

在1.1章节中我们初步了解了对象的单例模式,在Guice中提供了一些常见的作用域,比如对于单例模式有下面两个作用域。

    com.google.inject.Scopes.SINGLETON

    com.google.inject.Scopes.NO_SCOPE

在使用上,可以使用Module的bind来实现,看下面的例子。

public class ScopeDemo {
    public static void main(String[] args) {
        Service service = Guice.createInjector(new Module() {
            @Override
            public void configure(Binder binder) {
                binder.bind(Service.class).to(WwwService.class).in(Scopes.SINGLETON);
            }
        }).getInstance(Service.class);
        service.execute();
    }
}

    当然单例模式还可以似乎用@Singleton注解。在com.google.inject.binder.ScopedBindingBuilder.in(Scope)方法中,一个Scope除了可以使上面的SINGLETION和NO_SCOPE外,还可以是自己定义的Scope。下面的例子演示了一个与线程绑定的Scope例子。

/**
 * $Id: ThreadScopeDemo.java 90 2009-12-25 08:12:21Z xylz $
 * xylz study project (www.imxylz.cn)
 */
package cn.imxylz.study.guice.inject.more;
import com.google.inject.Binder;
import com.google.inject.Guice;
import com.google.inject.Injector;
import com.google.inject.Key;
import com.google.inject.Module;
import com.google.inject.Provider;
import com.google.inject.Scope;
/** a demo with thread-scope
 * @author xylz (www.imxylz.cn)
 * @version $Rev: 90 $
 */
public class ThreadScopeDemo {
    static class ThreadServiceScope implements Scope {
        static ThreadLocal<Object> threadLocal = new ThreadLocal<Object>();
        @Override
        public <T> Provider<T> scope(final Key<T> key, final Provider<T> unscoped) {
            return new Provider<T>() {
                @Override
                public T get() {
                    T instance = (T) threadLocal.get();
                    if (instance == null) {
                        instance = unscoped.get();
                        threadLocal.set(instance);
                    }
                    return instance;
                }
            };
        }
        @Override
        public String toString() {
            return "Scopes.ThreadServiceScope";
        }
    }
    public static void main(String[] args) {
        final Injector inj=Guice.createInjector(new Module() {
            @Override
            public void configure(Binder binder) {
                binder.bind(Service.class).to(WwwService.class).in(new ThreadServiceScope());
            }
        });
        for(int i=0;i<3;i++) {
            new Thread("Thread-"+i) {
                public void run() {
                    for(int m=0;m<3;m++) {
                        System.out.println(String.format("%s-%d:%d",//
                                getName()//
                                ,m//
                                ,inj.getInstance(Service.class).hashCode()));
                        try {
                            Thread.sleep(50L);
                        } catch (Exception e) {
                        }
                    }
                }
            }.start();
        }
    }
}

    注意,这里用到了《Google Guice 入门教程03 - 依赖注入》的中的两个类Service和WwwService。在本例中ThreadServiceScope类是一个与线程绑定的作用域(利用ThreadLocal特性),当当前线程中没有构造一个对象的时候先构造一个出来,然后放入线程上下文中,以后每次都从线程中获取对象。第50行是将WwwService服务以ThreadServiceScope的作用域绑定到Service服务上。第57-60行输出当前对象的hashCode,如果此类是同一对象的话就应该输出相同的hashCode。为了看到效果,我们使用3个线程,每个线程输出三次来看结果。

Thread-0-0:18303751

Thread-1-0:23473608

Thread-2-0:21480956

Thread-1-1:23473608

Thread-0-1:18303751

Thread-2-1:21480956

Thread-1-2:23473608

Thread-2-2:21480956

Thread-0-2:18303751

我们看到对于同一个线程(比如说Thread-0)的三次都输出了相同的对象(hashCode为18303751),而与线程2和线程3的hashCode不同。

(特别说明:如果两个线程输出了同一个hashCode不必惊慌,那是因为可能前一个线程生成的对象的地址空间被GC释放了,结果下一个线程使用了上一个线程的相同空间,所以这里使用Thread.sleep来降低这种可能性)

事实上在guice-servlet-2.0.jar中有与request和session绑定的scope。

com.google.inject.servlet.ServletScopes.REQUEST

com.google.inject.servlet.ServletScopes.SESSION

1.3.6 Eagerly Loading Bindings (立即初始化)

除了可以绑定scope外,对象默认在第一次调用时被创建,也即所谓的延时加载,Guice也允许对象在注入到Guice容器中时就被创建出来(显然这是针对单例模式才有效)。

public class EagerSingletonDemo {
    public EagerSingletonDemo() {
        System.out.println(" constuctor:"+System.nanoTime());
    }
    void doit() {
        System.out.println("       doit:"+System.nanoTime());
    }
    public static void main(String[] args) throws Exception{
        Injector inj = Guice.createInjector(new Module() {
            @Override
            public void configure(Binder binder) {
                binder.bind(EagerSingletonDemo.class).asEagerSingleton();
            }
        });
        System.out.println("before call:"+System.nanoTime());
        Thread.sleep(100L);
        inj.getInstance(EagerSingletonDemo.class).doit();
    }
}

结果输出如下:

 constuctor:26996967388652

before call:26996967713635

       doit:26997069993702

可以看到我们的对象在调用getInstance之前就已经被构造出来了。

1.3.7 Stages (运行阶段)

    Guice还有一个特效,可以指定Guice运行模式来控制Guice的加载速度。在com.google.inject.Stage枚举中提供了TOOL,DEVELOPMENT,PRODUCTION三种模式。

    TOOL描述的是带有IDE等插件的运行模式;DEVELOPMENT是指在开发阶段只加载自己需要的功能(对于非立即初始化单例对象采用延后加载),这样来降低加载不需要功能的时间;而PRODUCTION模式是指完全加载所有功能(对于单例对象采用立即加载方式),这样可以更早的发现问题,免得等需要某些功能的时候才发现问题(要知道我们某些功能可能需要特定的条件才能触发)。

    其实只有比较多的单例对象,并且单例对象构造比较耗时的情况下才能有用。大部分情况下这点性能可能都忽略不计了。默认情况下Guice采用DEVELOPMENT模式。

1.3.8 Optional Injection (选项注入 )

选项注入描述的是如果不能从Guice容器中注入一个对象,那么可以使用一个默认的对象。看下面的例子。

public class OptionalInjectionDemo {
    @Inject(optional=true)
    Service service = new WwwService();
    public static void main(String[] args) {
        Guice.createInjector(new Module() {
            public void configure(Binder binder) {
                //binder.bind(Service.class).to(HomeService.class);
            }
        }).getInstance(OptionalInjectionDemo.class).service.execute();
    }
}

上述例子中第2行描述的是选项注入,如果不能从Guice容器中获取一个Service服务那么就使用默认的WwwService,否则就是用获取的服务。如果将第7行注释去掉我们就可以看到实际上调用的是HomeService服务了。到此为止,Guice依赖注入的基本教程就学习完了,下面的章节我们进入经典的AOP教程学习。