Add support for multiple GuiceModuleMetadatas

This commit is contained in:
Dave Syer
2014-04-17 04:29:04 -07:00
parent 1ae99ce814
commit 97d5fa1d96
6 changed files with 101 additions and 28 deletions

View File

@@ -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);
}

View File

@@ -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 <code>@Configuration</code>
* 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

View File

@@ -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 <code>@Bean</code> of this type.
* context of a <code>@Bean</code> of this type. Can be used directly as a
* <code>@Bean</code>, but it is easier to just add <code>@</code>{@link GuiceModule} to
* your <code>@Configuration</code>.
*
* @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();

View File

@@ -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<TypeFilter> result = new HashSet<TypeFilter>();
AnnotationAttributes attributes = new AnnotationAttributes(
annotation.getAnnotationAttributes(GuiceModules.class.getName()));
annotation.getAnnotationAttributes(GuiceModule.class.getName()));
AnnotationAttributes[] filters = attributes.getAnnotationArray(attributeName);
for (AnnotationAttributes filter : filters) {

View File

@@ -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<Class<?>, Provider<?>> bound = new HashMap<Class<?>, 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<Object> 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<Object> cls,
private void bindConditionally(Binder binder, Class<Object> type,
Provider<Object> 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<Object> {
@@ -125,4 +125,22 @@ public class SpringModule implements Module {
}
}
private static class CompositeTypeMatcher implements BindingTypeMatcher {
private Collection<? extends BindingTypeMatcher> matchers;
public CompositeTypeMatcher(Collection<? extends BindingTypeMatcher> matchers) {
this.matchers = matchers;
}
@Override
public boolean matches(Class<?> type) {
for (BindingTypeMatcher matcher : matchers) {
if (matcher.matches(type)) {
return true;
}
}
return false;
}
}
}

View File

@@ -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