mirror of
https://github.com/spring-projects/spring-boot.git
synced 2024-07-05 00:56:58 +08:00
[bs-22] Add tests for JPA auto configuration
* Extracted the component scan detector so it can be used without @EnableAutoConfiguration * Added unit tests * Improve logging in @Conditional processing [#48127729]
This commit is contained in:
parent
ad2e311f2f
commit
73f28a3809
@ -0,0 +1,153 @@
|
||||
/*
|
||||
* Copyright 2012-2013 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.bootstrap.autoconfigure.data;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.BeanFactory;
|
||||
import org.springframework.beans.factory.BeanFactoryAware;
|
||||
import org.springframework.beans.factory.config.BeanDefinition;
|
||||
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
|
||||
import org.springframework.beans.factory.support.AbstractBeanDefinition;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
|
||||
import org.springframework.bootstrap.context.annotation.AutoConfigurationUtils;
|
||||
import org.springframework.cglib.proxy.Enhancer;
|
||||
import org.springframework.context.annotation.ComponentScan;
|
||||
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
|
||||
import org.springframework.core.annotation.AnnotationAttributes;
|
||||
import org.springframework.core.type.AnnotationMetadata;
|
||||
import org.springframework.core.type.StandardAnnotationMetadata;
|
||||
import org.springframework.core.type.classreading.MetadataReader;
|
||||
import org.springframework.core.type.classreading.MetadataReaderFactory;
|
||||
import org.springframework.core.type.classreading.SimpleMetadataReaderFactory;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* Helper to detect a component scan declared in the enclosing context (normally on a
|
||||
* <code>@Configuration</code> class). Once the component scan is detected, the base
|
||||
* packages are stored for retrieval later by the {@link JpaRepositoriesAutoConfiguration}
|
||||
* .
|
||||
*
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
class JpaComponentScanDetector implements ImportBeanDefinitionRegistrar, BeanFactoryAware {
|
||||
|
||||
private final Log logger = LogFactory.getLog(getClass());
|
||||
|
||||
private BeanFactory beanFactory;
|
||||
|
||||
private MetadataReaderFactory metadataReaderFactory = new SimpleMetadataReaderFactory();
|
||||
|
||||
@Override
|
||||
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata,
|
||||
final BeanDefinitionRegistry registry) {
|
||||
storeComponentScanBasePackages();
|
||||
}
|
||||
|
||||
private void storeComponentScanBasePackages() {
|
||||
if (this.beanFactory instanceof ConfigurableListableBeanFactory) {
|
||||
storeComponentScanBasePackages((ConfigurableListableBeanFactory) this.beanFactory);
|
||||
} else {
|
||||
if (this.logger.isWarnEnabled()) {
|
||||
this.logger
|
||||
.warn("Unable to read @ComponentScan annotations for auto-configure");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void storeComponentScanBasePackages(
|
||||
ConfigurableListableBeanFactory beanFactory) {
|
||||
List<String> basePackages = new ArrayList<String>();
|
||||
for (String beanName : beanFactory.getBeanDefinitionNames()) {
|
||||
BeanDefinition beanDefinition = beanFactory.getBeanDefinition(beanName);
|
||||
String[] basePackagesAttribute = (String[]) beanDefinition
|
||||
.getAttribute("componentScanBasePackages");
|
||||
if (basePackagesAttribute != null) {
|
||||
basePackages.addAll(Arrays.asList(basePackagesAttribute));
|
||||
}
|
||||
AnnotationMetadata metadata = getMetadata(beanDefinition);
|
||||
basePackages.addAll(getBasePackages(metadata));
|
||||
}
|
||||
AutoConfigurationUtils.storeBasePackages(beanFactory, basePackages);
|
||||
}
|
||||
|
||||
private AnnotationMetadata getMetadata(BeanDefinition beanDefinition) {
|
||||
if (beanDefinition instanceof AbstractBeanDefinition
|
||||
&& ((AbstractBeanDefinition) beanDefinition).hasBeanClass()) {
|
||||
Class<?> beanClass = ((AbstractBeanDefinition) beanDefinition).getBeanClass();
|
||||
if (Enhancer.isEnhanced(beanClass)) {
|
||||
beanClass = beanClass.getSuperclass();
|
||||
}
|
||||
return new StandardAnnotationMetadata(beanClass, true);
|
||||
}
|
||||
String className = beanDefinition.getBeanClassName();
|
||||
if (className != null) {
|
||||
try {
|
||||
MetadataReader metadataReader = this.metadataReaderFactory
|
||||
.getMetadataReader(className);
|
||||
return metadataReader.getAnnotationMetadata();
|
||||
} catch (IOException ex) {
|
||||
if (this.logger.isDebugEnabled()) {
|
||||
this.logger.debug(
|
||||
"Could not find class file for introspecting @ComponentScan classes: "
|
||||
+ className, ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private List<String> getBasePackages(AnnotationMetadata metadata) {
|
||||
AnnotationAttributes attributes = AnnotationAttributes
|
||||
.fromMap((metadata == null ? null : metadata.getAnnotationAttributes(
|
||||
ComponentScan.class.getName(), true)));
|
||||
if (attributes != null) {
|
||||
List<String> basePackages = new ArrayList<String>();
|
||||
addAllHavingText(basePackages, attributes.getStringArray("value"));
|
||||
addAllHavingText(basePackages, attributes.getStringArray("basePackages"));
|
||||
for (String packageClass : attributes.getStringArray("basePackageClasses")) {
|
||||
basePackages.add(ClassUtils.getPackageName(packageClass));
|
||||
}
|
||||
if (basePackages.isEmpty()) {
|
||||
basePackages.add(ClassUtils.getPackageName(metadata.getClassName()));
|
||||
}
|
||||
return basePackages;
|
||||
}
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
private void addAllHavingText(List<String> list, String[] strings) {
|
||||
for (String s : strings) {
|
||||
if (StringUtils.hasText(s)) {
|
||||
list.add(s);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
|
||||
this.beanFactory = beanFactory;
|
||||
}
|
||||
}
|
@ -34,7 +34,7 @@ import org.springframework.data.jpa.repository.support.JpaRepositoryFactoryBean;
|
||||
@Configuration
|
||||
@ConditionalOnClass(JpaRepository.class)
|
||||
@ConditionalOnMissingBean(JpaRepositoryFactoryBean.class)
|
||||
@Import(JpaRepositoriesAutoConfigureRegistrar.class)
|
||||
@Import({ JpaComponentScanDetector.class, JpaRepositoriesAutoConfigureRegistrar.class })
|
||||
public class JpaRepositoriesAutoConfiguration {
|
||||
|
||||
}
|
||||
|
@ -79,12 +79,20 @@ abstract class AbstractOnBeanCondition implements Condition {
|
||||
if (this.logger.isDebugEnabled()) {
|
||||
if (!beanClasses.isEmpty()) {
|
||||
this.logger.debug("Looking for beans with class: " + beanClasses);
|
||||
this.logger.debug("Found beans with classes: " + beanClassesFound);
|
||||
if (beanClassesFound.isEmpty()) {
|
||||
this.logger.debug("Found no beans");
|
||||
} else {
|
||||
this.logger.debug("Found beans with classes: " + beanClassesFound);
|
||||
}
|
||||
}
|
||||
|
||||
if (!beanNames.isEmpty()) {
|
||||
this.logger.debug("Looking for beans with names: " + beanNames);
|
||||
this.logger.debug("Found beans with names: " + beanNamesFound);
|
||||
if (beanNamesFound.isEmpty()) {
|
||||
this.logger.debug("Found no beans");
|
||||
} else {
|
||||
this.logger.debug("Found beans with names: " + beanNamesFound);
|
||||
}
|
||||
}
|
||||
this.logger.debug("Match result is: " + result);
|
||||
}
|
||||
|
@ -16,35 +16,17 @@
|
||||
|
||||
package org.springframework.bootstrap.context.annotation;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.BeanClassLoaderAware;
|
||||
import org.springframework.beans.factory.BeanFactory;
|
||||
import org.springframework.beans.factory.BeanFactoryAware;
|
||||
import org.springframework.beans.factory.config.BeanDefinition;
|
||||
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
|
||||
import org.springframework.beans.factory.support.AbstractBeanDefinition;
|
||||
import org.springframework.cglib.proxy.Enhancer;
|
||||
import org.springframework.context.annotation.ComponentScan;
|
||||
import org.springframework.context.annotation.DeferredImportSelector;
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.core.annotation.AnnotationAttributes;
|
||||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.core.io.support.SpringFactoriesLoader;
|
||||
import org.springframework.core.type.AnnotationMetadata;
|
||||
import org.springframework.core.type.StandardAnnotationMetadata;
|
||||
import org.springframework.core.type.classreading.MetadataReader;
|
||||
import org.springframework.core.type.classreading.MetadataReaderFactory;
|
||||
import org.springframework.core.type.classreading.SimpleMetadataReaderFactory;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* {@link DeferredImportSelector} to handle {@link EnableAutoConfiguration
|
||||
@ -55,19 +37,12 @@ import org.springframework.util.StringUtils;
|
||||
*/
|
||||
@Order(Ordered.LOWEST_PRECEDENCE)
|
||||
class EnableAutoConfigurationImportSelector implements DeferredImportSelector,
|
||||
BeanClassLoaderAware, BeanFactoryAware {
|
||||
|
||||
private final Log logger = LogFactory.getLog(getClass());
|
||||
BeanClassLoaderAware {
|
||||
|
||||
private ClassLoader beanClassLoader;
|
||||
|
||||
private BeanFactory beanFactory;
|
||||
|
||||
private MetadataReaderFactory metadataReaderFactory = new SimpleMetadataReaderFactory();
|
||||
|
||||
@Override
|
||||
public String[] selectImports(AnnotationMetadata metadata) {
|
||||
storeComponentScanBasePackages();
|
||||
AnnotationAttributes attributes = AnnotationAttributes.fromMap(metadata
|
||||
.getAnnotationAttributes(EnableAutoConfiguration.class.getName(), true));
|
||||
List<String> factories = new ArrayList<String>(
|
||||
@ -77,94 +52,9 @@ class EnableAutoConfigurationImportSelector implements DeferredImportSelector,
|
||||
return factories.toArray(new String[factories.size()]);
|
||||
}
|
||||
|
||||
private void storeComponentScanBasePackages() {
|
||||
if (this.beanFactory instanceof ConfigurableListableBeanFactory) {
|
||||
storeComponentScanBasePackages((ConfigurableListableBeanFactory) this.beanFactory);
|
||||
} else {
|
||||
if (this.logger.isWarnEnabled()) {
|
||||
this.logger
|
||||
.warn("Unable to read @ComponentScan annotations for auto-configure");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void storeComponentScanBasePackages(
|
||||
ConfigurableListableBeanFactory beanFactory) {
|
||||
List<String> basePackages = new ArrayList<String>();
|
||||
for (String beanName : beanFactory.getBeanDefinitionNames()) {
|
||||
BeanDefinition beanDefinition = beanFactory.getBeanDefinition(beanName);
|
||||
String[] basePackagesAttribute = (String[]) beanDefinition
|
||||
.getAttribute("componentScanBasePackages");
|
||||
if (basePackagesAttribute != null) {
|
||||
basePackages.addAll(Arrays.asList(basePackagesAttribute));
|
||||
}
|
||||
AnnotationMetadata metadata = getMetadata(beanDefinition);
|
||||
basePackages.addAll(getBasePackages(metadata));
|
||||
}
|
||||
AutoConfigurationUtils.storeBasePackages(beanFactory, basePackages);
|
||||
}
|
||||
|
||||
private AnnotationMetadata getMetadata(BeanDefinition beanDefinition) {
|
||||
if (beanDefinition instanceof AbstractBeanDefinition
|
||||
&& ((AbstractBeanDefinition) beanDefinition).hasBeanClass()) {
|
||||
Class<?> beanClass = ((AbstractBeanDefinition) beanDefinition).getBeanClass();
|
||||
if (Enhancer.isEnhanced(beanClass)) {
|
||||
beanClass = beanClass.getSuperclass();
|
||||
}
|
||||
return new StandardAnnotationMetadata(beanClass, true);
|
||||
}
|
||||
String className = beanDefinition.getBeanClassName();
|
||||
if (className != null) {
|
||||
try {
|
||||
MetadataReader metadataReader = this.metadataReaderFactory
|
||||
.getMetadataReader(className);
|
||||
return metadataReader.getAnnotationMetadata();
|
||||
} catch (IOException ex) {
|
||||
if (this.logger.isDebugEnabled()) {
|
||||
this.logger.debug(
|
||||
"Could not find class file for introspecting @ComponentScan classes: "
|
||||
+ className, ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private List<String> getBasePackages(AnnotationMetadata metadata) {
|
||||
AnnotationAttributes attributes = AnnotationAttributes
|
||||
.fromMap((metadata == null ? null : metadata.getAnnotationAttributes(
|
||||
ComponentScan.class.getName(), true)));
|
||||
if (attributes != null) {
|
||||
List<String> basePackages = new ArrayList<String>();
|
||||
addAllHavingText(basePackages, attributes.getStringArray("value"));
|
||||
addAllHavingText(basePackages, attributes.getStringArray("basePackages"));
|
||||
for (String packageClass : attributes.getStringArray("basePackageClasses")) {
|
||||
basePackages.add(ClassUtils.getPackageName(packageClass));
|
||||
}
|
||||
if (basePackages.isEmpty()) {
|
||||
basePackages.add(ClassUtils.getPackageName(metadata.getClassName()));
|
||||
}
|
||||
return basePackages;
|
||||
}
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
private void addAllHavingText(List<String> list, String[] strings) {
|
||||
for (String s : strings) {
|
||||
if (StringUtils.hasText(s)) {
|
||||
list.add(s);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBeanClassLoader(ClassLoader classLoader) {
|
||||
this.beanClassLoader = classLoader;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
|
||||
this.beanFactory = beanFactory;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -62,7 +62,7 @@ class OnClassCondition implements Condition {
|
||||
}
|
||||
}
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("All classes found (search terminated with matches=true)");
|
||||
logger.debug("Match result is: true");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
@ -16,9 +16,9 @@
|
||||
|
||||
package org.springframework.bootstrap.context.annotation;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.context.annotation.Condition;
|
||||
import org.springframework.context.annotation.ConditionContext;
|
||||
import org.springframework.core.type.AnnotatedTypeMetadata;
|
||||
|
||||
/**
|
||||
* {@link Condition} that checks that specific beans are missing.
|
||||
@ -34,7 +34,7 @@ class OnMissingBeanCondition extends AbstractOnBeanCondition {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
|
||||
return !super.matches(context, metadata);
|
||||
protected boolean evaluate(List<String> beanClassesFound, List<String> beanNamesFound) {
|
||||
return !super.evaluate(beanClassesFound, beanNamesFound);
|
||||
}
|
||||
}
|
||||
|
@ -60,6 +60,9 @@ class OnMissingClassCondition implements Condition {
|
||||
}
|
||||
}
|
||||
}
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Match result is: true");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,58 @@
|
||||
/*
|
||||
* Copyright 2012-2013 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.bootstrap.autoconfigure.data;
|
||||
|
||||
import javax.persistence.EntityManagerFactory;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.springframework.bootstrap.autoconfigure.data.test.CityRepository;
|
||||
import org.springframework.bootstrap.autoconfigure.jdbc.EmbeddedDatabaseAutoConfiguration;
|
||||
import org.springframework.bootstrap.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration;
|
||||
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
|
||||
import org.springframework.context.annotation.ComponentScan;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.transaction.PlatformTransactionManager;
|
||||
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
|
||||
/**
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
public class JpaRepositoriesAutoConfigurationTests {
|
||||
|
||||
private AnnotationConfigApplicationContext context;
|
||||
|
||||
@Test
|
||||
public void testDefaultRepositoryConfiguration() throws Exception {
|
||||
this.context = new AnnotationConfigApplicationContext();
|
||||
this.context.register(TestConfiguration.class,
|
||||
EmbeddedDatabaseAutoConfiguration.class,
|
||||
JpaRepositoriesAutoConfiguration.class,
|
||||
HibernateJpaAutoConfiguration.class);
|
||||
this.context.refresh();
|
||||
assertNotNull(this.context.getBean(CityRepository.class));
|
||||
assertNotNull(this.context.getBean(PlatformTransactionManager.class));
|
||||
assertNotNull(this.context.getBean(EntityManagerFactory.class));
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@ComponentScan("org.springframework.bootstrap.autoconfigure.data.test")
|
||||
protected static class TestConfiguration {
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,60 @@
|
||||
package org.springframework.bootstrap.autoconfigure.data.test;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
import javax.persistence.Column;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.GeneratedValue;
|
||||
import javax.persistence.Id;
|
||||
|
||||
@Entity
|
||||
public class City implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Id
|
||||
@GeneratedValue
|
||||
private Long id;
|
||||
|
||||
@Column(nullable = false)
|
||||
private String name;
|
||||
|
||||
@Column(nullable = false)
|
||||
private String state;
|
||||
|
||||
@Column(nullable = false)
|
||||
private String country;
|
||||
|
||||
@Column(nullable = false)
|
||||
private String map;
|
||||
|
||||
protected City() {
|
||||
}
|
||||
|
||||
public City(String name, String country) {
|
||||
super();
|
||||
this.name = name;
|
||||
this.country = country;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
public String getState() {
|
||||
return this.state;
|
||||
}
|
||||
|
||||
public String getCountry() {
|
||||
return this.country;
|
||||
}
|
||||
|
||||
public String getMap() {
|
||||
return this.map;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return getName() + "," + getState() + "," + getCountry();
|
||||
}
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
package org.springframework.bootstrap.autoconfigure.data.test;
|
||||
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.repository.Repository;
|
||||
|
||||
public interface CityRepository extends Repository<City, Long> {
|
||||
|
||||
Page<City> findAll(Pageable pageable);
|
||||
|
||||
Page<City> findByNameLikeAndCountryLikeAllIgnoringCase(String name, String country,
|
||||
Pageable pageable);
|
||||
|
||||
City findByNameAndCountryAllIgnoringCase(String name, String country);
|
||||
|
||||
}
|
Loading…
Reference in New Issue
Block a user