diff --git a/src/main/java/org/springframework/guice/BindingTypeMatcher.java b/src/main/java/org/springframework/guice/BindingTypeMatcher.java new file mode 100644 index 0000000..a088e10 --- /dev/null +++ b/src/main/java/org/springframework/guice/BindingTypeMatcher.java @@ -0,0 +1,27 @@ +/* + * Copyright 2012-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.guice; + +/** + * @author Dave Syer + * + */ +public interface BindingTypeMatcher { + + boolean matches(Class type); + +} diff --git a/src/main/java/org/springframework/guice/GuiceModules.java b/src/main/java/org/springframework/guice/GuiceModule.java similarity index 71% rename from src/main/java/org/springframework/guice/GuiceModules.java rename to src/main/java/org/springframework/guice/GuiceModule.java index 2e3e670..5be2056 100644 --- a/src/main/java/org/springframework/guice/GuiceModules.java +++ b/src/main/java/org/springframework/guice/GuiceModule.java @@ -23,14 +23,24 @@ import org.springframework.context.annotation.ComponentScan.Filter; import org.springframework.context.annotation.Import; /** + * Annotation that decorates the whole application context and provides metadata to Guice + * if the context is used as a Module. Can be added to any @Configuration + * class (and if added to many then the filters are combined with logical OR). By default + * all beans in the context will be bound to Guice with all of their implemented + * interfaces. If you need to filter out which beans are added you can filter by class. + * TODO: filter by name. + * * @author Dave Syer + * + * @see SpringModule + * @see GuiceModuleMetadata * */ @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Import(GuiceModuleMetadataRegistrar.class) -public @interface GuiceModules { +public @interface GuiceModule { /** * Specifies which types are eligible for inclusion in Guice module diff --git a/src/main/java/org/springframework/guice/GuiceModuleMetadata.java b/src/main/java/org/springframework/guice/GuiceModuleMetadata.java index 2555353..c1f5a0c 100644 --- a/src/main/java/org/springframework/guice/GuiceModuleMetadata.java +++ b/src/main/java/org/springframework/guice/GuiceModuleMetadata.java @@ -30,12 +30,14 @@ import org.springframework.core.type.filter.TypeFilter; /** * Encapsulates some metadata about a Guice module that is to be created from the parent - * context of a @Bean of this type. + * context of a @Bean of this type. Can be used directly as a + * @Bean, but it is easier to just add @{@link GuiceModule} to + * your @Configuration. * * @author Dave Syer * */ -public class GuiceModuleMetadata { +public class GuiceModuleMetadata implements BindingTypeMatcher { private TypeFilter[] includeFilters; @@ -60,6 +62,7 @@ public class GuiceModuleMetadata { return this; } + @Override public boolean matches(Class type) { if (infrastructureTypes.contains(type)) { @@ -104,7 +107,9 @@ public class GuiceModuleMetadata { private boolean visible(Class type) { Class cls = type; while (cls != null && cls != Object.class) { - if (!Modifier.isInterface(cls.getModifiers()) && !Modifier.isPublic(cls.getModifiers()) && !Modifier.isProtected(cls.getModifiers())) { + if (!Modifier.isInterface(cls.getModifiers()) + && !Modifier.isPublic(cls.getModifiers()) + && !Modifier.isProtected(cls.getModifiers())) { return false; } cls = cls.getDeclaringClass(); diff --git a/src/main/java/org/springframework/guice/GuiceModuleMetadataRegistrar.java b/src/main/java/org/springframework/guice/GuiceModuleMetadataRegistrar.java index 0f3bc42..f4aec9e 100644 --- a/src/main/java/org/springframework/guice/GuiceModuleMetadataRegistrar.java +++ b/src/main/java/org/springframework/guice/GuiceModuleMetadataRegistrar.java @@ -26,8 +26,10 @@ import java.util.regex.Pattern; import org.springframework.beans.BeanUtils; import org.springframework.beans.factory.FactoryBean; +import org.springframework.beans.factory.support.AbstractBeanDefinition; import org.springframework.beans.factory.support.BeanDefinitionBuilder; import org.springframework.beans.factory.support.BeanDefinitionRegistry; +import org.springframework.beans.factory.support.DefaultBeanNameGenerator; import org.springframework.context.ResourceLoaderAware; import org.springframework.context.annotation.FilterType; import org.springframework.context.annotation.ImportBeanDefinitionRegistrar; @@ -65,8 +67,10 @@ public class GuiceModuleMetadataRegistrar implements ImportBeanDefinitionRegistr parseFilters(annotation, "includeFilters")); builder.addPropertyValue("excludeFilters", parseFilters(annotation, "excludeFilters")); - registry.registerBeanDefinition(GuiceModules.class.getName(), - builder.getBeanDefinition()); + AbstractBeanDefinition definition = builder.getBeanDefinition(); + String name = new DefaultBeanNameGenerator().generateBeanName(definition, + registry); + registry.registerBeanDefinition(name, definition); } public static class GuiceModuleMetadataFactory implements @@ -110,7 +114,7 @@ public class GuiceModuleMetadataRegistrar implements ImportBeanDefinitionRegistr Set result = new HashSet(); AnnotationAttributes attributes = new AnnotationAttributes( - annotation.getAnnotationAttributes(GuiceModules.class.getName())); + annotation.getAnnotationAttributes(GuiceModule.class.getName())); AnnotationAttributes[] filters = attributes.getAnnotationArray(attributeName); for (AnnotationAttributes filter : filters) { diff --git a/src/main/java/org/springframework/guice/SpringModule.java b/src/main/java/org/springframework/guice/SpringModule.java index e2b5794..1350546 100644 --- a/src/main/java/org/springframework/guice/SpringModule.java +++ b/src/main/java/org/springframework/guice/SpringModule.java @@ -13,6 +13,7 @@ package org.springframework.guice; +import java.util.Collection; import java.util.HashMap; import java.util.Map; @@ -36,7 +37,7 @@ public class SpringModule implements Module { private DefaultListableBeanFactory beanFactory; - private GuiceModuleMetadata metadata = new GuiceModuleMetadata(); + private BindingTypeMatcher matcher = new GuiceModuleMetadata(); private Map, Provider> bound = new HashMap, Provider>(); @@ -44,10 +45,8 @@ public class SpringModule implements Module { this.beanFactory = (DefaultListableBeanFactory) context .getAutowireCapableBeanFactory(); if (beanFactory.getBeanNamesForType(GuiceModuleMetadata.class).length > 0) { - this.metadata = beanFactory.getBean(GuiceModuleMetadata.class); - } else if (BeanFactoryUtils.beanNamesForTypeIncludingAncestors(beanFactory, - GuiceModuleMetadata.class).length > 0) { - this.metadata = beanFactory.getBean(GuiceModuleMetadata.class); + this.matcher = new CompositeTypeMatcher(beanFactory.getBeansOfType( + GuiceModuleMetadata.class).values()); } } @@ -63,9 +62,6 @@ public class SpringModule implements Module { final String beanName = name; Provider provider = new BeanFactoryProvider(beanFactory, beanName, type); - if (bound.get(type) != null) { - continue; // TODO: named beans - } if (!cls.isInterface() && !ClassUtils.isCglibProxyClass(cls)) { bindConditionally(binder, cls, provider); } @@ -78,13 +74,17 @@ public class SpringModule implements Module { } } - private void bindConditionally(Binder binder, Class cls, + private void bindConditionally(Binder binder, Class type, Provider provider) { - if (!metadata.matches(cls)) { + if (bound.get(type) != null) { + // Only bind one provider for each type + return; // TODO: named beans + } + if (!matcher.matches(type)) { return; } - binder.bind(cls).toProvider(provider); - bound.put(cls, provider); + binder.bind(type).toProvider(provider); + bound.put(type, provider); } private static class BeanFactoryProvider implements Provider { @@ -125,4 +125,22 @@ public class SpringModule implements Module { } } + private static class CompositeTypeMatcher implements BindingTypeMatcher { + private Collection matchers; + + public CompositeTypeMatcher(Collection matchers) { + this.matchers = matchers; + } + + @Override + public boolean matches(Class type) { + for (BindingTypeMatcher matcher : matchers) { + if (matcher.matches(type)) { + return true; + } + } + return false; + } + } + } diff --git a/src/test/java/org/springframework/guice/GuiceModulesTests.java b/src/test/java/org/springframework/guice/GuiceModuleAnnotationTests.java similarity index 75% rename from src/test/java/org/springframework/guice/GuiceModulesTests.java rename to src/test/java/org/springframework/guice/GuiceModuleAnnotationTests.java index 85b11e9..bf327bb 100644 --- a/src/test/java/org/springframework/guice/GuiceModulesTests.java +++ b/src/test/java/org/springframework/guice/GuiceModuleAnnotationTests.java @@ -13,13 +13,13 @@ package org.springframework.guice; +import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; -import javax.inject.Inject; - import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan.Filter; @@ -34,7 +34,7 @@ import com.google.inject.Injector; * @author Dave Syer * */ -public class GuiceModulesTests { +public class GuiceModuleAnnotationTests { @Rule public ExpectedException expected = ExpectedException.none(); @@ -42,8 +42,7 @@ public class GuiceModulesTests { @Test public void includes() throws Exception { Injector injector = createInjector(TestConfig.class, MetadataIncludesConfig.class); - expected.expect(ConfigurationException.class); - assertNull(injector.getBinding(Service.class)); + assertNotNull(injector.getBinding(Service.class)); } @Test @@ -53,6 +52,12 @@ public class GuiceModulesTests { assertNull(injector.getInstance(Service.class)); } + @Test + public void twoIncludes() throws Exception { + Injector injector = createInjector(TestConfig.class, MetadataIncludesConfig.class, MetadataMoreIncludesConfig.class); + assertNotNull(injector.getBinding(Service.class)); + } + private Injector createInjector(Class... config) { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); context.register(config); @@ -69,23 +74,27 @@ public class GuiceModulesTests { public static class Foo { - @Inject + @Autowired public Foo(Service service) { } } @Configuration - @GuiceModules(excludeFilters=@Filter(type=FilterType.REGEX, pattern=".*")) + @GuiceModule(excludeFilters=@Filter(type=FilterType.REGEX, pattern=".*")) protected static class MetadataExcludesConfig { } - // TODO: allow include Service.class as well @Configuration - @GuiceModules(includeFilters=@Filter(type=FilterType.ASSIGNABLE_TYPE, value=MyService.class)) + @GuiceModule(includeFilters=@Filter(type=FilterType.ASSIGNABLE_TYPE, value=Service.class)) protected static class MetadataIncludesConfig { } + @Configuration + @GuiceModule(includeFilters=@Filter(type=FilterType.ASSIGNABLE_TYPE, value=Foo.class)) + protected static class MetadataMoreIncludesConfig { + } + @Configuration public static class TestConfig { @Bean