今天被自己之前遗留的问题坑了一把,还是写篇日志记录一下吧。
之前某个Jar里写了一个接口,然后该接口有几个不同的实现类,然后项目中使用了Guice来管理依赖并进行注入,因此对待这个情况就采用Guice的BindingAnnotations的方式来进行注入,但是在运行时Guice会抛出无法正确注入,找到实现类的错误提示,很是让人恼火。经过仔细观察Log发现问题就出在这个接口上。
这个接口和Guice的例子唯一不同的地方就在于其是一个泛型接口,我们来举例说明这个问题:
public interface IFoo<T> {
public T foo();
}
可以看到上面的泛型参数决定了foo函数的返回值,其实现类如下:
public static class FooA implements IFoo<Integer> {
@Override
public Integer foo() {
return 1;
}
}
public static class FooB implements IFoo<String> {
@Override
public String foo() {
return "hello";
}
}
然后在创建一个Module类来注册这些实现类:
public static class FooModule extends AbstractModule {
@Override
protected void configure() {
bind(IFoo.class).annotatedWith(Names.named("A")).to(FooA.class);
bind(IFoo.class).annotatedWith(Names.named("B")).to(FooB.class);
}
}
再写一个用来使用这些接口的测试类:
@Singleton
public static class TestFoo {
@Named("A")
@Inject
private IFoo<Integer> fooA;
@Named("B")
@Inject()
private IFoo<String> fooB;
public void t() {
Object a = fooA.foo();
Object b = fooB.foo();
System.out.println("A:" + a);
System.out.println("B:" + b);
}
}
public static void main(String[] args) {
Injector injector = Guice.createInjector(new FooModule());
TestFoo f = injector.getInstance(TestFoo.class);
f.t();
}
当运行时我们会发现Guice会抛出如下运行时错误:
Exception in thread "main" com.google.inject.ConfigurationException: Guice configuration errors:
1) No implementation for com.aceclarks.papaya.storage.test.TestGuiceNamedBind$IFoo<java.lang.Integer> annotated with @com.google.inject.name.Named(value=A) was bound.
while locating com.aceclarks.papaya.storage.test.TestGuiceNamedBind$IFoo<java.lang.Integer> annotated with @com.google.inject.name.Named(value=A)
for field at com.aceclarks.papaya.storage.test.TestGuiceNamedBind$TestFoo.fooA(TestGuiceNamedBind.java:57)
while locating com.aceclarks.papaya.storage.test.TestGuiceNamedBind$TestFoo
2) No implementation for com.aceclarks.papaya.storage.test.TestGuiceNamedBind$IFoo<java.lang.String> annotated with @com.google.inject.name.Named(value=B) was bound.
while locating com.aceclarks.papaya.storage.test.TestGuiceNamedBind$IFoo<java.lang.String> annotated with @com.google.inject.name.Named(value=B)
for field at com.aceclarks.papaya.storage.test.TestGuiceNamedBind$TestFoo.fooB(TestGuiceNamedBind.java:57)
while locating com.aceclarks.papaya.storage.test.TestGuiceNamedBind$TestFoo
2 errors
at com.google.inject.internal.InjectorImpl.getProvider(InjectorImpl.java:1004)
at com.google.inject.internal.InjectorImpl.getProvider(InjectorImpl.java:961)
at com.google.inject.internal.InjectorImpl.getInstance(InjectorImpl.java:1013)
at com.aceclarks.papaya.storage.test.TestGuiceNamedBind.main(TestGuiceNamedBind.java:78)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:606)
at com.intellij.rt.execution.application.AppMain.main(AppMain.java:120)
其实这里发生的原因就是在于我们在FooModule的configure里注册的是绑定到接口IFoo的关联关系,但是Guice在运行时在TestFoo里的两个IFoo接口成员的声明是带有泛型参数的,Guice在注入时,没有找到对应的实现类,因此就会抛出如上的运行时异常,这个问题解决方法有两个:
-
第一个方法是,在TestFoo的IFoo接口成员声明时,去掉泛型参数,这样Guice在运行时查找匹配的实现时就会按照IFoo的实现类去查找,而不是按照IFoo<Integer> 或 IFoo<String> 这样的带有泛型参数的接口去查找
-
第二个方法是,在FooModule中configure函数里,在bind的时候使用TypeLiteral类来在注册的时候保持泛型参数信息进行注册,这样就可以保证注册的信息里是以带有泛型参数的接口注册的,代码例子如下:
public static class FooModule extends AbstractModule { @Override protected void configure() { bind(new TypeLiteral<IFoo<Integer>>() {}).annotatedWith(Names.named("A")).to(FooA.class); bind(new TypeLiteral<IFoo<String>>() {}).annotatedWith(Names.named("B")).to(FooB.class); } }