Create spring-boot-jpa module
This commit is contained in:
committed by
Phillip Webb
parent
81d4528eee
commit
079713039d
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
* Copyright 2012-2025 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
|
||||
*
|
||||
* https://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.autoconfigure.domain;
|
||||
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.core.annotation.AliasFor;
|
||||
|
||||
/**
|
||||
* Configures the base packages used by auto-configuration when scanning for entity
|
||||
* classes. Refer to the documentation of the data technology you are using for more
|
||||
* details.
|
||||
* <p>
|
||||
* One of {@link #basePackageClasses()}, {@link #basePackages()} or its alias
|
||||
* {@link #value()} may be specified to define specific packages to scan. If specific
|
||||
* packages are not defined scanning will occur from the package of the class with this
|
||||
* annotation.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @since 4.0.0
|
||||
* @see EntityScanPackages
|
||||
*/
|
||||
@Target(ElementType.TYPE)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
@Import(EntityScanPackages.Registrar.class)
|
||||
public @interface EntityScan {
|
||||
|
||||
/**
|
||||
* Alias for the {@link #basePackages()} attribute. Allows for more concise annotation
|
||||
* declarations e.g.: {@code @EntityScan("org.my.pkg")} instead of
|
||||
* {@code @EntityScan(basePackages="org.my.pkg")}.
|
||||
* @return the base packages to scan
|
||||
*/
|
||||
@AliasFor("basePackages")
|
||||
String[] value() default {};
|
||||
|
||||
/**
|
||||
* Base packages to scan for entities. {@link #value()} is an alias for (and mutually
|
||||
* exclusive with) this attribute.
|
||||
* <p>
|
||||
* Use {@link #basePackageClasses()} for a type-safe alternative to String-based
|
||||
* package names.
|
||||
* @return the base packages to scan
|
||||
*/
|
||||
@AliasFor("value")
|
||||
String[] basePackages() default {};
|
||||
|
||||
/**
|
||||
* Type-safe alternative to {@link #basePackages()} for specifying the packages to
|
||||
* scan for entities. The package of each class specified will be scanned.
|
||||
* <p>
|
||||
* Consider creating a special no-op marker class or interface in each package that
|
||||
* serves no purpose other than being referenced by this attribute.
|
||||
* @return classes from the base packages to scan
|
||||
*/
|
||||
Class<?>[] basePackageClasses() default {};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,179 @@
|
||||
/*
|
||||
* Copyright 2012-2025 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
|
||||
*
|
||||
* https://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.autoconfigure.domain;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.beans.factory.BeanFactory;
|
||||
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.RootBeanDefinition;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
|
||||
import org.springframework.core.annotation.AnnotationAttributes;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.core.type.AnnotationMetadata;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* Class for storing {@link EntityScan @EntityScan} specified packages for reference later
|
||||
* (e.g. by JPA auto-configuration).
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @since 4.0.0
|
||||
* @see EntityScan
|
||||
* @see EntityScanner
|
||||
*/
|
||||
public class EntityScanPackages {
|
||||
|
||||
private static final String BEAN = EntityScanPackages.class.getName();
|
||||
|
||||
private static final EntityScanPackages NONE = new EntityScanPackages();
|
||||
|
||||
private final List<String> packageNames;
|
||||
|
||||
EntityScanPackages(String... packageNames) {
|
||||
List<String> packages = new ArrayList<>();
|
||||
for (String name : packageNames) {
|
||||
if (StringUtils.hasText(name)) {
|
||||
packages.add(name);
|
||||
}
|
||||
}
|
||||
this.packageNames = Collections.unmodifiableList(packages);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the package names specified from all {@link EntityScan @EntityScan}
|
||||
* annotations.
|
||||
* @return the entity scan package names
|
||||
*/
|
||||
public List<String> getPackageNames() {
|
||||
return this.packageNames;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the {@link EntityScanPackages} for the given bean factory.
|
||||
* @param beanFactory the source bean factory
|
||||
* @return the {@link EntityScanPackages} for the bean factory (never {@code null})
|
||||
*/
|
||||
public static EntityScanPackages get(BeanFactory beanFactory) {
|
||||
// Currently we only store a single base package, but we return a list to
|
||||
// allow this to change in the future if needed
|
||||
try {
|
||||
return beanFactory.getBean(BEAN, EntityScanPackages.class);
|
||||
}
|
||||
catch (NoSuchBeanDefinitionException ex) {
|
||||
return NONE;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the specified entity scan packages with the system.
|
||||
* @param registry the source registry
|
||||
* @param packageNames the package names to register
|
||||
*/
|
||||
public static void register(BeanDefinitionRegistry registry, String... packageNames) {
|
||||
Assert.notNull(registry, "'registry' must not be null");
|
||||
Assert.notNull(packageNames, "'packageNames' must not be null");
|
||||
register(registry, Arrays.asList(packageNames));
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the specified entity scan packages with the system.
|
||||
* @param registry the source registry
|
||||
* @param packageNames the package names to register
|
||||
*/
|
||||
public static void register(BeanDefinitionRegistry registry, Collection<String> packageNames) {
|
||||
Assert.notNull(registry, "'registry' must not be null");
|
||||
Assert.notNull(packageNames, "'packageNames' must not be null");
|
||||
if (registry.containsBeanDefinition(BEAN)) {
|
||||
EntityScanPackagesBeanDefinition beanDefinition = (EntityScanPackagesBeanDefinition) registry
|
||||
.getBeanDefinition(BEAN);
|
||||
beanDefinition.addPackageNames(packageNames);
|
||||
}
|
||||
else {
|
||||
registry.registerBeanDefinition(BEAN, new EntityScanPackagesBeanDefinition(packageNames));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link ImportBeanDefinitionRegistrar} to store the base package from the importing
|
||||
* configuration.
|
||||
*/
|
||||
static class Registrar implements ImportBeanDefinitionRegistrar {
|
||||
|
||||
private final Environment environment;
|
||||
|
||||
Registrar(Environment environment) {
|
||||
this.environment = environment;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
|
||||
register(registry, getPackagesToScan(metadata));
|
||||
}
|
||||
|
||||
private Set<String> getPackagesToScan(AnnotationMetadata metadata) {
|
||||
AnnotationAttributes attributes = AnnotationAttributes
|
||||
.fromMap(metadata.getAnnotationAttributes(EntityScan.class.getName()));
|
||||
Set<String> packagesToScan = new LinkedHashSet<>();
|
||||
for (String basePackage : attributes.getStringArray("basePackages")) {
|
||||
String[] tokenized = StringUtils.tokenizeToStringArray(
|
||||
this.environment.resolvePlaceholders(basePackage),
|
||||
ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
|
||||
Collections.addAll(packagesToScan, tokenized);
|
||||
}
|
||||
for (Class<?> basePackageClass : attributes.getClassArray("basePackageClasses")) {
|
||||
packagesToScan.add(this.environment.resolvePlaceholders(ClassUtils.getPackageName(basePackageClass)));
|
||||
}
|
||||
if (packagesToScan.isEmpty()) {
|
||||
String packageName = ClassUtils.getPackageName(metadata.getClassName());
|
||||
Assert.state(StringUtils.hasLength(packageName), "@EntityScan cannot be used with the default package");
|
||||
return Collections.singleton(packageName);
|
||||
}
|
||||
return packagesToScan;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static class EntityScanPackagesBeanDefinition extends RootBeanDefinition {
|
||||
|
||||
private final Set<String> packageNames = new LinkedHashSet<>();
|
||||
|
||||
EntityScanPackagesBeanDefinition(Collection<String> packageNames) {
|
||||
setBeanClass(EntityScanPackages.class);
|
||||
setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
|
||||
addPackageNames(packageNames);
|
||||
}
|
||||
|
||||
private void addPackageNames(Collection<String> additionalPackageNames) {
|
||||
this.packageNames.addAll(additionalPackageNames);
|
||||
getConstructorArgumentValues().addIndexedArgumentValue(0, StringUtils.toStringArray(this.packageNames));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
/*
|
||||
* Copyright 2012-2025 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
|
||||
*
|
||||
* https://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.autoconfigure.domain;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.beans.factory.config.BeanDefinition;
|
||||
import org.springframework.boot.autoconfigure.AutoConfigurationPackages;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
|
||||
import org.springframework.core.type.filter.AnnotationTypeFilter;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* An entity scanner that searches the classpath from an {@link EntityScan @EntityScan}
|
||||
* specified packages.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public class EntityScanner {
|
||||
|
||||
private final ApplicationContext context;
|
||||
|
||||
/**
|
||||
* Create a new {@link EntityScanner} instance.
|
||||
* @param context the source application context
|
||||
*/
|
||||
public EntityScanner(ApplicationContext context) {
|
||||
Assert.notNull(context, "'context' must not be null");
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
/**
|
||||
* Scan for entities with the specified annotations.
|
||||
* @param annotationTypes the annotation types used on the entities
|
||||
* @return a set of entity classes
|
||||
* @throws ClassNotFoundException if an entity class cannot be loaded
|
||||
*/
|
||||
@SafeVarargs
|
||||
public final Set<Class<?>> scan(Class<? extends Annotation>... annotationTypes) throws ClassNotFoundException {
|
||||
List<String> packages = getPackages();
|
||||
if (packages.isEmpty()) {
|
||||
return Collections.emptySet();
|
||||
}
|
||||
ClassPathScanningCandidateComponentProvider scanner = createClassPathScanningCandidateComponentProvider(
|
||||
this.context);
|
||||
for (Class<? extends Annotation> annotationType : annotationTypes) {
|
||||
scanner.addIncludeFilter(new AnnotationTypeFilter(annotationType));
|
||||
}
|
||||
Set<Class<?>> entitySet = new HashSet<>();
|
||||
for (String basePackage : packages) {
|
||||
if (StringUtils.hasText(basePackage)) {
|
||||
for (BeanDefinition candidate : scanner.findCandidateComponents(basePackage)) {
|
||||
entitySet.add(ClassUtils.forName(candidate.getBeanClassName(), this.context.getClassLoader()));
|
||||
}
|
||||
}
|
||||
}
|
||||
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<String> getPackages() {
|
||||
List<String> packages = EntityScanPackages.get(this.context).getPackageNames();
|
||||
if (packages.isEmpty() && AutoConfigurationPackages.has(this.context)) {
|
||||
packages = AutoConfigurationPackages.get(this.context);
|
||||
}
|
||||
return packages;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
* Copyright 2012-2025 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
|
||||
*
|
||||
* https://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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* General purpose domain annotations and classes.
|
||||
*/
|
||||
package org.springframework.boot.autoconfigure.domain;
|
||||
@@ -0,0 +1,182 @@
|
||||
/*
|
||||
* Copyright 2012-2025 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
|
||||
*
|
||||
* https://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.autoconfigure.domain;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.beans.factory.support.RootBeanDefinition;
|
||||
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.annotation.AnnotationConfigurationException;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
|
||||
|
||||
/**
|
||||
* Tests for {@link EntityScanPackages}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
class EntityScanPackagesTests {
|
||||
|
||||
private AnnotationConfigApplicationContext context;
|
||||
|
||||
@AfterEach
|
||||
void cleanup() {
|
||||
if (this.context != null) {
|
||||
this.context.close();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void getWhenNoneRegisteredShouldReturnNone() {
|
||||
this.context = new AnnotationConfigApplicationContext();
|
||||
this.context.refresh();
|
||||
EntityScanPackages packages = EntityScanPackages.get(this.context);
|
||||
assertThat(packages).isNotNull();
|
||||
assertThat(packages.getPackageNames()).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
void getShouldReturnRegisterPackages() {
|
||||
this.context = new AnnotationConfigApplicationContext();
|
||||
EntityScanPackages.register(this.context, "a", "b");
|
||||
EntityScanPackages.register(this.context, "b", "c");
|
||||
this.context.refresh();
|
||||
EntityScanPackages packages = EntityScanPackages.get(this.context);
|
||||
assertThat(packages.getPackageNames()).containsExactly("a", "b", "c");
|
||||
}
|
||||
|
||||
@Test
|
||||
void registerFromArrayWhenRegistryIsNullShouldThrowException() {
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> EntityScanPackages.register(null))
|
||||
.withMessageContaining("'registry' must not be null");
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
void registerFromArrayWhenPackageNamesIsNullShouldThrowException() {
|
||||
this.context = new AnnotationConfigApplicationContext();
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> EntityScanPackages.register(this.context, (String[]) null))
|
||||
.withMessageContaining("'packageNames' must not be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
void registerFromCollectionWhenRegistryIsNullShouldThrowException() {
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> EntityScanPackages.register(null, Collections.emptyList()))
|
||||
.withMessageContaining("'registry' must not be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
void registerFromCollectionWhenPackageNamesIsNullShouldThrowException() {
|
||||
this.context = new AnnotationConfigApplicationContext();
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> EntityScanPackages.register(this.context, (Collection<String>) null))
|
||||
.withMessageContaining("'packageNames' must not be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
void entityScanAnnotationWhenHasValueAttributeShouldSetupPackages() {
|
||||
this.context = new AnnotationConfigApplicationContext(EntityScanValueConfig.class);
|
||||
EntityScanPackages packages = EntityScanPackages.get(this.context);
|
||||
assertThat(packages.getPackageNames()).containsExactly("a");
|
||||
}
|
||||
|
||||
@Test
|
||||
void entityScanAnnotationWhenHasValueAttributeShouldSetupPackagesAsm() {
|
||||
this.context = new AnnotationConfigApplicationContext();
|
||||
this.context.registerBeanDefinition("entityScanValueConfig",
|
||||
new RootBeanDefinition(EntityScanValueConfig.class.getName()));
|
||||
this.context.refresh();
|
||||
EntityScanPackages packages = EntityScanPackages.get(this.context);
|
||||
assertThat(packages.getPackageNames()).containsExactly("a");
|
||||
}
|
||||
|
||||
@Test
|
||||
void entityScanAnnotationWhenHasBasePackagesAttributeShouldSetupPackages() {
|
||||
this.context = new AnnotationConfigApplicationContext(EntityScanBasePackagesConfig.class);
|
||||
EntityScanPackages packages = EntityScanPackages.get(this.context);
|
||||
assertThat(packages.getPackageNames()).containsExactly("b");
|
||||
}
|
||||
|
||||
@Test
|
||||
void entityScanAnnotationWhenHasValueAndBasePackagesAttributeShouldThrow() {
|
||||
assertThatExceptionOfType(AnnotationConfigurationException.class)
|
||||
.isThrownBy(() -> this.context = new AnnotationConfigApplicationContext(
|
||||
EntityScanValueAndBasePackagesConfig.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void entityScanAnnotationWhenHasBasePackageClassesAttributeShouldSetupPackages() {
|
||||
this.context = new AnnotationConfigApplicationContext(EntityScanBasePackageClassesConfig.class);
|
||||
EntityScanPackages packages = EntityScanPackages.get(this.context);
|
||||
assertThat(packages.getPackageNames()).containsExactly(getClass().getPackage().getName());
|
||||
}
|
||||
|
||||
@Test
|
||||
void entityScanAnnotationWhenNoAttributesShouldSetupPackages() {
|
||||
this.context = new AnnotationConfigApplicationContext(EntityScanNoAttributesConfig.class);
|
||||
EntityScanPackages packages = EntityScanPackages.get(this.context);
|
||||
assertThat(packages.getPackageNames()).containsExactly(getClass().getPackage().getName());
|
||||
}
|
||||
|
||||
@Test
|
||||
void entityScanAnnotationWhenLoadingFromMultipleConfigsShouldCombinePackages() {
|
||||
this.context = new AnnotationConfigApplicationContext(EntityScanValueConfig.class,
|
||||
EntityScanBasePackagesConfig.class);
|
||||
EntityScanPackages packages = EntityScanPackages.get(this.context);
|
||||
assertThat(packages.getPackageNames()).containsExactly("a", "b");
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@EntityScan("a")
|
||||
static class EntityScanValueConfig {
|
||||
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@EntityScan(basePackages = "b")
|
||||
static class EntityScanBasePackagesConfig {
|
||||
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@EntityScan(value = "a", basePackages = "b")
|
||||
static class EntityScanValueAndBasePackagesConfig {
|
||||
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@EntityScan(basePackageClasses = EntityScanPackagesTests.class)
|
||||
static class EntityScanBasePackageClassesConfig {
|
||||
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@EntityScan
|
||||
static class EntityScanNoAttributesConfig {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,177 @@
|
||||
/*
|
||||
* Copyright 2012-2025 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
|
||||
*
|
||||
* https://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.autoconfigure.domain;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Set;
|
||||
|
||||
import jakarta.persistence.Embeddable;
|
||||
import jakarta.persistence.Entity;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
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;
|
||||
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.boot.test.util.TestPropertyValues;
|
||||
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.ArgumentMatchers.assertArg;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
import static org.mockito.BDDMockito.then;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
/**
|
||||
* Tests for {@link EntityScanner}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
class EntityScannerTests {
|
||||
|
||||
@Test
|
||||
void createWhenContextIsNullShouldThrowException() {
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> new EntityScanner(null))
|
||||
.withMessageContaining("'context' must not be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
void scanShouldScanFromSinglePackage() throws Exception {
|
||||
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ScanConfig.class);
|
||||
EntityScanner scanner = new EntityScanner(context);
|
||||
Set<Class<?>> scanned = scanner.scan(Entity.class);
|
||||
assertThat(scanned).containsOnly(EntityA.class, EntityB.class, EntityC.class);
|
||||
context.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
void scanShouldScanFromResolvedPlaceholderPackage() throws Exception {
|
||||
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
|
||||
TestPropertyValues.of("com.example.entity-package=org.springframework.boot.autoconfigure.domain.scan")
|
||||
.applyTo(context);
|
||||
context.register(ScanPlaceholderConfig.class);
|
||||
context.refresh();
|
||||
EntityScanner scanner = new EntityScanner(context);
|
||||
Set<Class<?>> scanned = scanner.scan(Entity.class);
|
||||
assertThat(scanned).containsOnly(EntityA.class, EntityB.class, EntityC.class);
|
||||
context.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
void scanShouldScanFromMultiplePackages() throws Exception {
|
||||
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ScanAConfig.class,
|
||||
ScanBConfig.class);
|
||||
EntityScanner scanner = new EntityScanner(context);
|
||||
Set<Class<?>> scanned = scanner.scan(Entity.class);
|
||||
assertThat(scanned).containsOnly(EntityA.class, EntityB.class);
|
||||
context.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
void scanShouldFilterOnAnnotation() throws Exception {
|
||||
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ScanConfig.class);
|
||||
EntityScanner scanner = new EntityScanner(context);
|
||||
assertThat(scanner.scan(Entity.class)).containsOnly(EntityA.class, EntityB.class, EntityC.class);
|
||||
assertThat(scanner.scan(Embeddable.class)).containsOnly(EmbeddableA.class, EmbeddableB.class,
|
||||
EmbeddableC.class);
|
||||
assertThat(scanner.scan(Entity.class, Embeddable.class)).containsOnly(EntityA.class, EntityB.class,
|
||||
EntityC.class, EmbeddableA.class, EmbeddableB.class, EmbeddableC.class);
|
||||
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);
|
||||
then(candidateComponentProvider).should()
|
||||
.addIncludeFilter(
|
||||
assertArg((typeFilter) -> assertThat(typeFilter).isInstanceOfSatisfying(AnnotationTypeFilter.class,
|
||||
(filter) -> assertThat(filter.getAnnotationType()).isEqualTo(Entity.class))));
|
||||
then(candidateComponentProvider).should()
|
||||
.findCandidateComponents("org.springframework.boot.autoconfigure.domain.scan");
|
||||
then(candidateComponentProvider).shouldHaveNoMoreInteractions();
|
||||
}
|
||||
|
||||
@Test
|
||||
void scanShouldScanCommaSeparatedPackagesInPlaceholderPackage() throws Exception {
|
||||
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
|
||||
TestPropertyValues
|
||||
.of("com.example.entity-package=org.springframework.boot.autoconfigure.domain.scan.a,org.springframework.boot.autoconfigure.domain.scan.b")
|
||||
.applyTo(context);
|
||||
context.register(ScanPlaceholderConfig.class);
|
||||
context.refresh();
|
||||
EntityScanner scanner = new EntityScanner(context);
|
||||
Set<Class<?>> scanned = scanner.scan(Entity.class);
|
||||
assertThat(scanned).containsOnly(EntityA.class, EntityB.class);
|
||||
context.close();
|
||||
}
|
||||
|
||||
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 {
|
||||
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@EntityScan(basePackageClasses = EntityA.class)
|
||||
static class ScanAConfig {
|
||||
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@EntityScan(basePackageClasses = EntityB.class)
|
||||
static class ScanBConfig {
|
||||
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@EntityScan("${com.example.entity-package}")
|
||||
static class ScanPlaceholderConfig {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
* Copyright 2012-2025 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
|
||||
*
|
||||
* https://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.autoconfigure.domain.scan.a;
|
||||
|
||||
import jakarta.persistence.Embeddable;
|
||||
|
||||
@Embeddable
|
||||
public class EmbeddableA {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
* Copyright 2012-2025 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
|
||||
*
|
||||
* https://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.autoconfigure.domain.scan.a;
|
||||
|
||||
import jakarta.persistence.Entity;
|
||||
|
||||
@Entity
|
||||
public class EntityA {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
* Copyright 2012-2025 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
|
||||
*
|
||||
* https://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.autoconfigure.domain.scan.b;
|
||||
|
||||
import jakarta.persistence.Embeddable;
|
||||
|
||||
@Embeddable
|
||||
public class EmbeddableB {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
* Copyright 2012-2025 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
|
||||
*
|
||||
* https://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.autoconfigure.domain.scan.b;
|
||||
|
||||
import jakarta.persistence.Entity;
|
||||
|
||||
@Entity
|
||||
public class EntityB {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
* Copyright 2012-2025 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
|
||||
*
|
||||
* https://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.autoconfigure.domain.scan.c;
|
||||
|
||||
import jakarta.persistence.Embeddable;
|
||||
|
||||
@Embeddable
|
||||
public class EmbeddableC {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
* Copyright 2012-2025 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
|
||||
*
|
||||
* https://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.autoconfigure.domain.scan.c;
|
||||
|
||||
import jakarta.persistence.Entity;
|
||||
|
||||
@Entity
|
||||
public class EntityC {
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user