Support for candidate components index
This commit adds a "spring-context-indexer" module that can be added to any project in order to generate an index of candidate components defined in the project. `CandidateComponentsIndexer` is a standard annotation processor that looks for source files with target annotations (typically `@Component`) and references them in a `META-INF/spring.components` generated file. Each entry in the index is the fully qualified name of a candidate component and the comma-separated list of stereotypes that apply to that candidate. A typical example of a stereotype is `@Component`. If a project has a `com.example.FooService` annotated with `@Component` the following `META-INF/spring.components` file is generated at compile time: ``` com.example.FooService=org.springframework.stereotype.Component ``` A new `@Indexed` annotation can be added on any annotation to instructs the scanner to include a source file that contains that annotation. For instance, `@Component` is meta-annotated with `@Indexed` now and adding `@Indexed` to more annotation types will transparently improve the index with additional information. This also works for interaces or parent classes: adding `@Indexed` on a `Repository` base interface means that the indexed can be queried for its implementation by using the fully qualified name of the `Repository` interface. The indexer also adds any class or interface that has a type-level annotation from the `javax` package. This includes obviously JPA (`@Entity` and related) but also CDI (`@Named`, `@ManagedBean`) and servlet annotations (i.e. `@WebFilter`). These are meant to handle cases where a component needs to identify candidates and use classpath scanning currently. If a `package-info.java` file exists, the package is registered using a "package-info" stereotype. Such files can later be reused by the `ApplicationContext` to avoid using component scan. A global `CandidateComponentsIndex` can be easily loaded from the current classpath using `CandidateComponentsIndexLoader`. The core framework uses such infrastructure in two areas: to retrieve the candidate `@Component`s and to build a default `PersistenceUnitInfo`. Rather than scanning the classpath and using ASM to identify candidates, the index is used if present. As long as the include filters refer to an annotation that is directly annotated with `@Indexed` or an assignable type that is directly annotated with `@Indexed`, the index can be used since a dedicated entry wil be present for that type. If any other unsupported include filter is specified, we fallback on classpath scanning. In case the index is incomplete or cannot be used, The `spring.index.ignore` system property can be set to `true` or, alternatively, in a "spring.properties" at the root of the classpath. Issue: SPR-11890
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2009 the original author or authors.
|
||||
* Copyright 2002-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.
|
||||
@@ -19,11 +19,13 @@ package example.scannable;
|
||||
import java.util.concurrent.Future;
|
||||
|
||||
import org.springframework.scheduling.annotation.Async;
|
||||
import org.springframework.stereotype.Indexed;
|
||||
|
||||
/**
|
||||
* @author Mark Fisher
|
||||
* @author Juergen Hoeller
|
||||
*/
|
||||
@Indexed
|
||||
public interface FooService {
|
||||
|
||||
String foo(int id);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2010 the original author or authors.
|
||||
* Copyright 2002-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.
|
||||
@@ -24,20 +24,28 @@ import java.util.regex.Pattern;
|
||||
import example.profilescan.DevComponent;
|
||||
import example.profilescan.ProfileAnnotatedComponent;
|
||||
import example.profilescan.ProfileMetaAnnotatedComponent;
|
||||
import example.scannable.AutowiredQualifierFooService;
|
||||
import example.scannable.CustomStereotype;
|
||||
import example.scannable.DefaultNamedComponent;
|
||||
import example.scannable.FooDao;
|
||||
import example.scannable.FooService;
|
||||
import example.scannable.FooServiceImpl;
|
||||
import example.scannable.MessageBean;
|
||||
import example.scannable.NamedComponent;
|
||||
import example.scannable.NamedStubDao;
|
||||
import example.scannable.ScopedProxyTestBean;
|
||||
import example.scannable.ServiceInvocationCounter;
|
||||
import example.scannable.StubFooDao;
|
||||
import org.aspectj.lang.annotation.Aspect;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.beans.factory.annotation.AnnotatedGenericBeanDefinition;
|
||||
import org.springframework.beans.factory.config.BeanDefinition;
|
||||
import org.springframework.context.index.CandidateComponentsTestClassLoader;
|
||||
import org.springframework.core.env.ConfigurableEnvironment;
|
||||
import org.springframework.core.env.StandardEnvironment;
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
import org.springframework.core.io.DefaultResourceLoader;
|
||||
import org.springframework.core.type.filter.AnnotationTypeFilter;
|
||||
import org.springframework.core.type.filter.AssignableTypeFilter;
|
||||
import org.springframework.core.type.filter.RegexPatternTypeFilter;
|
||||
@@ -53,6 +61,7 @@ import static org.junit.Assert.*;
|
||||
* @author Mark Fisher
|
||||
* @author Juergen Hoeller
|
||||
* @author Chris Beams
|
||||
* @author Stephane Nicoll
|
||||
*/
|
||||
public class ClassPathScanningCandidateComponentProviderTests {
|
||||
|
||||
@@ -60,34 +69,192 @@ public class ClassPathScanningCandidateComponentProviderTests {
|
||||
private static final String TEST_PROFILE_PACKAGE = "example.profilescan";
|
||||
private static final String TEST_DEFAULT_PROFILE_NAME = "testDefault";
|
||||
|
||||
private static final ClassLoader TEST_BASE_CLASSLOADER = CandidateComponentsTestClassLoader.index(
|
||||
ClassPathScanningCandidateComponentProviderTests.class.getClassLoader(),
|
||||
new ClassPathResource("spring.components", NamedComponent.class));
|
||||
|
||||
|
||||
@Test
|
||||
public void testWithDefaults() {
|
||||
public void defaultsWithScan() {
|
||||
ClassPathScanningCandidateComponentProvider provider = new ClassPathScanningCandidateComponentProvider(true);
|
||||
provider.setResourceLoader(new DefaultResourceLoader(
|
||||
CandidateComponentsTestClassLoader.disableIndex(getClass().getClassLoader())));
|
||||
testDefault(provider, ScannedGenericBeanDefinition.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void defaultsWithIndex() {
|
||||
ClassPathScanningCandidateComponentProvider provider = new ClassPathScanningCandidateComponentProvider(true);
|
||||
provider.setResourceLoader(new DefaultResourceLoader(TEST_BASE_CLASSLOADER));
|
||||
testDefault(provider, AnnotatedGenericBeanDefinition.class);
|
||||
}
|
||||
|
||||
private void testDefault(ClassPathScanningCandidateComponentProvider provider,
|
||||
Class<? extends BeanDefinition> expectedBeanDefinitionType) {
|
||||
Set<BeanDefinition> candidates = provider.findCandidateComponents(TEST_BASE_PACKAGE);
|
||||
assertEquals(6, candidates.size());
|
||||
assertTrue(containsBeanClass(candidates, DefaultNamedComponent.class));
|
||||
assertTrue(containsBeanClass(candidates, NamedComponent.class));
|
||||
assertTrue(containsBeanClass(candidates, FooServiceImpl.class));
|
||||
assertTrue(containsBeanClass(candidates, StubFooDao.class));
|
||||
assertTrue(containsBeanClass(candidates, NamedStubDao.class));
|
||||
assertTrue(containsBeanClass(candidates, ServiceInvocationCounter.class));
|
||||
assertEquals(6, candidates.size());
|
||||
assertBeanDefinitionType(candidates, expectedBeanDefinitionType);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWithBogusBasePackage() {
|
||||
public void bogusPackageWithScan() {
|
||||
ClassPathScanningCandidateComponentProvider provider = new ClassPathScanningCandidateComponentProvider(true);
|
||||
provider.setResourceLoader(new DefaultResourceLoader(
|
||||
CandidateComponentsTestClassLoader.disableIndex(getClass().getClassLoader())));
|
||||
Set<BeanDefinition> candidates = provider.findCandidateComponents("bogus");
|
||||
assertEquals(0, candidates.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWithPackageExcludeFilter() {
|
||||
public void bogusPackageWithIndex() {
|
||||
ClassPathScanningCandidateComponentProvider provider = new ClassPathScanningCandidateComponentProvider(true);
|
||||
provider.addExcludeFilter(new RegexPatternTypeFilter(Pattern.compile(TEST_BASE_PACKAGE + ".*")));
|
||||
Set<BeanDefinition> candidates = provider.findCandidateComponents(TEST_BASE_PACKAGE);
|
||||
provider.setResourceLoader(new DefaultResourceLoader(TEST_BASE_CLASSLOADER));
|
||||
Set<BeanDefinition> candidates = provider.findCandidateComponents("bogus");
|
||||
assertEquals(0, candidates.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void customFiltersFollowedByResetUseIndex() {
|
||||
ClassPathScanningCandidateComponentProvider provider = new ClassPathScanningCandidateComponentProvider(false);
|
||||
provider.setResourceLoader(new DefaultResourceLoader(TEST_BASE_CLASSLOADER));
|
||||
provider.addIncludeFilter(new AnnotationTypeFilter(Component.class));
|
||||
provider.resetFilters(true);
|
||||
Set<BeanDefinition> candidates = provider.findCandidateComponents(TEST_BASE_PACKAGE);
|
||||
assertBeanDefinitionType(candidates, AnnotatedGenericBeanDefinition.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void customAnnotationTypeIncludeFilterWithScan() {
|
||||
ClassPathScanningCandidateComponentProvider provider = new ClassPathScanningCandidateComponentProvider(false);
|
||||
provider.setResourceLoader(new DefaultResourceLoader(
|
||||
CandidateComponentsTestClassLoader.disableIndex(getClass().getClassLoader())));
|
||||
testCustomAnnotationTypeIncludeFilter(provider, ScannedGenericBeanDefinition.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void customAnnotationTypeIncludeFilterWithIndex() {
|
||||
ClassPathScanningCandidateComponentProvider provider = new ClassPathScanningCandidateComponentProvider(false);
|
||||
provider.setResourceLoader(new DefaultResourceLoader(TEST_BASE_CLASSLOADER));
|
||||
testCustomAnnotationTypeIncludeFilter(provider, AnnotatedGenericBeanDefinition.class);
|
||||
}
|
||||
|
||||
private void testCustomAnnotationTypeIncludeFilter(ClassPathScanningCandidateComponentProvider provider,
|
||||
Class<? extends BeanDefinition> expectedBeanDefinitionType) {
|
||||
provider.addIncludeFilter(new AnnotationTypeFilter(Component.class));
|
||||
testDefault(provider, expectedBeanDefinitionType);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void customAssignableTypeIncludeFilterWithScan() {
|
||||
ClassPathScanningCandidateComponentProvider provider = new ClassPathScanningCandidateComponentProvider(false);
|
||||
provider.setResourceLoader(new DefaultResourceLoader(
|
||||
CandidateComponentsTestClassLoader.disableIndex(getClass().getClassLoader())));
|
||||
testCustomAssignableTypeIncludeFilter(provider, ScannedGenericBeanDefinition.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void customAssignableTypeIncludeFilterWithIndex() {
|
||||
ClassPathScanningCandidateComponentProvider provider = new ClassPathScanningCandidateComponentProvider(false);
|
||||
provider.setResourceLoader(new DefaultResourceLoader(TEST_BASE_CLASSLOADER));
|
||||
testCustomAssignableTypeIncludeFilter(provider, AnnotatedGenericBeanDefinition.class);
|
||||
}
|
||||
|
||||
private void testCustomAssignableTypeIncludeFilter(ClassPathScanningCandidateComponentProvider provider,
|
||||
Class<? extends BeanDefinition> expectedBeanDefinitionType) {
|
||||
provider.addIncludeFilter(new AssignableTypeFilter(FooService.class));
|
||||
Set<BeanDefinition> candidates = provider.findCandidateComponents(TEST_BASE_PACKAGE);
|
||||
// Interfaces/Abstract class are filtered out automatically.
|
||||
assertTrue(containsBeanClass(candidates, AutowiredQualifierFooService.class));
|
||||
assertTrue(containsBeanClass(candidates, FooServiceImpl.class));
|
||||
assertTrue(containsBeanClass(candidates, ScopedProxyTestBean.class));
|
||||
assertEquals(3, candidates.size());
|
||||
assertBeanDefinitionType(candidates, expectedBeanDefinitionType);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void customSupportedIncludeAndExcludedFilterWithScan() {
|
||||
ClassPathScanningCandidateComponentProvider provider = new ClassPathScanningCandidateComponentProvider(false);
|
||||
provider.setResourceLoader(new DefaultResourceLoader(
|
||||
CandidateComponentsTestClassLoader.disableIndex(getClass().getClassLoader())));
|
||||
testCustomSupportedIncludeAndExcludeFilter(provider, ScannedGenericBeanDefinition.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void customSupportedIncludeAndExcludeFilterWithIndex() {
|
||||
ClassPathScanningCandidateComponentProvider provider = new ClassPathScanningCandidateComponentProvider(false);
|
||||
provider.setResourceLoader(new DefaultResourceLoader(TEST_BASE_CLASSLOADER));
|
||||
testCustomSupportedIncludeAndExcludeFilter(provider, AnnotatedGenericBeanDefinition.class);
|
||||
}
|
||||
|
||||
private void testCustomSupportedIncludeAndExcludeFilter(ClassPathScanningCandidateComponentProvider provider,
|
||||
Class<? extends BeanDefinition> expectedBeanDefinitionType) {
|
||||
provider.addIncludeFilter(new AnnotationTypeFilter(Component.class));
|
||||
provider.addExcludeFilter(new AnnotationTypeFilter(Service.class));
|
||||
provider.addExcludeFilter(new AnnotationTypeFilter(Repository.class));
|
||||
Set<BeanDefinition> candidates = provider.findCandidateComponents(TEST_BASE_PACKAGE);
|
||||
assertTrue(containsBeanClass(candidates, NamedComponent.class));
|
||||
assertTrue(containsBeanClass(candidates, ServiceInvocationCounter.class));
|
||||
assertEquals(2, candidates.size());
|
||||
assertBeanDefinitionType(candidates, expectedBeanDefinitionType);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void customSupportIncludeFilterWithNonIndexedTypeUseScan() {
|
||||
ClassPathScanningCandidateComponentProvider provider = new ClassPathScanningCandidateComponentProvider(false);
|
||||
provider.setResourceLoader(new DefaultResourceLoader(TEST_BASE_CLASSLOADER));
|
||||
// This annotation type is not directly annotated with Indexed so we can use
|
||||
// the index to find candidates
|
||||
provider.addIncludeFilter(new AnnotationTypeFilter(CustomStereotype.class));
|
||||
Set<BeanDefinition> candidates = provider.findCandidateComponents(TEST_BASE_PACKAGE);
|
||||
assertTrue(containsBeanClass(candidates, DefaultNamedComponent.class));
|
||||
assertEquals(1, candidates.size());
|
||||
assertBeanDefinitionType(candidates, ScannedGenericBeanDefinition.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void customNotSupportedIncludeFilterUseScan() {
|
||||
ClassPathScanningCandidateComponentProvider provider = new ClassPathScanningCandidateComponentProvider(false);
|
||||
provider.setResourceLoader(new DefaultResourceLoader(TEST_BASE_CLASSLOADER));
|
||||
provider.addIncludeFilter(new AssignableTypeFilter(FooDao.class));
|
||||
Set<BeanDefinition> candidates = provider.findCandidateComponents(TEST_BASE_PACKAGE);
|
||||
assertTrue(containsBeanClass(candidates, StubFooDao.class));
|
||||
assertEquals(1, candidates.size());
|
||||
assertBeanDefinitionType(candidates, ScannedGenericBeanDefinition.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void excludeFilterWithScan() {
|
||||
ClassPathScanningCandidateComponentProvider provider = new ClassPathScanningCandidateComponentProvider(true);
|
||||
provider.setResourceLoader(new DefaultResourceLoader(
|
||||
CandidateComponentsTestClassLoader.disableIndex(getClass().getClassLoader())));
|
||||
provider.addExcludeFilter(new RegexPatternTypeFilter(Pattern.compile(TEST_BASE_PACKAGE + ".*Named.*")));
|
||||
testExclude(provider, ScannedGenericBeanDefinition.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void excludeFilterWithIndex() {
|
||||
ClassPathScanningCandidateComponentProvider provider = new ClassPathScanningCandidateComponentProvider(true);
|
||||
provider.setResourceLoader(new DefaultResourceLoader(TEST_BASE_CLASSLOADER));
|
||||
provider.addExcludeFilter(new RegexPatternTypeFilter(Pattern.compile(TEST_BASE_PACKAGE + ".*Named.*")));
|
||||
testExclude(provider, AnnotatedGenericBeanDefinition.class);
|
||||
}
|
||||
|
||||
private void testExclude(ClassPathScanningCandidateComponentProvider provider,
|
||||
Class<? extends BeanDefinition> expectedBeanDefinitionType) {
|
||||
Set<BeanDefinition> candidates = provider.findCandidateComponents(TEST_BASE_PACKAGE);
|
||||
assertTrue(containsBeanClass(candidates, FooServiceImpl.class));
|
||||
assertTrue(containsBeanClass(candidates, StubFooDao.class));
|
||||
assertTrue(containsBeanClass(candidates, ServiceInvocationCounter.class));
|
||||
assertEquals(3, candidates.size());
|
||||
assertBeanDefinitionType(candidates, expectedBeanDefinitionType);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWithNoFilters() {
|
||||
ClassPathScanningCandidateComponentProvider provider = new ClassPathScanningCandidateComponentProvider(false);
|
||||
@@ -306,14 +473,20 @@ public class ClassPathScanningCandidateComponentProviderTests {
|
||||
|
||||
private boolean containsBeanClass(Set<BeanDefinition> candidates, Class<?> beanClass) {
|
||||
for (BeanDefinition candidate : candidates) {
|
||||
ScannedGenericBeanDefinition definition = (ScannedGenericBeanDefinition) candidate;
|
||||
if (beanClass.getName().equals(definition.getBeanClassName())) {
|
||||
if (beanClass.getName().equals(candidate.getBeanClassName())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void assertBeanDefinitionType(Set<BeanDefinition> candidates,
|
||||
Class<? extends BeanDefinition> expectedType) {
|
||||
candidates.forEach(c -> {
|
||||
assertThat(c, is(instanceOf(expectedType)));
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@Profile(TEST_DEFAULT_PROFILE_NAME)
|
||||
@Component(DefaultProfileAnnotatedComponent.BEAN_NAME)
|
||||
|
||||
@@ -0,0 +1,111 @@
|
||||
/*
|
||||
* Copyright 2002-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.context.index;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Set;
|
||||
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.ExpectedException;
|
||||
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
|
||||
import static org.hamcrest.Matchers.*;
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
/**
|
||||
* Tests for {@link CandidateComponentsIndexLoader}.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
*/
|
||||
public class CandidateComponentsIndexLoaderTests {
|
||||
|
||||
@Rule
|
||||
public final ExpectedException thrown = ExpectedException.none();
|
||||
|
||||
@Test
|
||||
public void validateIndexIsDisabledByDefault() {
|
||||
CandidateComponentsIndex index = CandidateComponentsIndexLoader.loadIndex(null);
|
||||
assertThat("No spring.components should be available at the default location", index, is(nullValue()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void loadIndexSeveralMatches() {
|
||||
CandidateComponentsIndex index = CandidateComponentsIndexLoader.loadIndex(
|
||||
CandidateComponentsTestClassLoader.index(getClass().getClassLoader(),
|
||||
new ClassPathResource("spring.components", getClass())));
|
||||
Set<String> components = index.getCandidateTypes("org.springframework", "foo");
|
||||
assertThat(components, containsInAnyOrder(
|
||||
"org.springframework.context.index.Sample1",
|
||||
"org.springframework.context.index.Sample2"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void loadIndexSingleMatch() {
|
||||
CandidateComponentsIndex index = CandidateComponentsIndexLoader.loadIndex(
|
||||
CandidateComponentsTestClassLoader.index(getClass().getClassLoader(),
|
||||
new ClassPathResource("spring.components", getClass())));
|
||||
Set<String> components = index.getCandidateTypes("org.springframework", "biz");
|
||||
assertThat(components, containsInAnyOrder(
|
||||
"org.springframework.context.index.Sample3"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void loadIndexNoMatch() {
|
||||
CandidateComponentsIndex index = CandidateComponentsIndexLoader.loadIndex(
|
||||
CandidateComponentsTestClassLoader.index(getClass().getClassLoader(),
|
||||
new ClassPathResource("spring.components", getClass())));
|
||||
Set<String> components = index.getCandidateTypes("org.springframework", "none");
|
||||
assertThat(components, hasSize(0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void loadIndexNoPackage() {
|
||||
CandidateComponentsIndex index = CandidateComponentsIndexLoader.loadIndex(
|
||||
CandidateComponentsTestClassLoader.index(getClass().getClassLoader(),
|
||||
new ClassPathResource("spring.components", getClass())));
|
||||
Set<String> components = index.getCandidateTypes("com.example", "foo");
|
||||
assertThat(components, hasSize(0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void loadIndexNoSpringComponentsResource() {
|
||||
CandidateComponentsIndex index = CandidateComponentsIndexLoader.loadIndex(
|
||||
CandidateComponentsTestClassLoader.disableIndex(getClass().getClassLoader()));
|
||||
assertThat(index, is(nullValue()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void loadIndexNoEntry() throws IOException {
|
||||
CandidateComponentsIndex index = CandidateComponentsIndexLoader.loadIndex(
|
||||
CandidateComponentsTestClassLoader.index(getClass().getClassLoader(),
|
||||
new ClassPathResource("empty-spring.components", getClass())));
|
||||
assertThat(index, is(nullValue()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void loadIndexWithException() throws IOException {
|
||||
final IOException cause = new IOException("test exception");
|
||||
this.thrown.expect(IllegalArgumentException.class);
|
||||
this.thrown.expectMessage("Unable to load indexes");
|
||||
this.thrown.expectCause(is(cause));
|
||||
CandidateComponentsIndexLoader.loadIndex(new CandidateComponentsTestClassLoader(
|
||||
getClass().getClassLoader(), cause));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
/*
|
||||
* Copyright 2002-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.context.index;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Properties;
|
||||
import java.util.Set;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.hamcrest.Matchers.*;
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
/**
|
||||
* Tests for {@link CandidateComponentsIndex}.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
*/
|
||||
public class CandidateComponentsIndexTests {
|
||||
|
||||
@Test
|
||||
public void getCandidateTypes() {
|
||||
CandidateComponentsIndex index = new CandidateComponentsIndex(
|
||||
Collections.singletonList(createSampleProperties()));
|
||||
Set<String> actual = index.getCandidateTypes("com.example.service", "service");
|
||||
assertThat(actual, containsInAnyOrder("com.example.service.One",
|
||||
"com.example.service.sub.Two", "com.example.service.Three"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getCandidateTypesSubPackage() {
|
||||
CandidateComponentsIndex index = new CandidateComponentsIndex(
|
||||
Collections.singletonList(createSampleProperties()));
|
||||
Set<String> actual = index.getCandidateTypes("com.example.service.sub", "service");
|
||||
assertThat(actual, containsInAnyOrder("com.example.service.sub.Two"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getCandidateTypesSubPackageNoMatch() {
|
||||
CandidateComponentsIndex index = new CandidateComponentsIndex(
|
||||
Collections.singletonList(createSampleProperties()));
|
||||
Set<String> actual = index.getCandidateTypes("com.example.service.none", "service");
|
||||
assertThat(actual, hasSize(0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getCandidateTypesNoMatch() {
|
||||
CandidateComponentsIndex index = new CandidateComponentsIndex(
|
||||
Collections.singletonList(createSampleProperties()));
|
||||
Set<String> actual = index.getCandidateTypes("com.example.service", "entity");
|
||||
assertThat(actual, hasSize(0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void mergeCandidateStereotypes() {
|
||||
CandidateComponentsIndex index = new CandidateComponentsIndex(Arrays.asList(
|
||||
createProperties("com.example.Foo", "service"),
|
||||
createProperties("com.example.Foo", "entity")));
|
||||
assertThat(index.getCandidateTypes("com.example", "service"),
|
||||
contains("com.example.Foo"));
|
||||
assertThat(index.getCandidateTypes("com.example", "entity"),
|
||||
contains("com.example.Foo"));
|
||||
}
|
||||
|
||||
private static Properties createProperties(String key, String stereotypes) {
|
||||
Properties properties = new Properties();
|
||||
properties.put(key, String.join(",", stereotypes));
|
||||
return properties;
|
||||
}
|
||||
|
||||
private static Properties createSampleProperties() {
|
||||
Properties properties = new Properties();
|
||||
properties.put("com.example.service.One", "service");
|
||||
properties.put("com.example.service.sub.Two", "service");
|
||||
properties.put("com.example.service.Three", "service");
|
||||
properties.put("com.example.domain.Four", "entity");
|
||||
return properties;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
/*
|
||||
* Copyright 2002-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.context.index;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.util.Collections;
|
||||
import java.util.Enumeration;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.springframework.core.io.Resource;
|
||||
|
||||
/**
|
||||
* A test {@link ClassLoader} that can be used in testing context to control the
|
||||
* {@code spring.components} resource that should be loaded. Can also simulate a failure
|
||||
* by throwing a configurable {@link IOException}.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
*/
|
||||
public class CandidateComponentsTestClassLoader extends ClassLoader {
|
||||
|
||||
/**
|
||||
* Create a test {@link ClassLoader} that disable the use of the index, even
|
||||
* if resources are present at the standard location.
|
||||
* @param classLoader the classloader to use for all other operations
|
||||
* @return a test {@link ClassLoader} that has no index
|
||||
* @see CandidateComponentsIndexLoader#COMPONENTS_RESOURCE_LOCATION
|
||||
*/
|
||||
public static ClassLoader disableIndex(ClassLoader classLoader) {
|
||||
return new CandidateComponentsTestClassLoader(classLoader,
|
||||
Collections.enumeration(Collections.emptyList()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a test {@link ClassLoader} that creates an index with the
|
||||
* specifed {@link Resource} instances
|
||||
* @param classLoader the classloader to use for all other operations
|
||||
* @return a test {@link ClassLoader} with an index built based on the
|
||||
* specified resources.
|
||||
*/
|
||||
public static ClassLoader index(ClassLoader classLoader, Resource... resources) {
|
||||
return new CandidateComponentsTestClassLoader(classLoader,
|
||||
Collections.enumeration(Stream.of(resources).map(r -> {
|
||||
try {
|
||||
return r.getURL();
|
||||
}
|
||||
catch (Exception ex) {
|
||||
throw new IllegalArgumentException("Invalid resource " + r, ex);
|
||||
}
|
||||
}).collect(Collectors.toList())));
|
||||
}
|
||||
|
||||
|
||||
private final Enumeration<URL> resourceUrls;
|
||||
|
||||
private final IOException cause;
|
||||
|
||||
public CandidateComponentsTestClassLoader(ClassLoader classLoader, Enumeration<URL> resourceUrls) {
|
||||
super(classLoader);
|
||||
this.resourceUrls = resourceUrls;
|
||||
this.cause = null;
|
||||
}
|
||||
|
||||
public CandidateComponentsTestClassLoader(ClassLoader parent, IOException cause) {
|
||||
super(parent);
|
||||
this.resourceUrls = null;
|
||||
this.cause = cause;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Enumeration<URL> getResources(String name) throws IOException {
|
||||
if (CandidateComponentsIndexLoader.COMPONENTS_RESOURCE_LOCATION.equals(name)) {
|
||||
if (this.resourceUrls != null) {
|
||||
return this.resourceUrls;
|
||||
}
|
||||
throw this.cause;
|
||||
}
|
||||
return super.getResources(name);
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user