Add @GuiceModules with includes and excludes

This commit is contained in:
Dave Syer
2014-04-16 21:51:33 -07:00
parent bc73409012
commit 431094e664
3 changed files with 320 additions and 0 deletions

View File

@@ -0,0 +1,179 @@
/*
* 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.lang.annotation.Annotation;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.regex.Pattern;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.context.ResourceLoaderAware;
import org.springframework.context.annotation.FilterType;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.core.type.filter.AnnotationTypeFilter;
import org.springframework.core.type.filter.AspectJTypeFilter;
import org.springframework.core.type.filter.AssignableTypeFilter;
import org.springframework.core.type.filter.RegexPatternTypeFilter;
import org.springframework.core.type.filter.TypeFilter;
import org.springframework.util.Assert;
/**
* @author Dave Syer
*
*/
public class GuiceModuleMetadataRegistrar implements ImportBeanDefinitionRegistrar,
ResourceLoaderAware {
private ResourceLoader resourceLoader = new DefaultResourceLoader();
@Override
public void setResourceLoader(ResourceLoader resourceLoader) {
this.resourceLoader = resourceLoader;
}
@Override
public void registerBeanDefinitions(AnnotationMetadata annotation,
BeanDefinitionRegistry registry) {
BeanDefinitionBuilder builder = BeanDefinitionBuilder
.genericBeanDefinition(GuiceModuleMetadataFactory.class);
builder.addPropertyValue("includeFilters",
parseFilters(annotation, "includeFilters"));
builder.addPropertyValue("excludeFilters",
parseFilters(annotation, "excludeFilters"));
registry.registerBeanDefinition(GuiceModules.class.getName(),
builder.getBeanDefinition());
}
public static class GuiceModuleMetadataFactory implements
FactoryBean<GuiceModuleMetadata> {
private Collection<? extends TypeFilter> includeFilters;
private Collection<? extends TypeFilter> excludeFilters;
public void setIncludeFilters(Collection<? extends TypeFilter> includeFilters) {
this.includeFilters = includeFilters;
}
public void setExcludeFilters(Collection<? extends TypeFilter> excludeFilters) {
this.excludeFilters = excludeFilters;
}
@Override
public GuiceModuleMetadata getObject() throws Exception {
return new GuiceModuleMetadata()
.include(
includeFilters.toArray(new TypeFilter[includeFilters.size()]))
.exclude(
excludeFilters.toArray(new TypeFilter[excludeFilters.size()]));
}
@Override
public Class<?> getObjectType() {
return GuiceModuleMetadata.class;
}
@Override
public boolean isSingleton() {
return false;
}
}
private Set<TypeFilter> parseFilters(AnnotationMetadata annotation,
String attributeName) {
Set<TypeFilter> result = new HashSet<TypeFilter>();
AnnotationAttributes attributes = new AnnotationAttributes(
annotation.getAnnotationAttributes(GuiceModules.class.getName()));
AnnotationAttributes[] filters = attributes.getAnnotationArray(attributeName);
for (AnnotationAttributes filter : filters) {
result.addAll(typeFiltersFor(filter));
}
return result;
}
private List<TypeFilter> typeFiltersFor(AnnotationAttributes filterAttributes) {
List<TypeFilter> typeFilters = new ArrayList<TypeFilter>();
FilterType filterType = filterAttributes.getEnum("type");
for (Class<?> filterClass : filterAttributes.getClassArray("value")) {
switch (filterType) {
case ANNOTATION:
Assert.isAssignable(Annotation.class, filterClass,
"An error occured when processing a @ComponentScan "
+ "ANNOTATION type filter: ");
@SuppressWarnings("unchecked")
Class<Annotation> annoClass = (Class<Annotation>) filterClass;
typeFilters.add(new AnnotationTypeFilter(annoClass));
break;
case ASSIGNABLE_TYPE:
typeFilters.add(new AssignableTypeFilter(filterClass));
break;
case CUSTOM:
Assert.isAssignable(TypeFilter.class, filterClass,
"An error occured when processing a @ComponentScan "
+ "CUSTOM type filter: ");
typeFilters
.add(BeanUtils.instantiateClass(filterClass, TypeFilter.class));
break;
default:
throw new IllegalArgumentException("Unknown filter type " + filterType);
}
}
for (String expression : getPatterns(filterAttributes)) {
String rawName = filterType.toString();
if ("REGEX".equals(rawName)) {
typeFilters.add(new RegexPatternTypeFilter(Pattern.compile(expression)));
} else if ("ASPECTJ".equals(rawName)) {
typeFilters.add(new AspectJTypeFilter(expression, this.resourceLoader
.getClassLoader()));
} else {
throw new IllegalArgumentException("Unknown filter type " + filterType);
}
}
return typeFilters;
}
private String[] getPatterns(AnnotationAttributes filterAttributes) {
try {
return filterAttributes.getStringArray("pattern");
} catch (IllegalArgumentException o_O) {
return new String[0];
}
}
}

View File

@@ -0,0 +1,45 @@
/*
* 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 java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.context.annotation.ComponentScan.Filter;
import org.springframework.context.annotation.Import;
/**
* @author Dave Syer
*
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(GuiceModuleMetadataRegistrar.class)
public @interface GuiceModules {
/**
* Specifies which types are eligible for inclusion in Guice module
*/
Filter[] includeFilters() default {};
/**
* Specifies which types are not eligible for inclusion in Guice module.
*/
Filter[] excludeFilters() default {};
}

View File

@@ -0,0 +1,96 @@
/*
* 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.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan.Filter;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;
import com.google.inject.ConfigurationException;
import com.google.inject.Guice;
import com.google.inject.Injector;
/**
* @author Dave Syer
*
*/
public class GuiceModulesTests {
@Rule
public ExpectedException expected = ExpectedException.none();
@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.getInstance(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
@GuiceModules(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))
protected static class MetadataIncludesConfig {
}
@Configuration
public static class TestConfig {
@Bean
public Service service() {
return new MyService();
}
}
}