From ce169c4d51bedfaebd1db2795f4770e646d08b43 Mon Sep 17 00:00:00 2001 From: Stephane Nicoll Date: Tue, 1 Sep 2020 10:00:41 +0200 Subject: [PATCH] Allow to customize how EntityScanner scans entities This commit adds a protected method that lets an override customize the configuration of the ClassPathScanningCandidateComponentProvider used to scan entities. Closes gh-23154 --- .../autoconfigure/domain/EntityScanner.java | 23 ++++++++-- .../domain/EntityScannerTests.java | 46 ++++++++++++++++++- 2 files changed, 64 insertions(+), 5 deletions(-) diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/domain/EntityScanner.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/domain/EntityScanner.java index 99bab3b6a6..4fb4efd0a9 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/domain/EntityScanner.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/domain/EntityScanner.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2019 the original author or authors. + * Copyright 2012-2020 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. @@ -63,9 +63,8 @@ public class EntityScanner { if (packages.isEmpty()) { return Collections.emptySet(); } - ClassPathScanningCandidateComponentProvider scanner = new ClassPathScanningCandidateComponentProvider(false); - scanner.setEnvironment(this.context.getEnvironment()); - scanner.setResourceLoader(this.context); + ClassPathScanningCandidateComponentProvider scanner = createClassPathScanningCandidateComponentProvider( + this.context); for (Class annotationType : annotationTypes) { scanner.addIncludeFilter(new AnnotationTypeFilter(annotationType)); } @@ -80,6 +79,22 @@ public class EntityScanner { return entitySet; } + /** + * Create a {@link ClassPathScanningCandidateComponentProvider} to scan entities based + * on the specified {@link ApplicationContext}. + * @param context the {@link ApplicationContext} to use + * @return a {@link ClassPathScanningCandidateComponentProvider} suitable to scan + * entities + * @since 2.4.0 + */ + protected ClassPathScanningCandidateComponentProvider createClassPathScanningCandidateComponentProvider( + ApplicationContext context) { + ClassPathScanningCandidateComponentProvider scanner = new ClassPathScanningCandidateComponentProvider(false); + scanner.setEnvironment(context.getEnvironment()); + scanner.setResourceLoader(context); + return scanner; + } + private List getPackages() { List packages = EntityScanPackages.get(this.context).getPackageNames(); if (packages.isEmpty() && AutoConfigurationPackages.has(this.context)) { diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/domain/EntityScannerTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/domain/EntityScannerTests.java index 1110a73a84..4b6ca9e4a8 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/domain/EntityScannerTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/domain/EntityScannerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2019 the original author or authors. + * Copyright 2012-2020 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. @@ -16,12 +16,14 @@ package org.springframework.boot.autoconfigure.domain; +import java.util.Collections; import java.util.Set; import javax.persistence.Embeddable; import javax.persistence.Entity; import org.junit.jupiter.api.Test; +import org.mockito.ArgumentCaptor; import org.springframework.boot.autoconfigure.domain.scan.a.EmbeddableA; import org.springframework.boot.autoconfigure.domain.scan.a.EntityA; @@ -29,11 +31,18 @@ import org.springframework.boot.autoconfigure.domain.scan.b.EmbeddableB; import org.springframework.boot.autoconfigure.domain.scan.b.EntityB; import org.springframework.boot.autoconfigure.domain.scan.c.EmbeddableC; import org.springframework.boot.autoconfigure.domain.scan.c.EntityC; +import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider; import org.springframework.context.annotation.Configuration; +import org.springframework.core.type.filter.AnnotationTypeFilter; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; /** * Tests for {@link EntityScanner}. @@ -79,6 +88,41 @@ class EntityScannerTests { context.close(); } + @Test + void scanShouldUseCustomCandidateComponentProvider() throws ClassNotFoundException { + ClassPathScanningCandidateComponentProvider candidateComponentProvider = mock( + ClassPathScanningCandidateComponentProvider.class); + given(candidateComponentProvider.findCandidateComponents("org.springframework.boot.autoconfigure.domain.scan")) + .willReturn(Collections.emptySet()); + AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ScanConfig.class); + TestEntityScanner scanner = new TestEntityScanner(context, candidateComponentProvider); + scanner.scan(Entity.class); + ArgumentCaptor annotationTypeFilter = ArgumentCaptor.forClass(AnnotationTypeFilter.class); + verify(candidateComponentProvider).addIncludeFilter(annotationTypeFilter.capture()); + verify(candidateComponentProvider) + .findCandidateComponents("org.springframework.boot.autoconfigure.domain.scan"); + verifyNoMoreInteractions(candidateComponentProvider); + assertThat(annotationTypeFilter.getValue().getAnnotationType()).isEqualTo(Entity.class); + } + + private static class TestEntityScanner extends EntityScanner { + + private final ClassPathScanningCandidateComponentProvider candidateComponentProvider; + + TestEntityScanner(ApplicationContext context, + ClassPathScanningCandidateComponentProvider candidateComponentProvider) { + super(context); + this.candidateComponentProvider = candidateComponentProvider; + } + + @Override + protected ClassPathScanningCandidateComponentProvider createClassPathScanningCandidateComponentProvider( + ApplicationContext context) { + return this.candidateComponentProvider; + } + + } + @Configuration(proxyBeanMethods = false) @EntityScan("org.springframework.boot.autoconfigure.domain.scan") static class ScanConfig {