diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/domain/EntityScanPackages.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/domain/EntityScanPackages.java index 185a90fd57d..4a17db7b295 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/domain/EntityScanPackages.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/domain/EntityScanPackages.java @@ -30,6 +30,7 @@ import org.springframework.beans.factory.NoSuchBeanDefinitionException; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.beans.factory.support.GenericBeanDefinition; +import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.annotation.ImportBeanDefinitionRegistrar; import org.springframework.core.annotation.AnnotationAttributes; import org.springframework.core.env.Environment; @@ -141,7 +142,10 @@ public class EntityScanPackages { .fromMap(metadata.getAnnotationAttributes(EntityScan.class.getName())); Set packagesToScan = new LinkedHashSet<>(); for (String basePackage : attributes.getStringArray("basePackages")) { - addResolvedPackage(basePackage, packagesToScan); + String[] tokenized = StringUtils.tokenizeToStringArray( + this.environment.resolvePlaceholders(basePackage), + ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS); + Collections.addAll(packagesToScan, tokenized); } for (Class basePackageClass : attributes.getClassArray("basePackageClasses")) { addResolvedPackage(ClassUtils.getPackageName(basePackageClass), packagesToScan); 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 e78e03aec8b..22300e2e26f 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 @@ -25,6 +25,7 @@ import javax.persistence.Entity; import org.junit.jupiter.api.Test; import org.mockito.ArgumentCaptor; +import org.springframework.boot.autoconfigure.data.jpa.city.City; import org.springframework.boot.autoconfigure.domain.scan.a.EmbeddableA; import org.springframework.boot.autoconfigure.domain.scan.a.EntityA; import org.springframework.boot.autoconfigure.domain.scan.b.EmbeddableB; @@ -119,6 +120,20 @@ class EntityScannerTests { assertThat(annotationTypeFilter.getValue().getAnnotationType()).isEqualTo(Entity.class); } + @Test + void scanShouldScanCommaSeparatedPackagesInPlaceholderPackage() throws Exception { + AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); + TestPropertyValues.of( + "com.example.entity-package=org.springframework.boot.autoconfigure.domain.scan,org.springframework.boot.autoconfigure.data.jpa.city") + .applyTo(context); + context.register(ScanPlaceholderConfig.class); + context.refresh(); + EntityScanner scanner = new EntityScanner(context); + Set> scanned = scanner.scan(Entity.class); + assertThat(scanned).containsOnly(EntityA.class, EntityB.class, EntityC.class, City.class); + context.close(); + } + private static class TestEntityScanner extends EntityScanner { private final ClassPathScanningCandidateComponentProvider candidateComponentProvider;