diff --git a/README.md b/README.md index 79edb2e..3cfaa3b 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,31 @@ Note that the `ApplicationContext` in this example might contain the (`MyModule`), or if `Service` is a concrete class it could be neither, but Guice creates an instance and wires it for us. +If the `ApplicationConfiguration` is annotated `@GuiceModule` then it +can filter the types of bean that are registered with the Guice +binder. Example: + +```java +@Configuration +@GuiceModule(includeFilters=@Filter(pattern=.*\\.Service)) +public class ApplicationConfiguration { + @Bean + public MyService service() { + ... + } +} +``` + +In this case, only bean types (or interfaces) called "Service" will +match the include filter, and only those beans will be bound. + +If there are multiple `@Beans` of the same type in the +`ApplicationContext` then the `SpringModule` will register them all, +and there will be a runtime exception if an `Injector` needs one. As +with normal Spring dependency resolution, you can add the `@Primary` +marker to a single bean to differentiate and hint to the `Injector` +which instance to use. + ## Using existing Guice Modules in a Spring ApplicationContext The main feature here is a Spring `@Configuration` annotation: @@ -65,6 +90,39 @@ If there is a `@Bean` of type `Service` it will be returned from the create it and autowire its dependencies for you. A side effect of this is that a `BeanDefinition` *will* be created. +In the example above, if `ApplicationConfiguration` was annotated +`@EnableGuiceModules` then there is an `Injector` bean already waiting +to be used, including wiring it into the application itself if you +need it. So this works as well: + +```java +@Configuration +@EnableGuiceModules +public class ApplicationConfiguration { + + @Autowired + private Injector injector; + + @Bean + public Foo foo() { + // Guice creates and does the wiring of Foo instead of Spring + return injector.getInstance(Foo.class); + } +} +``` + +In this example if the `Injector` has a binding for a `Provider` of +`Foo.class` then the `foo()` method is redundant - it is already +resolvable as a Spring dependency. But if Guice is being used as a +factory for new objects that it doesn't have bindings for, then it +makes sense. + +**Note:** if you also have `@GuiceModule` in your context, then using +the injector to create a `@Bean` directly is a bad idea (there's a +dependency cycle). You *can* do it, and break the cycle, if you +exclude the `@Bean` type from the `Injector` bindings using the +`@GuiceModule` exclude filters. + ## Limitations * So far there is no support for the Guice SPI methods in @@ -80,6 +138,6 @@ is that a `BeanDefinition` *will* be created. * `SpringModule` treats all beans as singletons. -* `SpringModule` binds all interfaces of a bean it can find. This will - cause issues sooner rather than later (e.g. when 2 beans implement - the same interface). +* `SpringModule` binds all interfaces of a bean it can find. This + should work out OK, as long as those interfaces are not needed for + injection (and if there is no `@Primary` bean). diff --git a/src/main/java/org/springframework/guice/GuiceModule.java b/src/main/java/org/springframework/guice/GuiceModule.java index 5be2056..c92d5dd 100644 --- a/src/main/java/org/springframework/guice/GuiceModule.java +++ b/src/main/java/org/springframework/guice/GuiceModule.java @@ -39,7 +39,7 @@ import org.springframework.context.annotation.Import; @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented -@Import(GuiceModuleMetadataRegistrar.class) +@Import(GuiceModuleRegistrar.class) public @interface GuiceModule { /** diff --git a/src/main/java/org/springframework/guice/GuiceModuleMetadataRegistrar.java b/src/main/java/org/springframework/guice/GuiceModuleRegistrar.java similarity index 98% rename from src/main/java/org/springframework/guice/GuiceModuleMetadataRegistrar.java rename to src/main/java/org/springframework/guice/GuiceModuleRegistrar.java index f4aec9e..9ad1ec7 100644 --- a/src/main/java/org/springframework/guice/GuiceModuleMetadataRegistrar.java +++ b/src/main/java/org/springframework/guice/GuiceModuleRegistrar.java @@ -48,7 +48,7 @@ import org.springframework.util.Assert; * @author Dave Syer * */ -public class GuiceModuleMetadataRegistrar implements ImportBeanDefinitionRegistrar, +public class GuiceModuleRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware { private ResourceLoader resourceLoader = new DefaultResourceLoader(); diff --git a/src/main/java/org/springframework/guice/ModuleRegistryConfiguration.java b/src/main/java/org/springframework/guice/ModuleRegistryConfiguration.java index ce4a8ea..8e30786 100644 --- a/src/main/java/org/springframework/guice/ModuleRegistryConfiguration.java +++ b/src/main/java/org/springframework/guice/ModuleRegistryConfiguration.java @@ -13,6 +13,7 @@ package org.springframework.guice; +import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Map.Entry; @@ -23,7 +24,7 @@ import org.springframework.beans.BeansException; import org.springframework.beans.factory.ObjectFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.config.BeanPostProcessor; -import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; +import org.springframework.beans.factory.support.DefaultListableBeanFactory; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.Ordered; @@ -41,7 +42,7 @@ import com.google.inject.Provider; public class ModuleRegistryConfiguration implements BeanPostProcessor { @Autowired - private ConfigurableListableBeanFactory beanFactory; + private DefaultListableBeanFactory beanFactory; @Autowired(required=false) private List modules = Collections.emptyList(); @@ -55,6 +56,8 @@ public class ModuleRegistryConfiguration implements BeanPostProcessor { @PostConstruct public void init() { + List modules = new ArrayList(this.modules); + modules.add(new SpringModule(beanFactory)); injector = Guice.createInjector(modules); for (Entry, Binding> entry : injector.getBindings().entrySet()) { if (entry.getKey().getTypeLiteral().getRawType().equals(Injector.class)) { diff --git a/src/main/java/org/springframework/guice/SpringModule.java b/src/main/java/org/springframework/guice/SpringModule.java index 1350546..2f5632d 100644 --- a/src/main/java/org/springframework/guice/SpringModule.java +++ b/src/main/java/org/springframework/guice/SpringModule.java @@ -21,7 +21,7 @@ import org.springframework.beans.factory.BeanFactoryUtils; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.support.AbstractBeanDefinition; import org.springframework.beans.factory.support.DefaultListableBeanFactory; -import org.springframework.context.support.GenericApplicationContext; +import org.springframework.context.ApplicationContext; import org.springframework.util.ClassUtils; import com.google.inject.Binder; @@ -41,12 +41,14 @@ public class SpringModule implements Module { private Map, Provider> bound = new HashMap, Provider>(); - public SpringModule(GenericApplicationContext context) { - this.beanFactory = (DefaultListableBeanFactory) context - .getAutowireCapableBeanFactory(); + public SpringModule(ApplicationContext context) { + this((DefaultListableBeanFactory) context.getAutowireCapableBeanFactory()); + } + + public SpringModule(DefaultListableBeanFactory beanFactory) { + this.beanFactory = beanFactory; if (beanFactory.getBeanNamesForType(GuiceModuleMetadata.class).length > 0) { - this.matcher = new CompositeTypeMatcher(beanFactory.getBeansOfType( - GuiceModuleMetadata.class).values()); + this.matcher = new CompositeTypeMatcher(beanFactory.getBeansOfType(GuiceModuleMetadata.class).values()); } } @@ -54,14 +56,12 @@ public class SpringModule implements Module { public void configure(Binder binder) { for (String name : beanFactory.getBeanDefinitionNames()) { BeanDefinition definition = beanFactory.getBeanDefinition(name); - if (definition.isAutowireCandidate() - && definition.getRole() == AbstractBeanDefinition.ROLE_APPLICATION) { + if (definition.isAutowireCandidate() && definition.getRole() == AbstractBeanDefinition.ROLE_APPLICATION) { Class type = beanFactory.getType(name); @SuppressWarnings("unchecked") final Class cls = (Class) type; final String beanName = name; - Provider provider = new BeanFactoryProvider(beanFactory, - beanName, type); + Provider provider = new BeanFactoryProvider(beanFactory, beanName, type); if (!cls.isInterface() && !ClassUtils.isCglibProxyClass(cls)) { bindConditionally(binder, cls, provider); } @@ -74,8 +74,7 @@ public class SpringModule implements Module { } } - private void bindConditionally(Binder binder, Class type, - Provider provider) { + private void bindConditionally(Binder binder, Class type, Provider provider) { if (bound.get(type) != null) { // Only bind one provider for each type return; // TODO: named beans @@ -83,6 +82,9 @@ public class SpringModule implements Module { if (!matcher.matches(type)) { return; } + if (type.getName().startsWith("com.google.inject")) { + return; + } binder.bind(type).toProvider(provider); bound.put(type, provider); } @@ -90,12 +92,14 @@ public class SpringModule implements Module { private static class BeanFactoryProvider implements Provider { private DefaultListableBeanFactory beanFactory; + private String name; + private Class type; + private Object result; - public BeanFactoryProvider(DefaultListableBeanFactory beanFactory, String name, - Class type) { + public BeanFactoryProvider(DefaultListableBeanFactory beanFactory, String name, Class type) { this.beanFactory = beanFactory; this.name = name; this.type = type; @@ -104,11 +108,11 @@ public class SpringModule implements Module { @Override public Object get() { if (result == null) { - String[] names = BeanFactoryUtils.beanNamesForTypeIncludingAncestors( - beanFactory, type); + String[] names = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(beanFactory, type); if (names.length == 1) { result = beanFactory.getBean(name, type); - } else { + } + else { for (String name : names) { if (beanFactory.getBeanDefinition(name).isPrimary()) { result = beanFactory.getBean(name, type); @@ -116,8 +120,7 @@ public class SpringModule implements Module { } } if (result == null) { - throw new ProvisionException( - "No primary bean definition for type: " + type); + throw new ProvisionException("No primary bean definition for type: " + type); } } } diff --git a/src/test/java/org/springframework/guice/EnableGuiceModulesTests.java b/src/test/java/org/springframework/guice/EnableGuiceModulesTests.java new file mode 100644 index 0000000..0d00c6c --- /dev/null +++ b/src/test/java/org/springframework/guice/EnableGuiceModulesTests.java @@ -0,0 +1,77 @@ +/* + * Copyright 2013-2014 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; + +import static org.junit.Assert.assertNotNull; + +import javax.inject.Inject; + +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.FilterType; +import org.springframework.context.annotation.ComponentScan.Filter; +import org.springframework.context.annotation.Configuration; + +import com.google.inject.Injector; + +/** + * @author Dave Syer + * + */ +public class EnableGuiceModulesTests { + + @Test + public void test() { + AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(TestConfig.class); + assertNotNull(context.getBean(Foo.class)); + context.close(); + } + + interface Service { + } + + protected static class MyService implements Service { + } + + public static class Foo { + + @Inject + public Foo(Service service) { + } + + } + + @Configuration + @EnableGuiceModules + @GuiceModule(excludeFilters = @Filter(type = FilterType.ASSIGNABLE_TYPE, value = Foo.class)) + protected static class TestConfig { + + @Autowired + private Injector injector; + + @Bean + public Foo foo() { + return injector.getInstance(Foo.class); + } + + @Bean + public Service service() { + return new MyService(); + } + + } + +}