diff --git a/pom.xml b/pom.xml index 686535b..b893153 100644 --- a/pom.xml +++ b/pom.xml @@ -47,7 +47,7 @@ - 1.7 + 1.6 UTF-8 UTF-8 diff --git a/src/main/java/org/springframework/guice/GuiceModuleMetadata.java b/src/main/java/org/springframework/guice/GuiceModuleMetadata.java new file mode 100644 index 0000000..499b83a --- /dev/null +++ b/src/main/java/org/springframework/guice/GuiceModuleMetadata.java @@ -0,0 +1,115 @@ +/* + * 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; + +import java.io.IOException; +import java.lang.reflect.Modifier; +import java.util.HashSet; +import java.util.Set; + +import org.springframework.beans.factory.DisposableBean; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.core.type.classreading.CachingMetadataReaderFactory; +import org.springframework.core.type.classreading.MetadataReader; +import org.springframework.core.type.classreading.MetadataReaderFactory; +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. + * + * @author Dave Syer + * + */ +public class GuiceModuleMetadata { + + private TypeFilter[] includeFilters; + + private TypeFilter[] excludeFilters; + + private Set> infrastructureTypes = new HashSet>(); + + { + infrastructureTypes.add(InitializingBean.class); + infrastructureTypes.add(DisposableBean.class); + } + + private MetadataReaderFactory metadataReaderFactory = new CachingMetadataReaderFactory(); + + public GuiceModuleMetadata include(TypeFilter... filters) { + includeFilters = filters; + return this; + } + + public GuiceModuleMetadata exclude(TypeFilter... filters) { + excludeFilters = filters; + return this; + } + + public boolean matches(Class type) { + + if (infrastructureTypes.contains(type)) { + return false; + } + + if (!visible(type)) { + return false; + } + + if (includeFilters != null) { + try { + MetadataReader reader = metadataReaderFactory.getMetadataReader(type + .getName()); + for (TypeFilter filter : includeFilters) { + if (!filter.match(reader, metadataReaderFactory)) { + return false; + } + } + } catch (IOException e) { + throw new IllegalStateException("Cannot read metadata for class " + type, + e); + } + } + if (excludeFilters != null) { + try { + MetadataReader reader = metadataReaderFactory.getMetadataReader(type + .getName()); + for (TypeFilter filter : excludeFilters) { + if (filter.match(reader, metadataReaderFactory)) { + return false; + } + } + } catch (IOException e) { + throw new IllegalStateException("Cannot read metadata for class " + type, + e); + } + } + return true; + } + + private boolean visible(Class type) { + Class cls = type; + while (cls != null && cls != Object.class) { + if (!Modifier.isPublic(cls.getModifiers())) { + return false; + } + cls = cls.getDeclaringClass(); + } + return true; + } + +} diff --git a/src/main/java/org/springframework/guice/SpringModule.java b/src/main/java/org/springframework/guice/SpringModule.java index dd3ab7f..bf4c7e8 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 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; @@ -30,10 +31,17 @@ import com.google.inject.Provider; public class SpringModule implements Module { private DefaultListableBeanFactory beanFactory; + + private GuiceModuleMetadata metadata = new GuiceModuleMetadata(); public SpringModule(GenericApplicationContext context) { 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); + } } @Override @@ -45,22 +53,31 @@ public class SpringModule implements Module { Class type = beanFactory.getType(name); @SuppressWarnings("unchecked") final Class cls = (Class) type; + final String beanName = name; Provider provider = new Provider() { @Override public Object get() { - return beanFactory.getBean(cls); + return beanFactory.getBean(beanName, cls); } }; - if (!cls.isInterface()) { - binder.bind(cls).toProvider(provider); + if (!cls.isInterface() && !ClassUtils.isCglibProxyClass(cls)) { + bindConditionally(binder, cls, provider); } for (Class iface : ClassUtils.getAllInterfacesForClass(cls)) { @SuppressWarnings("unchecked") Class unchecked = (Class) iface; - binder.bind(unchecked).toProvider(provider); + bindConditionally(binder, unchecked, provider); } } } } + private void bindConditionally(Binder binder, Class cls, + Provider provider) { + if (!metadata.matches(cls)) { + return; + } + binder.bind(cls).toProvider(provider); + } + } diff --git a/src/test/java/org/springframework/guice/SpringModuleMetadataTests.java b/src/test/java/org/springframework/guice/SpringModuleMetadataTests.java new file mode 100644 index 0000000..76da586 --- /dev/null +++ b/src/test/java/org/springframework/guice/SpringModuleMetadataTests.java @@ -0,0 +1,118 @@ +/* + * 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.assertNull; + +import javax.inject.Inject; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.springframework.cache.annotation.Cacheable; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.type.filter.AnnotationTypeFilter; +import org.springframework.core.type.filter.AssignableTypeFilter; + +import com.google.inject.ConfigurationException; +import com.google.inject.Guice; +import com.google.inject.Injector; + +/** + * @author Dave Syer + * + */ +public class SpringModuleMetadataTests { + + @Rule + public ExpectedException expected = ExpectedException.none(); + + @Test + public void twoConfigClasses() throws Exception { + Injector injector = createInjector(TestConfig.class, OtherConfig.class); + expected.expect(ConfigurationException.class); + assertNull(injector.getBinding(Service.class)); + } + + @Test + public void includes() throws Exception { + Injector injector = createInjector(TestConfig.class, MetadataIncludesConfig.class); + expected.expect(ConfigurationException.class); + assertNull(injector.getBinding(Service.class)); + } + + @Test + public void excludes() throws Exception { + Injector injector = createInjector(TestConfig.class, MetadataExcludesConfig.class); + expected.expect(ConfigurationException.class); + assertNull(injector.getBinding(Service.class)); + } + + private Injector createInjector(Class... config) { + AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); + context.register(config); + context.refresh(); + Injector injector = Guice.createInjector(new SpringModule(context)); + return injector; + } + + interface Service { + } + + protected static class MyService implements Service { + } + + public static class Foo { + + @Inject + public Foo(Service service) { + } + + } + + @Configuration + protected static class MetadataExcludesConfig { + @Bean + public GuiceModuleMetadata guiceModuleMetadata() { + GuiceModuleMetadata metadata = new GuiceModuleMetadata(); + metadata.exclude(new AssignableTypeFilter(Service.class)); + return metadata; + } + } + + @Configuration + protected static class MetadataIncludesConfig { + @Bean + public GuiceModuleMetadata guiceModuleMetadata() { + GuiceModuleMetadata metadata = new GuiceModuleMetadata(); + metadata.include(new AnnotationTypeFilter(Cacheable.class)); + return metadata; + } + } + + @Configuration + public static class TestConfig { + @Bean + public Service service() { + return new MyService(); + } + } + + @Configuration + public static class OtherConfig { + } + +} diff --git a/src/test/java/org/springframework/guice/SpringModuleWiringTests.java b/src/test/java/org/springframework/guice/SpringModuleWiringTests.java index c20d409..4b43d43 100644 --- a/src/test/java/org/springframework/guice/SpringModuleWiringTests.java +++ b/src/test/java/org/springframework/guice/SpringModuleWiringTests.java @@ -13,6 +13,8 @@ package org.springframework.guice; +import org.junit.Rule; +import org.junit.rules.ExpectedException; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -26,6 +28,9 @@ import com.google.inject.Injector; */ public class SpringModuleWiringTests extends AbstractCompleteWiringTests { + @Rule + public ExpectedException expected = ExpectedException.none(); + @Override protected Injector createInjector() { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); @@ -40,6 +45,7 @@ public class SpringModuleWiringTests extends AbstractCompleteWiringTests { public Service service() { return new MyService(); } + @Bean public Baz baz() { return new Baz(service());