Fix TypeExcludeFiltersContextCustomer key

Update `TypeExcludeFiltersContextCustomer` to use filter instances
as part of the key, rather than class references. In order to be used
in tests, `TypeExcludeFilter` implementations must now implement valid
`hashCode` and `equals` methods.

Fixes gh-8125
pull/8206/head
Phillip Webb 8 years ago
parent 180ab2da8b
commit aaf118c544

@ -16,7 +16,6 @@
package org.springframework.boot.test.autoconfigure.data.mongo;
import java.io.IOException;
import java.util.Collections;
import java.util.Set;
@ -24,8 +23,6 @@ import org.springframework.boot.context.TypeExcludeFilter;
import org.springframework.boot.test.autoconfigure.filter.AnnotationCustomizableTypeExcludeFilter;
import org.springframework.context.annotation.ComponentScan.Filter;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.core.type.classreading.MetadataReaderFactory;
/**
* {@link TypeExcludeFilter} for {@link DataMongoTest @DataMongoTest}.
@ -64,13 +61,12 @@ class DataMongoTypeExcludeFilter extends AnnotationCustomizableTypeExcludeFilter
}
@Override
protected boolean defaultInclude(final MetadataReader metadataReader,
final MetadataReaderFactory metadataReaderFactory) throws IOException {
return false;
protected Set<Class<?>> getDefaultIncludes() {
return Collections.emptySet();
}
@Override
protected Set<Class<?>> getDefaultIncludes() {
protected Set<Class<?>> getComponentIncludes() {
return Collections.emptySet();
}

@ -27,6 +27,7 @@ import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.core.type.classreading.MetadataReaderFactory;
import org.springframework.core.type.filter.AnnotationTypeFilter;
import org.springframework.core.type.filter.AssignableTypeFilter;
import org.springframework.util.ObjectUtils;
/**
* Abstract base class for a {@link TypeExcludeFilter} that can be customized using an
@ -75,6 +76,11 @@ public abstract class AnnotationCustomizableTypeExcludeFilter extends TypeExclud
return true;
}
}
for (Class<?> component : getComponentIncludes()) {
if (isTypeOrAnnotated(metadataReader, metadataReaderFactory, component)) {
return true;
}
}
return false;
}
@ -103,10 +109,50 @@ public abstract class AnnotationCustomizableTypeExcludeFilter extends TypeExclud
protected abstract Set<Class<?>> getDefaultIncludes();
protected abstract Set<Class<?>> getComponentIncludes();
protected enum FilterType {
INCLUDE, EXCLUDE
}
@Override
public int hashCode() {
final int prime = 31;
int result = 0;
result = prime * result + ObjectUtils.hashCode(hasAnnotation());
for (FilterType filterType : FilterType.values()) {
result = prime * result
+ ObjectUtils.nullSafeHashCode(getFilters(filterType));
}
result = prime * result + ObjectUtils.hashCode(isUseDefaultFilters());
result = prime * result + ObjectUtils.nullSafeHashCode(getDefaultIncludes());
result = prime * result + ObjectUtils.nullSafeHashCode(getComponentIncludes());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (getClass() != obj.getClass()) {
return false;
}
AnnotationCustomizableTypeExcludeFilter other = (AnnotationCustomizableTypeExcludeFilter) obj;
boolean result = true;
result &= hasAnnotation() == other.hasAnnotation();
for (FilterType filterType : FilterType.values()) {
result &= ObjectUtils.nullSafeEquals(getFilters(filterType),
other.getFilters(filterType));
}
result &= isUseDefaultFilters() == other.isUseDefaultFilters();
result &= ObjectUtils.nullSafeEquals(getDefaultIncludes(),
other.getDefaultIncludes());
result &= ObjectUtils.nullSafeEquals(getComponentIncludes(),
other.getComponentIncludes());
return result;
};
}

@ -18,6 +18,7 @@ package org.springframework.boot.test.autoconfigure.filter;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.Set;
@ -40,48 +41,65 @@ class TypeExcludeFiltersContextCustomizer implements ContextCustomizer {
private static final String EXCLUDE_FILTER_BEAN_NAME = TypeExcludeFilters.class
.getName();
private final Class<?> testClass;
private final Set<Class<? extends TypeExcludeFilter>> filterClasses;
private final Set<TypeExcludeFilter> filters;
TypeExcludeFiltersContextCustomizer(Class<?> testClass,
Set<Class<? extends TypeExcludeFilter>> filterClasses) {
this.testClass = testClass;
this.filterClasses = filterClasses;
this.filters = instantiateTypeExcludeFilters(testClass, filterClasses);
}
private Set<TypeExcludeFilter> instantiateTypeExcludeFilters(Class<?> testClass,
Set<Class<? extends TypeExcludeFilter>> filterClasses) {
Set<TypeExcludeFilter> filters = new LinkedHashSet<TypeExcludeFilter>();
for (Class<? extends TypeExcludeFilter> filterClass : filterClasses) {
filters.add(instantiateTypeExcludeFilter(testClass, filterClass));
}
return Collections.unmodifiableSet(filters);
}
private TypeExcludeFilter instantiateTypeExcludeFilter(Class<?> testClass,
Class<?> filterClass) {
try {
Constructor<?> constructor = getTypeExcludeFilterConstructor(filterClass);
ReflectionUtils.makeAccessible(constructor);
if (constructor.getParameterTypes().length == 1) {
return (TypeExcludeFilter) constructor.newInstance(testClass);
}
return (TypeExcludeFilter) constructor.newInstance();
}
catch (Exception ex) {
throw new IllegalStateException("Unable to create filter for " + filterClass,
ex);
}
}
@Override
public int hashCode() {
return this.filterClasses.hashCode();
return this.filters.hashCode();
}
@Override
public boolean equals(Object obj) {
return (obj != null && getClass().equals(obj.getClass()) && this.filterClasses
.equals(((TypeExcludeFiltersContextCustomizer) obj).filterClasses));
return (obj != null && getClass().equals(obj.getClass()) && this.filters
.equals(((TypeExcludeFiltersContextCustomizer) obj).filters));
}
@Override
public void customizeContext(ConfigurableApplicationContext context,
MergedContextConfiguration mergedContextConfiguration) {
if (!this.filterClasses.isEmpty()) {
if (!this.filters.isEmpty()) {
context.getBeanFactory().registerSingleton(EXCLUDE_FILTER_BEAN_NAME,
createDelegatingTypeExcludeFilter());
}
}
private TypeExcludeFilter createDelegatingTypeExcludeFilter() {
final Set<TypeExcludeFilter> filters = new LinkedHashSet<TypeExcludeFilter>(
this.filterClasses.size());
for (Class<? extends TypeExcludeFilter> filterClass : this.filterClasses) {
filters.add(createTypeExcludeFilter(filterClass));
}
return new TypeExcludeFilter() {
@Override
public boolean match(MetadataReader metadataReader,
MetadataReaderFactory metadataReaderFactory) throws IOException {
for (TypeExcludeFilter filter : filters) {
for (TypeExcludeFilter filter : TypeExcludeFiltersContextCustomizer.this.filters) {
if (filter.match(metadataReader, metadataReaderFactory)) {
return true;
}
@ -92,20 +110,6 @@ class TypeExcludeFiltersContextCustomizer implements ContextCustomizer {
};
}
private TypeExcludeFilter createTypeExcludeFilter(Class<?> type) {
try {
Constructor<?> constructor = getTypeExcludeFilterConstructor(type);
ReflectionUtils.makeAccessible(constructor);
if (constructor.getParameterTypes().length == 1) {
return (TypeExcludeFilter) constructor.newInstance(this.testClass);
}
return (TypeExcludeFilter) constructor.newInstance();
}
catch (Exception ex) {
throw new IllegalStateException("Unable to create filter for " + type, ex);
}
}
private Constructor<?> getTypeExcludeFilterConstructor(Class<?> type)
throws NoSuchMethodException {
try {

@ -16,7 +16,6 @@
package org.springframework.boot.test.autoconfigure.jdbc;
import java.io.IOException;
import java.util.Collections;
import java.util.Set;
@ -24,8 +23,6 @@ import org.springframework.boot.context.TypeExcludeFilter;
import org.springframework.boot.test.autoconfigure.filter.AnnotationCustomizableTypeExcludeFilter;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.core.type.classreading.MetadataReaderFactory;
/**
* {@link TypeExcludeFilter} for {@link JdbcTest @JdbcTest}.
@ -63,13 +60,12 @@ class JdbcTypeExcludeFilter extends AnnotationCustomizableTypeExcludeFilter {
}
@Override
protected boolean defaultInclude(MetadataReader metadataReader,
MetadataReaderFactory metadataReaderFactory) throws IOException {
return false;
protected Set<Class<?>> getDefaultIncludes() {
return Collections.emptySet();
}
@Override
protected Set<Class<?>> getDefaultIncludes() {
protected Set<Class<?>> getComponentIncludes() {
return Collections.emptySet();
}

@ -77,4 +77,9 @@ class JsonExcludeFilter extends AnnotationCustomizableTypeExcludeFilter {
return DEFAULT_INCLUDES;
}
@Override
protected Set<Class<?>> getComponentIncludes() {
return Collections.emptySet();
}
}

@ -16,7 +16,6 @@
package org.springframework.boot.test.autoconfigure.orm.jpa;
import java.io.IOException;
import java.util.Collections;
import java.util.Set;
@ -24,8 +23,6 @@ import org.springframework.boot.context.TypeExcludeFilter;
import org.springframework.boot.test.autoconfigure.filter.AnnotationCustomizableTypeExcludeFilter;
import org.springframework.context.annotation.ComponentScan.Filter;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.core.type.classreading.MetadataReaderFactory;
/**
* {@link TypeExcludeFilter} for {@link DataJpaTest @DataJpaTest}.
@ -63,13 +60,12 @@ class DataJpaTypeExcludeFilter extends AnnotationCustomizableTypeExcludeFilter {
}
@Override
protected boolean defaultInclude(MetadataReader metadataReader,
MetadataReaderFactory metadataReaderFactory) throws IOException {
return false;
protected Set<Class<?>> getDefaultIncludes() {
return Collections.emptySet();
}
@Override
protected Set<Class<?>> getDefaultIncludes() {
protected Set<Class<?>> getComponentIncludes() {
return Collections.emptySet();
}

@ -16,7 +16,7 @@
package org.springframework.boot.test.autoconfigure.web.client;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.Set;
@ -26,8 +26,6 @@ import org.springframework.boot.jackson.JsonComponent;
import org.springframework.boot.test.autoconfigure.filter.AnnotationCustomizableTypeExcludeFilter;
import org.springframework.context.annotation.ComponentScan.Filter;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.core.type.classreading.MetadataReaderFactory;
import org.springframework.util.ClassUtils;
/**
@ -65,20 +63,6 @@ class RestClientExcludeFilter extends AnnotationCustomizableTypeExcludeFilter {
RestClientTest.class);
}
@Override
protected boolean defaultInclude(MetadataReader metadataReader,
MetadataReaderFactory metadataReaderFactory) throws IOException {
if (super.defaultInclude(metadataReader, metadataReaderFactory)) {
return true;
}
for (Class<?> controller : this.annotation.components()) {
if (isTypeOrAnnotated(metadataReader, metadataReaderFactory, controller)) {
return true;
}
}
return false;
}
@Override
protected boolean hasAnnotation() {
return this.annotation != null;
@ -105,4 +89,9 @@ class RestClientExcludeFilter extends AnnotationCustomizableTypeExcludeFilter {
return DEFAULT_INCLUDES;
}
@Override
protected Set<Class<?>> getComponentIncludes() {
return new LinkedHashSet<Class<?>>(Arrays.asList(this.annotation.components()));
}
}

@ -16,7 +16,7 @@
package org.springframework.boot.test.autoconfigure.web.servlet;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.Set;
@ -29,8 +29,6 @@ import org.springframework.boot.web.servlet.DelegatingFilterProxyRegistrationBea
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.ComponentScan.Filter;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.core.type.classreading.MetadataReaderFactory;
import org.springframework.stereotype.Controller;
import org.springframework.util.ObjectUtils;
import org.springframework.web.bind.annotation.ControllerAdvice;
@ -74,20 +72,6 @@ class WebMvcTypeExcludeFilter extends AnnotationCustomizableTypeExcludeFilter {
WebMvcTest.class);
}
@Override
protected boolean defaultInclude(MetadataReader metadataReader,
MetadataReaderFactory metadataReaderFactory) throws IOException {
if (super.defaultInclude(metadataReader, metadataReaderFactory)) {
return true;
}
for (Class<?> controller : this.annotation.controllers()) {
if (isTypeOrAnnotated(metadataReader, metadataReaderFactory, controller)) {
return true;
}
}
return false;
}
@Override
protected boolean hasAnnotation() {
return this.annotation != null;
@ -117,4 +101,9 @@ class WebMvcTypeExcludeFilter extends AnnotationCustomizableTypeExcludeFilter {
return DEFAULT_INCLUDES;
}
@Override
protected Set<Class<?>> getComponentIncludes() {
return new LinkedHashSet<Class<?>>(Arrays.asList(this.annotation.controllers()));
}
}

@ -122,12 +122,22 @@ public class TypeExcludeFiltersContextCustomizerFactoryTests {
.equals(getClass().getName());
}
@Override
public int hashCode() {
return SimpleExclude.class.hashCode();
}
@Override
public boolean equals(Object obj) {
return obj.getClass().equals(getClass());
}
}
static class TestClassAwareExclude extends SimpleExclude {
TestClassAwareExclude(Class<?> testClass) {
assertThat(testClass).isEqualTo(WithExcludeFilters.class);
assertThat(testClass).isNotNull();
}
}

@ -36,7 +36,9 @@ import org.springframework.core.type.filter.TypeFilter;
* </pre>
* <p>
* Implementations should provide a subclass registered with {@link BeanFactory} and
* override the {@link #match(MetadataReader, MetadataReaderFactory)} method.
* override the {@link #match(MetadataReader, MetadataReaderFactory)} method. They should
* also implement a valid {@link #hashCode() hashCode} and {@link #equals(Object) equals}
* methods so that they can be used as part of Spring test's application context caches.
* <p>
* Note that {@code TypeExcludeFilters} are initialized very early in the application
* lifecycle, they should generally not have dependencies on any other beans. They are
@ -70,4 +72,16 @@ public class TypeExcludeFilter implements TypeFilter, BeanFactoryAware {
return false;
}
@Override
public boolean equals(Object obj) {
throw new IllegalStateException(
"TypeExcludeFilter " + getClass() + " has not implemented equals");
}
@Override
public int hashCode() {
throw new IllegalStateException(
"TypeExcludeFilter " + getClass() + " has not implemented hashCode");
}
}

Loading…
Cancel
Save