From 513dec718fd3e7449ec76b6a916f4696d1942d5d Mon Sep 17 00:00:00 2001 From: Phillip Webb Date: Tue, 1 Mar 2016 15:35:45 -0800 Subject: [PATCH] Add TestExcludeFilter for component scanning Add a new TypeFilter specifically for excluding candidate components. The filter is applied to `@SpringBootApplication` and allows tests to dynamically contribute exclude filters so that specific classes of component can be excluded. See gh-5295 See gh-4901 --- .../autoconfigure/SpringBootApplication.java | 5 +- .../boot/context/TypeExcludeFilter.java | 73 +++++++++++++++++++ .../boot/context/TypeExcludeFilterTests.java | 70 ++++++++++++++++++ .../filtersample/ExampleComponent.java | 24 ++++++ .../ExampleFilteredComponent.java | 24 ++++++ .../filtersample/SampleTypeExcludeFilter.java | 34 +++++++++ 6 files changed, 229 insertions(+), 1 deletion(-) create mode 100644 spring-boot/src/main/java/org/springframework/boot/context/TypeExcludeFilter.java create mode 100644 spring-boot/src/test/java/org/springframework/boot/context/TypeExcludeFilterTests.java create mode 100644 spring-boot/src/test/java/org/springframework/boot/context/filtersample/ExampleComponent.java create mode 100644 spring-boot/src/test/java/org/springframework/boot/context/filtersample/ExampleFilteredComponent.java create mode 100644 spring-boot/src/test/java/org/springframework/boot/context/filtersample/SampleTypeExcludeFilter.java diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/SpringBootApplication.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/SpringBootApplication.java index 9f6a2cd0b3..20052873c6 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/SpringBootApplication.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/SpringBootApplication.java @@ -24,9 +24,12 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import org.springframework.boot.SpringBootConfiguration; +import org.springframework.boot.context.TypeExcludeFilter; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.ComponentScan.Filter; import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.FilterType; import org.springframework.core.annotation.AliasFor; /** @@ -46,7 +49,7 @@ import org.springframework.core.annotation.AliasFor; @Inherited @SpringBootConfiguration @EnableAutoConfiguration -@ComponentScan +@ComponentScan(excludeFilters = @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class)) public @interface SpringBootApplication { /** diff --git a/spring-boot/src/main/java/org/springframework/boot/context/TypeExcludeFilter.java b/spring-boot/src/main/java/org/springframework/boot/context/TypeExcludeFilter.java new file mode 100644 index 0000000000..a1b2ecac0d --- /dev/null +++ b/spring-boot/src/main/java/org/springframework/boot/context/TypeExcludeFilter.java @@ -0,0 +1,73 @@ +/* + * Copyright 2012-2016 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.boot.context; + +import java.io.IOException; +import java.util.Collection; + +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.BeanFactoryAware; +import org.springframework.beans.factory.ListableBeanFactory; +import org.springframework.core.type.classreading.MetadataReader; +import org.springframework.core.type.classreading.MetadataReaderFactory; +import org.springframework.core.type.filter.TypeFilter; + +/** + * Provides exclusion {@link TypeFilter TypeFilters} that are loaded from the + * {@link BeanFactory} and automatically applied to {@code SpringBootApplication} + * scanning. Can also be used directly with {@code @ComponentScan} as follows: + *
+ * @ComponentScan(excludeFilters = @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class))
+ * 
+ *

+ * Implementations should provide a subclass registered with {@link BeanFactory} and + * override the {@link #match(MetadataReader, MetadataReaderFactory)} method. + *

+ * Note that {@code TypeExcludeFilters} are initialized very early in the application + * lifecyle, they should generally not have dependencies on any other beans. They and are + * primarily used internally to support {@code spring-boot-test}. + * + * @author Phillip Webb + * @since 1.4.0 + */ +public class TypeExcludeFilter implements TypeFilter, BeanFactoryAware { + + private BeanFactory beanFactory; + + @Override + public void setBeanFactory(BeanFactory beanFactory) throws BeansException { + this.beanFactory = beanFactory; + } + + @Override + public boolean match(MetadataReader metadataReader, + MetadataReaderFactory metadataReaderFactory) throws IOException { + if (this.beanFactory instanceof ListableBeanFactory + && getClass().equals(TypeExcludeFilter.class)) { + Collection delegates = ((ListableBeanFactory) this.beanFactory) + .getBeansOfType(TypeExcludeFilter.class).values(); + for (TypeExcludeFilter delegate : delegates) { + if (delegate.match(metadataReader, metadataReaderFactory)) { + return true; + } + } + } + return false; + } + +} diff --git a/spring-boot/src/test/java/org/springframework/boot/context/TypeExcludeFilterTests.java b/spring-boot/src/test/java/org/springframework/boot/context/TypeExcludeFilterTests.java new file mode 100644 index 0000000000..abb82ecbbb --- /dev/null +++ b/spring-boot/src/test/java/org/springframework/boot/context/TypeExcludeFilterTests.java @@ -0,0 +1,70 @@ +/* + * Copyright 2012-2016 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.boot.context; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +import org.springframework.beans.factory.NoSuchBeanDefinitionException; +import org.springframework.boot.context.filtersample.ExampleComponent; +import org.springframework.boot.context.filtersample.ExampleFilteredComponent; +import org.springframework.boot.context.filtersample.SampleTypeExcludeFilter; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.ComponentScan.Filter; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.FilterType; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link TypeExcludeFilter}. + * + * @author Phillip Webb + */ +public class TypeExcludeFilterTests { + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + @Test + public void loadsTypeExcludeFilters() throws Exception { + AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); + context.getBeanFactory().registerSingleton("filter1", + new WithoutMatchOverrideFilter()); + context.getBeanFactory().registerSingleton("filter2", + new SampleTypeExcludeFilter()); + context.register(Config.class); + context.refresh(); + assertThat(context.getBean(ExampleComponent.class)).isNotNull(); + this.thrown.expect(NoSuchBeanDefinitionException.class); + context.getBean(ExampleFilteredComponent.class); + context.close(); + } + + @Configuration + @ComponentScan(basePackageClasses = SampleTypeExcludeFilter.class, excludeFilters = @Filter(type = FilterType.CUSTOM, classes = SampleTypeExcludeFilter.class)) + static class Config { + + } + + static class WithoutMatchOverrideFilter extends TypeExcludeFilter { + + } + +} diff --git a/spring-boot/src/test/java/org/springframework/boot/context/filtersample/ExampleComponent.java b/spring-boot/src/test/java/org/springframework/boot/context/filtersample/ExampleComponent.java new file mode 100644 index 0000000000..94640d0811 --- /dev/null +++ b/spring-boot/src/test/java/org/springframework/boot/context/filtersample/ExampleComponent.java @@ -0,0 +1,24 @@ +/* + * Copyright 2012-2016 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.boot.context.filtersample; + +import org.springframework.stereotype.Component; + +@Component +public class ExampleComponent { + +} diff --git a/spring-boot/src/test/java/org/springframework/boot/context/filtersample/ExampleFilteredComponent.java b/spring-boot/src/test/java/org/springframework/boot/context/filtersample/ExampleFilteredComponent.java new file mode 100644 index 0000000000..e41338c931 --- /dev/null +++ b/spring-boot/src/test/java/org/springframework/boot/context/filtersample/ExampleFilteredComponent.java @@ -0,0 +1,24 @@ +/* + * Copyright 2012-2016 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.boot.context.filtersample; + +import org.springframework.stereotype.Component; + +@Component +public class ExampleFilteredComponent { + +} diff --git a/spring-boot/src/test/java/org/springframework/boot/context/filtersample/SampleTypeExcludeFilter.java b/spring-boot/src/test/java/org/springframework/boot/context/filtersample/SampleTypeExcludeFilter.java new file mode 100644 index 0000000000..87ffabdf82 --- /dev/null +++ b/spring-boot/src/test/java/org/springframework/boot/context/filtersample/SampleTypeExcludeFilter.java @@ -0,0 +1,34 @@ +/* + * Copyright 2012-2016 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.boot.context.filtersample; + +import java.io.IOException; + +import org.springframework.boot.context.TypeExcludeFilter; +import org.springframework.core.type.classreading.MetadataReader; +import org.springframework.core.type.classreading.MetadataReaderFactory; + +public class SampleTypeExcludeFilter extends TypeExcludeFilter { + + @Override + public boolean match(MetadataReader metadataReader, + MetadataReaderFactory metadataReaderFactory) throws IOException { + return metadataReader.getClassMetadata().getClassName() + .equals(ExampleFilteredComponent.class.getName()); + } + +}