Skip to content

Commit 1cfc6f6

Browse files
odrotbohmphilwebb
authored andcommitted
Allow multiple @EntityScan annotations to be used
Update EntityScanRegistrar so that multiple @EntityScan annotations can be used with a single application. Previously, when an application used multiple annotations only the first one found would get applied. This changes alters that to augment the packages that will be scanned. Fixes gh-2757
1 parent d6e24a1 commit 1cfc6f6

File tree

2 files changed

+57
-16
lines changed

2 files changed

+57
-16
lines changed

spring-boot/src/main/java/org/springframework/boot/orm/jpa/EntityScanRegistrar.java

Lines changed: 40 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,16 @@
1616

1717
package org.springframework.boot.orm.jpa;
1818

19-
import java.util.ArrayList;
2019
import java.util.Arrays;
20+
import java.util.Collections;
2121
import java.util.LinkedHashSet;
2222
import java.util.Set;
2323

2424
import org.springframework.beans.BeansException;
2525
import org.springframework.beans.factory.SmartInitializingSingleton;
2626
import org.springframework.beans.factory.config.BeanDefinition;
2727
import org.springframework.beans.factory.config.BeanPostProcessor;
28+
import org.springframework.beans.factory.config.ConstructorArgumentValues.ValueHolder;
2829
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
2930
import org.springframework.beans.factory.support.GenericBeanDefinition;
3031
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
@@ -40,6 +41,7 @@
4041
* {@link ImportBeanDefinitionRegistrar} used by {@link EntityScan}.
4142
*
4243
* @author Phillip Webb
44+
* @author Oliver Gierke
4345
*/
4446
class EntityScanRegistrar implements ImportBeanDefinitionRegistrar {
4547

@@ -48,42 +50,64 @@ class EntityScanRegistrar implements ImportBeanDefinitionRegistrar {
4850
@Override
4951
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata,
5052
BeanDefinitionRegistry registry) {
53+
Set<String> packagesToScan = getPackagesToScan(importingClassMetadata);
5154
if (!registry.containsBeanDefinition(BEAN_NAME)) {
52-
GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
53-
beanDefinition.setBeanClass(EntityScanBeanPostProcessor.class);
54-
beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(
55-
getPackagesToScan(importingClassMetadata));
56-
beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
57-
// We don't need this one to be post processed otherwise it can cause a
58-
// cascade of bean instantiation that we would rather avoid.
59-
beanDefinition.setSynthetic(true);
60-
registry.registerBeanDefinition(BEAN_NAME, beanDefinition);
55+
addEntityScanBeanPostProcessor(registry, packagesToScan);
56+
}
57+
else {
58+
updateEntityScanBeanPostProcessor(registry, packagesToScan);
6159
}
6260
}
6361

64-
private String[] getPackagesToScan(AnnotationMetadata metadata) {
62+
private Set<String> getPackagesToScan(AnnotationMetadata metadata) {
6563
AnnotationAttributes attributes = AnnotationAttributes.fromMap(metadata
6664
.getAnnotationAttributes(EntityScan.class.getName()));
6765
String[] value = attributes.getStringArray("value");
6866
String[] basePackages = attributes.getStringArray("basePackages");
6967
Class<?>[] basePackageClasses = attributes.getClassArray("basePackageClasses");
70-
7168
if (!ObjectUtils.isEmpty(value)) {
7269
Assert.state(ObjectUtils.isEmpty(basePackages),
7370
"@EntityScan basePackages and value attributes are mutually exclusive");
7471
}
75-
7672
Set<String> packagesToScan = new LinkedHashSet<String>();
7773
packagesToScan.addAll(Arrays.asList(value));
7874
packagesToScan.addAll(Arrays.asList(basePackages));
7975
for (Class<?> basePackageClass : basePackageClasses) {
8076
packagesToScan.add(ClassUtils.getPackageName(basePackageClass));
8177
}
8278
if (packagesToScan.isEmpty()) {
83-
return new String[] { ClassUtils.getPackageName(metadata.getClassName()) };
79+
return Collections.singleton(ClassUtils.getPackageName(metadata
80+
.getClassName()));
8481
}
85-
return new ArrayList<String>(packagesToScan).toArray(new String[packagesToScan
86-
.size()]);
82+
return packagesToScan;
83+
}
84+
85+
private void addEntityScanBeanPostProcessor(BeanDefinitionRegistry registry,
86+
Set<String> packagesToScan) {
87+
GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
88+
beanDefinition.setBeanClass(EntityScanBeanPostProcessor.class);
89+
beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(
90+
toArray(packagesToScan));
91+
beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
92+
// We don't need this one to be post processed otherwise it can cause a
93+
// cascade of bean instantiation that we would rather avoid.
94+
beanDefinition.setSynthetic(true);
95+
registry.registerBeanDefinition(BEAN_NAME, beanDefinition);
96+
}
97+
98+
private void updateEntityScanBeanPostProcessor(BeanDefinitionRegistry registry,
99+
Set<String> packagesToScan) {
100+
BeanDefinition definition = registry.getBeanDefinition(BEAN_NAME);
101+
ValueHolder constructorArguments = definition.getConstructorArgumentValues()
102+
.getGenericArgumentValue(String[].class);
103+
Set<String> mergedPackages = new LinkedHashSet<String>();
104+
mergedPackages.addAll(Arrays.asList((String[]) constructorArguments.getValue()));
105+
mergedPackages.addAll(packagesToScan);
106+
constructorArguments.setValue(toArray(mergedPackages));
107+
}
108+
109+
private String[] toArray(Set<String> set) {
110+
return set.toArray(new String[set.size()]);
87111
}
88112

89113
/**

spring-boot/src/test/java/org/springframework/boot/orm/jpa/EntityScanTests.java

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,13 @@ public void userDeclaredBeanPostProcessorWithEntityManagerDependencyDoesNotPreve
109109
assertSetPackagesToScan("com.mycorp.entity");
110110
}
111111

112+
@Test
113+
public void considersMultipleEntityScanAnnotations() {
114+
this.context = new AnnotationConfigApplicationContext(MultiScanFirst.class,
115+
MultiScanSecond.class);
116+
assertSetPackagesToScan("foo", "bar");
117+
}
118+
112119
private void assertSetPackagesToScan(String... expected) {
113120
String[] actual = this.context.getBean(
114121
TestLocalContainerEntityManagerFactoryBean.class).getPackagesToScan();
@@ -185,6 +192,16 @@ public Object postProcessAfterInitialization(Object bean, String beanName)
185192
}
186193
}
187194

195+
@EntityScan(basePackages = "foo")
196+
static class MultiScanFirst extends BaseConfig {
197+
198+
}
199+
200+
@EntityScan(basePackages = "bar")
201+
static class MultiScanSecond extends BaseConfig {
202+
203+
}
204+
188205
private static class TestLocalContainerEntityManagerFactoryBean extends
189206
LocalContainerEntityManagerFactoryBean {
190207

0 commit comments

Comments
 (0)