Add @EntityScan annotation
Add an @EntityScan annotation that can be used to configure the `packagesToScan` attribute on `LocalContainerEntityManagerFactoryBean`. Fixed gh-239
This commit is contained in:
parent
8db1d0e044
commit
096ace6896
|
|
@ -94,6 +94,11 @@
|
|||
<artifactId>jul-to-slf4j</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-orm</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-test</artifactId>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,78 @@
|
|||
/*
|
||||
* Copyright 2012-2014 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.boot.orm.jpa;
|
||||
|
||||
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.orm.jpa.LocalContainerEntityManagerFactoryBean;
|
||||
|
||||
/**
|
||||
* Configures the {@link LocalContainerEntityManagerFactoryBean} to to scan for entity
|
||||
* classes in the classpath. This annotation provides an alternative to manually setting
|
||||
* {@link LocalContainerEntityManagerFactoryBean#setPackagesToScan(String...)} and is
|
||||
* particularly useful if you want to configure entity scanning in a type-safe way, or if
|
||||
* your {@link LocalContainerEntityManagerFactoryBean} is auto-configured.
|
||||
* <p>
|
||||
* A {@link LocalContainerEntityManagerFactoryBean} must be configured within your Spring
|
||||
* ApplicationContext in order to use entity scanning. Furthermore, any existing
|
||||
* {@code packagesToScan} setting will be replaced.
|
||||
* <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
|
||||
*/
|
||||
@Target(ElementType.TYPE)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
@Import(EntityScanRegistrar.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")}.
|
||||
*/
|
||||
String[] value() default {};
|
||||
|
||||
/**
|
||||
* Base packages to scan for annotated entities.
|
||||
* <p>
|
||||
* {@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.
|
||||
*/
|
||||
String[] basePackages() default {};
|
||||
|
||||
/**
|
||||
* Type-safe alternative to {@link #basePackages()} for specifying the packages to
|
||||
* scan for annotated 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.
|
||||
*/
|
||||
Class<?>[] basePackageClasses() default {};
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,124 @@
|
|||
/*
|
||||
* Copyright 2012-2014 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.boot.orm.jpa;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.config.BeanPostProcessor;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
|
||||
import org.springframework.beans.factory.support.GenericBeanDefinition;
|
||||
import org.springframework.context.ApplicationListener;
|
||||
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
|
||||
import org.springframework.context.event.ContextRefreshedEvent;
|
||||
import org.springframework.core.annotation.AnnotationAttributes;
|
||||
import org.springframework.core.type.AnnotationMetadata;
|
||||
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
|
||||
/**
|
||||
* {@link ImportBeanDefinitionRegistrar} used by {@link EntityScan}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
class EntityScanRegistrar implements ImportBeanDefinitionRegistrar {
|
||||
|
||||
private static final String BEAN_NAME = "entityScanBeanPostProcessor";
|
||||
|
||||
@Override
|
||||
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata,
|
||||
BeanDefinitionRegistry registry) {
|
||||
if (!registry.containsBeanDefinition(BEAN_NAME)) {
|
||||
GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
|
||||
beanDefinition.setBeanClass(EntityScanBeanPostProcessor.class);
|
||||
beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(
|
||||
getPackagesToScan(importingClassMetadata));
|
||||
registry.registerBeanDefinition(BEAN_NAME, beanDefinition);
|
||||
}
|
||||
}
|
||||
|
||||
private String[] getPackagesToScan(AnnotationMetadata metadata) {
|
||||
AnnotationAttributes attributes = AnnotationAttributes.fromMap(metadata
|
||||
.getAnnotationAttributes(EntityScan.class.getName()));
|
||||
String[] value = attributes.getStringArray("value");
|
||||
String[] basePackages = attributes.getStringArray("basePackages");
|
||||
Class<?>[] basePackageClasses = attributes.getClassArray("basePackageClasses");
|
||||
|
||||
if (!ObjectUtils.isEmpty(value)) {
|
||||
Assert.state(ObjectUtils.isEmpty(basePackages),
|
||||
"@EntityScan basePackages and value attributes are mutually exclusive");
|
||||
}
|
||||
|
||||
Set<String> packagesToScan = new LinkedHashSet<String>();
|
||||
packagesToScan.addAll(Arrays.asList(value));
|
||||
packagesToScan.addAll(Arrays.asList(basePackages));
|
||||
for (Class<?> basePackageClass : basePackageClasses) {
|
||||
packagesToScan.add(ClassUtils.getPackageName(basePackageClass));
|
||||
}
|
||||
if (packagesToScan.isEmpty()) {
|
||||
return new String[] { ClassUtils.getPackageName(metadata.getClassName()) };
|
||||
}
|
||||
return new ArrayList<String>(packagesToScan).toArray(new String[packagesToScan
|
||||
.size()]);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link BeanPostProcessor} to set
|
||||
* {@link LocalContainerEntityManagerFactoryBean#setPackagesToScan(String...)} based
|
||||
* on an {@link EntityScan} annotation.
|
||||
*/
|
||||
static class EntityScanBeanPostProcessor implements BeanPostProcessor,
|
||||
ApplicationListener<ContextRefreshedEvent> {
|
||||
|
||||
private final String[] packagesToScan;
|
||||
|
||||
private boolean processed;
|
||||
|
||||
public EntityScanBeanPostProcessor(String[] packagesToScan) {
|
||||
this.packagesToScan = packagesToScan;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object postProcessBeforeInitialization(Object bean, String beanName)
|
||||
throws BeansException {
|
||||
if (bean instanceof LocalContainerEntityManagerFactoryBean) {
|
||||
LocalContainerEntityManagerFactoryBean factoryBean = (LocalContainerEntityManagerFactoryBean) bean;
|
||||
factoryBean.setPackagesToScan(this.packagesToScan);
|
||||
this.processed = true;
|
||||
}
|
||||
return bean;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object postProcessAfterInitialization(Object bean, String beanName)
|
||||
throws BeansException {
|
||||
return bean;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onApplicationEvent(ContextRefreshedEvent event) {
|
||||
Assert.state(this.processed, "Unable to configure "
|
||||
+ "LocalContainerEntityManagerFactoryBean from @EntityScan, "
|
||||
+ "ensure an appropriate bean is registered.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,173 @@
|
|||
/*
|
||||
* Copyright 2012-2014 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.boot.orm.jpa;
|
||||
|
||||
import javax.persistence.EntityManagerFactory;
|
||||
import javax.persistence.PersistenceException;
|
||||
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.ExpectedException;
|
||||
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
|
||||
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
/**
|
||||
* Tests for {@link EntityScan}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
public class EntityScanTests {
|
||||
|
||||
@Rule
|
||||
public ExpectedException thrown = ExpectedException.none();
|
||||
|
||||
private AnnotationConfigApplicationContext context;
|
||||
|
||||
@Test
|
||||
public void testValue() throws Exception {
|
||||
this.context = new AnnotationConfigApplicationContext(ValueConfig.class);
|
||||
assertSetPackagesToScan("com.mycorp.entity");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void basePackages() throws Exception {
|
||||
this.context = new AnnotationConfigApplicationContext(BasePackagesConfig.class);
|
||||
assertSetPackagesToScan("com.mycorp.entity2");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void basePackageClasses() throws Exception {
|
||||
this.context = new AnnotationConfigApplicationContext(
|
||||
BasePackageClassesConfig.class);
|
||||
assertSetPackagesToScan(getClass().getPackage().getName());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void fromConfigurationClass() throws Exception {
|
||||
this.context = new AnnotationConfigApplicationContext(FromConfigConfig.class);
|
||||
assertSetPackagesToScan(getClass().getPackage().getName());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void valueAndBasePackagesThrows() throws Exception {
|
||||
this.thrown.expect(IllegalStateException.class);
|
||||
this.thrown.expectMessage("@EntityScan basePackages and value "
|
||||
+ "attributes are mutually exclusive");
|
||||
this.context = new AnnotationConfigApplicationContext(ValueAndBasePackages.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void valueAndBasePackageClassesMerges() throws Exception {
|
||||
this.context = new AnnotationConfigApplicationContext(
|
||||
ValueAndBasePackageClasses.class);
|
||||
assertSetPackagesToScan("com.mycorp.entity", getClass().getPackage().getName());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void basePackageAndBasePackageClassesMerges() throws Exception {
|
||||
this.context = new AnnotationConfigApplicationContext(
|
||||
BasePackagesAndBasePackageClasses.class);
|
||||
assertSetPackagesToScan("com.mycorp.entity2", getClass().getPackage().getName());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void needsEntityManageFactory() throws Exception {
|
||||
this.thrown.expect(IllegalStateException.class);
|
||||
this.thrown.expectMessage("Unable to configure "
|
||||
+ "LocalContainerEntityManagerFactoryBean from @EntityScan, "
|
||||
+ "ensure an appropriate bean is registered.");
|
||||
this.context = new AnnotationConfigApplicationContext(MissingEntityManager.class);
|
||||
}
|
||||
|
||||
private void assertSetPackagesToScan(String... expected) {
|
||||
String[] actual = this.context.getBean(
|
||||
TestLocalContainerEntityManagerFactoryBean.class).getPackagesToScan();
|
||||
assertThat(actual, equalTo(expected));
|
||||
}
|
||||
|
||||
@Configuration
|
||||
static class BaseConfig {
|
||||
|
||||
@Bean
|
||||
public TestLocalContainerEntityManagerFactoryBean entityManagerFactoryBean() {
|
||||
return new TestLocalContainerEntityManagerFactoryBean();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@EntityScan("com.mycorp.entity")
|
||||
static class ValueConfig extends BaseConfig {
|
||||
}
|
||||
|
||||
@EntityScan(basePackages = "com.mycorp.entity2")
|
||||
static class BasePackagesConfig extends BaseConfig {
|
||||
}
|
||||
|
||||
@EntityScan(basePackageClasses = EntityScanTests.class)
|
||||
static class BasePackageClassesConfig extends BaseConfig {
|
||||
}
|
||||
|
||||
@EntityScan
|
||||
static class FromConfigConfig extends BaseConfig {
|
||||
}
|
||||
|
||||
@EntityScan(value = "com.mycorp.entity", basePackages = "com.mycorp")
|
||||
static class ValueAndBasePackages extends BaseConfig {
|
||||
}
|
||||
|
||||
@EntityScan(value = "com.mycorp.entity", basePackageClasses = EntityScanTests.class)
|
||||
static class ValueAndBasePackageClasses extends BaseConfig {
|
||||
}
|
||||
|
||||
@EntityScan(basePackages = "com.mycorp.entity2", basePackageClasses = EntityScanTests.class)
|
||||
static class BasePackagesAndBasePackageClasses extends BaseConfig {
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EntityScan("com.mycorp.entity")
|
||||
static class MissingEntityManager {
|
||||
}
|
||||
|
||||
private static class TestLocalContainerEntityManagerFactoryBean extends
|
||||
LocalContainerEntityManagerFactoryBean {
|
||||
|
||||
private String[] packagesToScan;
|
||||
|
||||
@Override
|
||||
protected EntityManagerFactory createNativeEntityManagerFactory()
|
||||
throws PersistenceException {
|
||||
return mock(EntityManagerFactory.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPackagesToScan(String... packagesToScan) {
|
||||
this.packagesToScan = packagesToScan;
|
||||
}
|
||||
|
||||
public String[] getPackagesToScan() {
|
||||
return this.packagesToScan;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
Loading…
Reference in New Issue