第 3 章 一个接口多个实现

如果一个接口有多个实现类的情况下,单独的@Inject和Module都不容易区分这种情况。

3.1. 自定义注解

Service.java还像以前一样,这次我们有了两个实现BlueServiceImpl.java和GreeServiceImpl.java。

BlueServiceImpl.java

public class BlueServiceImpl implements Service{
    public void hello() {
        System.out.println("blue");
    }
}
        

GreenServiceImpl.java

public class GreenServiceImpl implements Service{
    public void hello() {
        System.out.println("green");
    }
}
        

现在的问题是如何决定什么地方使用BlueServiceImpl.java,什么地方使用GreenServiceImpl.java。@ImplementedService.java已经派不上用场了,它明显只能指定一个实现。

为了区分BlueServiceImpl.java和GreenServiceImpl.java这两种实现,我们定义两个绑定注解Blue.java和Green.java。

Blue.java

import java.lang.annotation.*;
import com.google.inject.*;

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD, ElementType.PARAMETER})
@BindingAnnotation
public @interface Blue {
}
        

Green.java

import java.lang.annotation.*;
import com.google.inject.*;

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD, ElementType.PARAMETER})
@BindingAnnotation
public @interface Green {
}
        

除了名称以外两者一模一样,现在我们看看这一层层的注解都是什么意思。

  • @Retention(RetentionPolicy.RUNTIME)

    runtime告诉我们这个注解可以在java的时候使用反射读取到。

  • @Target注解告诉我们Blue和Green可以用在field和parameter上面。

来看一下Main.java的内容。

import com.google.inject.*;

public class Main {

    @Inject
    @Blue
    private Service blueService;

    @Inject
    @Green
    private Service greenService;

    public static void main(String[] args) {
        Main main = Guice.createInjector(new Module() {
            public void configure(Binder binder) {
                binder.bind(Service.class).annotatedWith(Blue.class)
                                        .to(BlueServiceImpl.class);
                binder.bind(Service.class).annotatedWith(Green.class)
                                        .to(GreenServiceImpl.class);
            }
        }).getInstance(Main.class);

        main.blueService.hello();
        main.greenService.hello();
    }
}
        

我们还是要先看Module的部分,这里将BlueServiceImpl.class和GreenServiceImpl.class分别绑定到Service.class上。

与之前不同的是,这里增加了附加条件,annotatedWith(Blue.class)的意思是遇到@Blue就把BlueServiceImpl.class注入给Service。它对应的代码是@Inject @Blue private Service blueService;,这样经过guice注入,blueService最后会获得BlueServiceImpl.class类型的实例,最后执行main.blueService.hello()的时候,我们会看到控制台打印出来的“blue”,greenService字段也是相同效果,自己验证吧。

例子:03-01。

3.2. 不想自己写注解

重申一遍,懒惰是程序员最大的美德。自定义注解实在太麻烦了,有没有简便一点儿的方法?如果你像我一样懒得自己写新注解,就试用一下guice提供的Named注解吧。

修改后的Main.java内容如下。

import com.google.inject.*;
import com.google.inject.name.Named;
import static com.google.inject.name.Names.*;

public class Main {

    @Inject
    @Named("blue")
    private Service blueService;

    @Inject
    @Named("green")
    private Service greenService;

    public static void main(String[] args) {
        Main main = Guice.createInjector(new Module() {
            public void configure(Binder binder) {
                binder.bind(Service.class).annotatedWith(named("blue"))
                                        .to(BlueServiceImpl.class);
                binder.bind(Service.class).annotatedWith(named("green"))
                                        .to(GreenServiceImpl.class);
            }
        }).getInstance(Main.class);

        main.blueService.hello();
        main.greenService.hello();
    }
}
        

import static com.google.inject.name.Names.*;部分导入的静态方法,我们实际上只用到了named()。看看Module中的annotatedWith(named("blue")),它对应的是@Inject @Named("blue") private Service blueService;。

通过使用Names.named("blue")和@Named("blue"),我们实现了原来@Blue的功能,这里我们使用指定字符串的值将两者对应起来。以少写代码少犯错的原则来说,这种情况下还是用guice提供的@Named比较好哦。

例子:03-02。