support binding of generic interfaces from SpringModule

This commit is contained in:
Taylor Wicksell
2017-05-24 14:51:58 -07:00
committed by Dave Syer
parent 9dae979d39
commit 061aef36f2
9 changed files with 68 additions and 31 deletions

View File

@@ -16,12 +16,14 @@
package org.springframework.guice.module;
import java.lang.reflect.Type;
/**
* @author Dave Syer
*
*/
public interface BindingTypeMatcher {
boolean matches(String name, Class<?> type);
boolean matches(String name, Type type);
}

View File

@@ -18,12 +18,14 @@ package org.springframework.guice.module;
import java.io.IOException;
import java.lang.reflect.Modifier;
import java.lang.reflect.Type;
import java.util.HashSet;
import java.util.Set;
import java.util.regex.Pattern;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.core.ResolvableType;
import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.core.type.classreading.MetadataReaderFactory;
@@ -93,7 +95,7 @@ public class GuiceModuleMetadata implements BindingTypeMatcher {
}
@Override
public boolean matches(String name, Class<?> type) {
public boolean matches(String name, Type type) {
if (!matches(name) || !matches(type)) {
return false;
}
@@ -128,7 +130,7 @@ public class GuiceModuleMetadata implements BindingTypeMatcher {
return true;
}
private boolean matches(Class<?> type) {
private boolean matches(Type type) {
if (infrastructureTypes.contains(type)) {
return false;
}
@@ -140,7 +142,7 @@ public class GuiceModuleMetadata implements BindingTypeMatcher {
if (includeFilters != null) {
try {
MetadataReader reader = metadataReaderFactory.getMetadataReader(type
.getName());
.getTypeName());
for (TypeFilter filter : includeFilters) {
if (!filter.match(reader, metadataReaderFactory)) {
return false;
@@ -154,7 +156,7 @@ public class GuiceModuleMetadata implements BindingTypeMatcher {
if (excludeFilters != null) {
try {
MetadataReader reader = metadataReaderFactory.getMetadataReader(type
.getName());
.getTypeName());
for (TypeFilter filter : excludeFilters) {
if (filter.match(reader, metadataReaderFactory)) {
return false;
@@ -168,8 +170,8 @@ public class GuiceModuleMetadata implements BindingTypeMatcher {
return true;
}
private boolean visible(Class<?> type) {
Class<?> cls = type;
private boolean visible(Type type) {
Class<?> cls = ResolvableType.forType(type).resolve();
while (cls != null && cls != Object.class) {
if (!Modifier.isInterface(cls.getModifiers())
&& !Modifier.isPublic(cls.getModifiers())

View File

@@ -13,6 +13,7 @@
package org.springframework.guice.module;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
@@ -28,8 +29,10 @@ import org.springframework.util.ClassUtils;
import com.google.inject.Binder;
import com.google.inject.Module;
import com.google.inject.Key;
import com.google.inject.Provider;
import com.google.inject.ProvisionException;
import com.google.inject.TypeLiteral;
import com.google.inject.name.Names;
/**
@@ -42,7 +45,7 @@ public class SpringModule implements Module {
private BindingTypeMatcher matcher = new GuiceModuleMetadata();
private Map<Class<?>, Provider<?>> bound = new HashMap<Class<?>, Provider<?>>();
private Map<Type, Provider<?>> bound = new HashMap<Type, Provider<?>>();
public SpringModule(ApplicationContext context) {
this((DefaultListableBeanFactory) context.getAutowireCapableBeanFactory());
@@ -55,64 +58,65 @@ public class SpringModule implements Module {
}
}
@Override
@SuppressWarnings({ "rawtypes", "unchecked" })
public void configure(Binder binder) {
for (String name : this.beanFactory.getBeanDefinitionNames()) {
BeanDefinition definition = this.beanFactory.getBeanDefinition(name);
if (definition.isAutowireCandidate() && definition.getRole() == AbstractBeanDefinition.ROLE_APPLICATION) {
Class<?> type = this.beanFactory.getType(name);
@SuppressWarnings("unchecked")
final Class<Object> cls = (Class<Object>) type;
final String beanName = name;
Provider<Object> typeProvider = new BeanFactoryProvider(this.beanFactory, null, type);
Provider<Object> namedProvider = new BeanFactoryProvider(this.beanFactory, beanName, type);
if (!cls.isInterface() && !ClassUtils.isCglibProxyClass(cls)) {
bindConditionally(binder, name, cls, typeProvider, namedProvider);
if (!type.isInterface() && !ClassUtils.isCglibProxyClass(type)) {
bindConditionally(binder, name, type, typeProvider, namedProvider);
}
for (Class<?> iface : ClassUtils.getAllInterfacesForClass(cls)) {
@SuppressWarnings("unchecked")
Class<Object> unchecked = (Class<Object>) iface;
bindConditionally(binder, name, unchecked, typeProvider, namedProvider);
for (Class<?> iface : ClassUtils.getAllInterfacesForClass(type)) {
bindConditionally(binder, name, iface, typeProvider, namedProvider);
}
for (Type iface : type.getGenericInterfaces()) {
bindConditionally(binder, name, iface, typeProvider, namedProvider);
}
}
}
}
private void bindConditionally(Binder binder, String name, Class<Object> type, Provider<Object> typeProvider,
Provider<Object> namedProvider) {
@SuppressWarnings({ "rawtypes", "unchecked" })
private <T> void bindConditionally(Binder binder, String name, Type type, Provider typeProvider,
Provider namedProvider) {
if (!this.matcher.matches(name, type)) {
return;
}
if (type.getName().startsWith("com.google.inject")) {
if (type.getTypeName().startsWith("com.google.inject")) {
return;
}
if (this.bound.get(type) == null) {
// Only bind one provider for each type
binder.withSource("spring-guice").bind(type).toProvider(typeProvider);
this.bound.put(type, typeProvider);
binder.withSource("spring-guice").bind(Key.get(type)).toProvider(typeProvider);
this.bound.put(type, typeProvider);
}
// But allow binding to named beans
binder.withSource("spring-guice").bind(type).annotatedWith(Names.named(name)).toProvider(namedProvider);
binder.withSource("spring-guice").bind(TypeLiteral.get(type)).annotatedWith(Names.named(name)).toProvider(namedProvider);
}
private static class BeanFactoryProvider implements Provider<Object> {
private static class BeanFactoryProvider<T> implements Provider<T> {
private DefaultListableBeanFactory beanFactory;
private String name;
private Class<?> type;
private Class<T> type;
private Object result;
private T result;
public BeanFactoryProvider(DefaultListableBeanFactory beanFactory, String name, Class<?> type) {
public BeanFactoryProvider(DefaultListableBeanFactory beanFactory, String name, Class<T> type) {
this.beanFactory = beanFactory;
this.name = name;
this.type = type;
}
@Override
public Object get() {
public T get() {
if (this.result == null) {
String[] named = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(this.beanFactory, this.type);
@@ -151,7 +155,7 @@ public class SpringModule implements Module {
}
@Override
public boolean matches(String name, Class<?> type) {
public boolean matches(String name, Type type) {
for (BindingTypeMatcher matcher : this.matchers) {
if (matcher.matches(name, type)) {
return true;

View File

@@ -11,6 +11,7 @@ import org.junit.Test;
import com.google.inject.Injector;
import com.google.inject.Key;
import com.google.inject.TypeLiteral;
import com.google.inject.name.Names;
public abstract class AbstractCompleteWiringTests {
@@ -74,6 +75,12 @@ public abstract class AbstractCompleteWiringTests {
public void getNamedInjectedInstance() {
assertNotNull(this.injector.getInstance(Thing.class).thang);
}
@Test
public void getParameterizedType() {
Parameterized<String> instance = this.injector.getInstance(Key.get(new TypeLiteral<Parameterized<String>>() {}));
assertNotNull(instance);
}
public interface Service {
}
@@ -121,4 +128,7 @@ public abstract class AbstractCompleteWiringTests {
public static class Thang {
}
public static interface Parameterized<T> {
}
}

View File

@@ -18,6 +18,7 @@ import javax.inject.Singleton;
import com.google.inject.AbstractModule;
import com.google.inject.Guice;
import com.google.inject.Injector;
import com.google.inject.TypeLiteral;
import com.google.inject.name.Names;
/**
@@ -38,6 +39,7 @@ public class GuiceWiringTests extends AbstractCompleteWiringTests {
bind(Service.class).to(MyService.class);
bind(Baz.class).in(Singleton.class);
bind(Thang.class).annotatedWith(Names.named("thing")).to(Thang.class);
bind(new TypeLiteral<Parameterized<String>>(){}).toInstance(new Parameterized<String>(){});
}
}

View File

@@ -80,6 +80,11 @@ public class ModuleBeanWiringTests extends AbstractCompleteWiringTests {
public Baz baz(Service service) {
return new Baz(service);
}
@Bean
public Parameterized<String> parameterizedBean() {
return new Parameterized<String>() {};
}
}
protected static class Spam {

View File

@@ -86,6 +86,11 @@ public class ModuleNamedBeanWiringTests extends AbstractCompleteWiringTests {
public Baz baz(Service service) {
return new Baz(service);
}
@Bean
public Parameterized<String> parameterizedBean() {
return new Parameterized<String>() {};
}
}
protected static class Spam {

View File

@@ -48,6 +48,10 @@ public class SpringWiringTests extends AbstractCompleteWiringTests {
public Thang other() {
return new Thang();
}
@Bean
public Parameterized<String> parameterizedBean() {
return new Parameterized<String>() {};
}
}
}

View File

@@ -61,7 +61,10 @@ public class SpringModuleWiringTests extends AbstractCompleteWiringTests {
public Thing that() {
return new Thing();
}
@Bean
public Parameterized<String> parameterizedBean() {
return new Parameterized<String>(){};
}
}
}