diff --git a/src/main/java/org/springframework/guice/annotation/ModuleRegistryConfiguration.java b/src/main/java/org/springframework/guice/annotation/ModuleRegistryConfiguration.java index 5ab7ceb..78c9928 100644 --- a/src/main/java/org/springframework/guice/annotation/ModuleRegistryConfiguration.java +++ b/src/main/java/org/springframework/guice/annotation/ModuleRegistryConfiguration.java @@ -13,17 +13,6 @@ package org.springframework.guice.annotation; -import java.lang.annotation.Annotation; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.stream.Collectors; - import com.google.inject.Binding; import com.google.inject.Guice; import com.google.inject.Injector; @@ -31,13 +20,11 @@ import com.google.inject.Key; import com.google.inject.Module; import com.google.inject.Scopes; import com.google.inject.Stage; -import com.google.inject.internal.LinkedBindingImpl; import com.google.inject.name.Named; import com.google.inject.spi.Element; import com.google.inject.spi.ElementSource; import com.google.inject.spi.Elements; import com.google.inject.spi.PrivateElements; - import org.springframework.beans.BeansException; import org.springframework.beans.factory.NoSuchBeanDefinitionException; import org.springframework.beans.factory.annotation.Qualifier; @@ -55,10 +42,19 @@ import org.springframework.context.ApplicationContextException; import org.springframework.context.annotation.Configuration; import org.springframework.core.Ordered; import org.springframework.core.ResolvableType; -import org.springframework.core.annotation.AnnotationUtils; import org.springframework.core.annotation.Order; import org.springframework.guice.module.SpringModule; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.stream.Collectors; + /** * Configuration postprocessor that registers all the bindings in Guice modules as Spring * beans. diff --git a/src/main/java/org/springframework/guice/module/SpringModule.java b/src/main/java/org/springframework/guice/module/SpringModule.java index 427dc6a..b4e0f56 100644 --- a/src/main/java/org/springframework/guice/module/SpringModule.java +++ b/src/main/java/org/springframework/guice/module/SpringModule.java @@ -13,21 +13,6 @@ package org.springframework.guice.module; -import java.lang.annotation.Annotation; -import java.lang.reflect.Method; -import java.lang.reflect.ParameterizedType; -import java.lang.reflect.Type; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.HashMap; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; -import java.util.Optional; - -import javax.inject.Provider; - import com.google.inject.AbstractModule; import com.google.inject.Binder; import com.google.inject.Injector; @@ -40,20 +25,38 @@ import com.google.inject.matcher.Matchers; import com.google.inject.name.Named; import com.google.inject.name.Names; import com.google.inject.spi.ProvisionListener; - import org.springframework.beans.factory.BeanFactoryUtils; import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.beans.factory.support.AbstractBeanDefinition; import org.springframework.beans.factory.support.DefaultListableBeanFactory; +import org.springframework.beans.factory.support.RootBeanDefinition; import org.springframework.context.ApplicationContext; +import org.springframework.core.ResolvableType; import org.springframework.core.annotation.AnnotationUtils; import org.springframework.core.type.MethodMetadata; import org.springframework.core.type.StandardMethodMetadata; import org.springframework.util.ClassUtils; import org.springframework.util.ReflectionUtils; +import javax.inject.Provider; +import java.lang.annotation.Annotation; +import java.lang.reflect.Method; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Queue; +import java.util.Set; + /** * @author Dave Syer * @@ -114,23 +117,31 @@ public class SpringModule extends AbstractModule { Optional bindingAnnotation = getAnnotationForBeanDefinition(definition, beanFactory); if (definition.isAutowireCandidate() && definition.getRole() == AbstractBeanDefinition.ROLE_APPLICATION) { - Class type = beanFactory.getType(name); + Type type; + RootBeanDefinition rootBeanDefinition = (RootBeanDefinition) beanFactory.getMergedBeanDefinition(name); + if (rootBeanDefinition.getFactoryBeanName() != null && rootBeanDefinition.getResolvedFactoryMethod() != null) { + type = rootBeanDefinition.getResolvedFactoryMethod().getGenericReturnType(); + } else { + type = rootBeanDefinition.getResolvableType().getType(); + } if (type == null) { continue; } final String beanName = name; - Provider typeProvider = BeanFactoryProvider.typed(beanFactory, type, bindingAnnotation); - Provider namedProvider = BeanFactoryProvider.named(beanFactory, + Provider typeProvider = BeanFactoryProvider.typed(beanFactory, type, bindingAnnotation); + Provider namedProvider = BeanFactoryProvider.named(beanFactory, beanName, type, bindingAnnotation); - if (!type.isInterface() && !ClassUtils.isCglibProxyClass(type)) { - bindConditionally(binder(), name, type, typeProvider, namedProvider, bindingAnnotation); + + Class clazz = (type instanceof Class) ? (Class) type : beanFactory.getType(beanName); + if (!clazz.isInterface() && !ClassUtils.isCglibProxyClass(clazz)) { + bindConditionally(binder(), name, clazz, typeProvider, namedProvider, bindingAnnotation); } - for (Class iface : getAllSuperInterfaces(new Class[]{type})) { - if (!ClassUtils.isCglibProxyClass(iface)) { + for (Type iface : getAllSuperInterfaces(type, clazz)) { + if (!ClassUtils.isCglibProxyClassName(iface.getTypeName())) { bindConditionally(binder(), name, iface, typeProvider, namedProvider, bindingAnnotation); } } - for (Type iface : type.getGenericInterfaces()) { + for (Type iface : clazz.getGenericInterfaces()) { bindConditionally(binder(), name, iface, typeProvider, namedProvider, bindingAnnotation); } } @@ -215,17 +226,33 @@ public class SpringModule extends AbstractModule { return Arrays.equals(candidate.getParameterTypes(), current.getParameterTypes()); } - private static Class[] getAllSuperInterfaces(Class[] childInterfaces) { - List allInterfaces = new LinkedList<>(); - for (Class childInterface : childInterfaces) { - allInterfaces.add(childInterface); - allInterfaces.addAll( - Arrays.asList( - getAllSuperInterfaces(childInterface.getInterfaces()))); + private static Set getAllSuperInterfaces(Type originalType, Class clazz) { + Set allInterfaces = new HashSet<>(); + TypeLiteral typeToken = TypeLiteral.get(originalType); + Queue queue = new LinkedList<>(); + queue.add(clazz); + if (originalType != clazz) { + queue.add(originalType); } - return allInterfaces.toArray(new Class[0]); + while (!queue.isEmpty()) { + Type type = queue.poll(); + allInterfaces.add(type); + if (type instanceof Class) { + for (Type i : ((Class) type).getInterfaces()) { + if (i instanceof Class && ((Class) i).isAssignableFrom(typeToken.getRawType())) { + Type superType = typeToken.getSupertype((Class) i).getType(); + queue.add(superType); + if (!(superType instanceof Class)) { + queue.add(i); + } + } + } + } + } + return allInterfaces; } + @SuppressWarnings({ "rawtypes", "unchecked" }) private void bindConditionally(Binder binder, String name, Type type, Provider typeProvider, Provider namedProvider, Optional bindingAnnotation) { @@ -299,42 +326,42 @@ public class SpringModule extends AbstractModule { } } - private static class BeanFactoryProvider implements Provider { + private static class BeanFactoryProvider implements Provider { private ConfigurableListableBeanFactory beanFactory; private String name; - private Class type; + private Type type; - private T result; + private Object result; private Optional bindingAnnotation; private BeanFactoryProvider(ConfigurableListableBeanFactory beanFactory, - String name, Class type, Optional bindingAnnotation) { + String name, Type type, Optional bindingAnnotation) { this.beanFactory = beanFactory; this.name = name; this.bindingAnnotation = bindingAnnotation; this.type = type; } - public static Provider named(ConfigurableListableBeanFactory beanFactory, - String name, Class type, Optional bindingAnnotation) { - return new BeanFactoryProvider(beanFactory, name, type, bindingAnnotation); + public static Provider named(ConfigurableListableBeanFactory beanFactory, + String name, Type type, Optional bindingAnnotation) { + return new BeanFactoryProvider(beanFactory, name, type, bindingAnnotation); } - public static Provider typed(ConfigurableListableBeanFactory beanFactory, - Class type, Optional bindingAnnotation) { - return new BeanFactoryProvider(beanFactory, null, type, bindingAnnotation); + public static Provider typed(ConfigurableListableBeanFactory beanFactory, + Type type, Optional bindingAnnotation) { + return new BeanFactoryProvider(beanFactory, null, type, bindingAnnotation); } @Override - public T get() { + public Object get() { if (this.result == null) { String[] named = BeanFactoryUtils - .beanNamesForTypeIncludingAncestors(this.beanFactory, this.type); + .beanNamesForTypeIncludingAncestors(this.beanFactory, ResolvableType.forType(type)); List names = new ArrayList(named.length); if (named.length == 1) { names.add(named[0]); @@ -355,12 +382,12 @@ public class SpringModule extends AbstractModule { } } if (names.size() == 1) { - this.result = this.beanFactory.getBean(names.get(0), this.type); + this.result = this.beanFactory.getBean(names.get(0)); } else { for (String name : named) { if (this.beanFactory.getBeanDefinition(name).isPrimary()) { - this.result = this.beanFactory.getBean(name, this.type); + this.result = this.beanFactory.getBean(name); break; } } diff --git a/src/test/java/org/springframework/guice/SuperClassTests.java b/src/test/java/org/springframework/guice/SuperClassTests.java index 277f7c8..d9859e7 100644 --- a/src/test/java/org/springframework/guice/SuperClassTests.java +++ b/src/test/java/org/springframework/guice/SuperClassTests.java @@ -2,29 +2,154 @@ package org.springframework.guice; import com.google.inject.AbstractModule; import com.google.inject.Injector; +import com.google.inject.Key; +import com.google.inject.TypeLiteral; import org.junit.Test; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.core.ResolvableType; import org.springframework.guice.annotation.EnableGuiceModules; +import org.springframework.stereotype.Component; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; public class SuperClassTests { + @Test public void testSpringInterface() { - AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ModulesConfig.class); - IParent iParent = context.getBean(IParent.class); - assertTrue(iParent instanceof IChildImpl); + baseTestSpringInterface(ModulesConfig.class); + } + + @Test + public void testImportSpringInterface() { + baseTestSpringInterface(ImportConfig.class); + } + + @Test + public void testComponentScanSpringInterface() { + baseTestSpringInterface(ComponentScanConfig.class); + } + + private void baseTestSpringInterface(Class configClass) { + AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(configClass); + assertTrue(context.getBean(IParent.class) instanceof IGrandChildImpl); + assertTrue(context.getBean(IChild.class) instanceof IGrandChildImpl); + assertTrue(context.getBean(IGrandChild.class) instanceof IGrandChildImpl); } @Test public void testGuiceInterface() { - AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ModulesConfig.class); + baseTestGuiceInterface(ModulesConfig.class); + } + + @Test + public void testImportGuiceInterface() { + baseTestGuiceInterface(ImportConfig.class); + } + + @Test + public void testComponentScanGuiceInterface() { + baseTestGuiceInterface(ComponentScanConfig.class); + } + + private void baseTestGuiceInterface(Class configClass) { + AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(configClass); Injector injector = context.getBean(Injector.class); - IParent iParent = injector.getInstance(IParent.class); - assertTrue(iParent instanceof IChildImpl); + assertTrue(injector.getInstance(IParent.class) instanceof IGrandChildImpl); + assertTrue(injector.getInstance(IChild.class) instanceof IGrandChildImpl); + assertTrue(injector.getInstance(IGrandChild.class) instanceof IGrandChildImpl); + } + + @Test + public void testSpringInterfaceWithType() { + baseTestSpringInterfaceWithType(ModulesConfig.class); + } + + @Test + public void testImportSpringInterfaceWithType() { + baseTestSpringInterfaceWithType(ImportConfig.class); + } + + @Test + public void testComponentScanSpringInterfaceWithType() { + baseTestSpringInterfaceWithType(ComponentScanConfig.class); + } + + private void baseTestSpringInterfaceWithType(Class configClass) { + AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(configClass); + + String[] allParentBeanNames = context.getBeanNamesForType(IParentWithType.class); + assertEquals(2, allParentBeanNames.length); + + String[] stringParentBeanNames = context.getBeanNamesForType(ResolvableType.forClassWithGenerics(IParentWithType.class, String.class)); + assertEquals(1, stringParentBeanNames.length); + assertTrue(new TypeLiteral>() {}.getRawType().isInstance(context.getBean(stringParentBeanNames[0]))); + + String[] integerParentBeanNames = context.getBeanNamesForType(ResolvableType.forClassWithGenerics(IParentWithType.class, Integer.class)); + assertEquals(1, integerParentBeanNames.length); + assertTrue(new TypeLiteral>() {}.getRawType().isInstance(context.getBean(integerParentBeanNames[0]))); + + String[] allChildBeanNames = context.getBeanNamesForType(IChildWithType.class); + assertEquals(2, allChildBeanNames.length); + + String[] stringChildBeanNames = context.getBeanNamesForType(ResolvableType.forClassWithGenerics(IChildWithType.class, String.class)); + assertEquals(1, stringChildBeanNames.length); + assertTrue(new TypeLiteral>() {}.getRawType().isInstance(context.getBean(stringChildBeanNames[0]))); + + String[] integerChildBeanNames = context.getBeanNamesForType(ResolvableType.forClassWithGenerics(IChildWithType.class, Integer.class)); + assertEquals(1, integerChildBeanNames.length); + assertTrue(new TypeLiteral>() {}.getRawType().isInstance(context.getBean(integerChildBeanNames[0]))); + + String[] allGrandChildBeanNames = context.getBeanNamesForType(IGrandChildWithType.class); + assertEquals(2, allGrandChildBeanNames.length); + + String[] stringGrandChildBeanNames = context.getBeanNamesForType(ResolvableType.forClassWithGenerics(IGrandChildWithType.class, String.class)); + assertEquals(1, stringGrandChildBeanNames.length); + assertTrue(new TypeLiteral>() {}.getRawType().isInstance(context.getBean(stringGrandChildBeanNames[0]))); + + String[] integerGrandChildBeanNames = context.getBeanNamesForType(ResolvableType.forClassWithGenerics(IGrandChildWithType.class, Integer.class)); + assertEquals(1, integerGrandChildBeanNames.length); + assertTrue(new TypeLiteral>() {}.getRawType().isInstance(context.getBean(integerGrandChildBeanNames[0]))); + + } + + @Test + public void testGuiceInterfaceWithType() { + baseTestGuiceInterfaceWithType(ModulesConfig.class); + } + + @Test + public void testImportGuiceInterfaceWithType() { + baseTestGuiceInterfaceWithType(ImportConfig.class); + } + + @Test + public void testComponentScanGuiceInterfaceWithType() { + baseTestGuiceInterfaceWithType(ComponentScanConfig.class); + } + + private void baseTestGuiceInterfaceWithType(Class configClass) { + AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(configClass); + Injector injector = context.getBean(Injector.class); + IParentWithType iParentString = injector.getInstance(Key.get(new TypeLiteral>(){})); + assertTrue(iParentString instanceof IGrandChildString); + IParentWithType iParentInteger = injector.getInstance(Key.get(new TypeLiteral>(){})); + assertTrue(iParentInteger instanceof IGrandChildInteger); + + IChildWithType iChildString = injector.getInstance(Key.get(new TypeLiteral>(){})); + assertTrue(iChildString instanceof IGrandChildString); + IChildWithType iChildInteger = injector.getInstance(Key.get(new TypeLiteral>(){})); + assertTrue(iChildInteger instanceof IGrandChildInteger); + + IGrandChildWithType iGrandChildString = injector.getInstance(Key.get(new TypeLiteral>(){})); + assertTrue(iGrandChildString instanceof IGrandChildString); + IGrandChildWithType iGrandChildInteger = injector.getInstance(Key.get(new TypeLiteral>(){})); + assertTrue(iGrandChildInteger instanceof IGrandChildInteger); } @Test @@ -42,20 +167,29 @@ public class SuperClassTests { assertTrue(iFoo instanceof Foo); } - @Configuration - @EnableGuiceModules - static class ModulesConfig { + @Test + public void testSpringClassWithType() { + AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ModulesConfig.class); + String[] stringBeanNames = context.getBeanNamesForType(ResolvableType.forClassWithGenerics(IFooWithType.class, String.class)); + assertEquals(1, stringBeanNames.length); + assertTrue(context.getBean(stringBeanNames[0]) instanceof StringFoo); - @Bean - public IChild iChild() { - return new IChildImpl(); - } + String[] integerBeanNames = context.getBeanNamesForType(ResolvableType.forClassWithGenerics(IFooWithType.class, Integer.class)); + assertEquals(1, integerBeanNames.length); + assertTrue(context.getBean(integerBeanNames[0]) instanceof IntegerFoo); + } - @Bean - public Foo iFoo() { - return new Foo(); - } + @Test + public void testGuiceClassWithType() { + AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ModulesConfig.class); + Injector injector = context.getBean(Injector.class); + IFooWithType stringFoo = injector.getInstance(Key.get(new TypeLiteral>(){})); + assertTrue(stringFoo instanceof StringFoo); + IFooWithType integerFoo = injector.getInstance(Key.get(new TypeLiteral>(){})); + assertTrue(integerFoo instanceof IntegerFoo); + } + static class DisableJITConfig { @Bean public AbstractModule disableJITModule() { return new AbstractModule() { @@ -67,6 +201,55 @@ public class SuperClassTests { } } + @Configuration + @EnableGuiceModules + static class ModulesConfig extends DisableJITConfig { + + @Bean + public IGrandChild iGrandChild() { + return new IGrandChildImpl(); + } + + @Bean + public IGrandChildWithType iChildString() { + return new IGrandChildString(); + } + + @Bean + public IGrandChildWithType iChildInteger() { + return new IGrandChildInteger(); + } + + + @Bean + public Foo iFoo() { + return new Foo(); + } + + @Bean + public StringFoo stringFoo() { + return new StringFoo(); + } + + @Bean IntegerFoo integerFoo() { + return new IntegerFoo(); + } + + + } + + @Configuration + @EnableGuiceModules + @Import({IGrandChildImpl.class, IGrandChildString.class, IGrandChildInteger.class}) + static class ImportConfig extends DisableJITConfig { + } + + @Configuration + @EnableGuiceModules + @ComponentScan(basePackageClasses = ComponentScanConfig.class, resourcePattern = "**/SuperClassTests**.class", + excludeFilters = {@ComponentScan.Filter(Configuration.class)}) + static class ComponentScanConfig extends DisableJITConfig { + } public interface IParent { } @@ -75,10 +258,39 @@ public class SuperClassTests { } - public static class IChildImpl implements IChild { + public interface IGrandChild extends IChild { } + @Component + public static class IGrandChildImpl implements IGrandChild { + + } + + public interface IParentWithType { + + } + + public interface IChildWithType extends IParentWithType { + + } + + public interface IGrandChildWithType extends IChildWithType { + + } + + @Component + public static class IGrandChildString implements IGrandChildWithType { + + } + + @Component + public static class IGrandChildInteger implements IGrandChildWithType { + + } + + + public interface IFoo { } @@ -86,4 +298,17 @@ public class SuperClassTests { public static class Foo implements IFoo { } + + public interface IFooWithType { + + } + + public static class StringFoo implements IFooWithType { + + } + + public static class IntegerFoo implements IFooWithType { + + } + }