DATACMNS-1368 - Add support for deferred repository initialization.

Both the XML and annotation based configuration sources now support a bootstrap mode configuration property that allow to configure whether repositories are eagerly initialized unless declared as lazy, initialized in a deferred way (just before the application context finishs bootstrapping) or entirely lazy (upon first usage, i.e. a method invocation).

We now register a custom AutowireCandidateResolver that will consider all injection points of lazy repositories lazy too. This will prevent them to accidentally trigger downstream infrastructure initialization.
This commit is contained in:
Oliver Gierke
2018-08-09 08:15:11 +02:00
parent 60bbc59054
commit 5da6cf20a2
17 changed files with 728 additions and 6 deletions

View File

@@ -65,6 +65,7 @@ public class AnnotationRepositoryConfigurationSource extends RepositoryConfigura
private static final String REPOSITORY_FACTORY_BEAN_CLASS = "repositoryFactoryBeanClass";
private static final String REPOSITORY_BASE_CLASS = "repositoryBaseClass";
private static final String CONSIDER_NESTED_REPOSITORIES = "considerNestedRepositories";
private static final String BOOTSTRAP_MODE = "bootstrapMode";
private final AnnotationMetadata configMetadata;
private final AnnotationMetadata enableAnnotationMetadata;
@@ -341,6 +342,20 @@ public class AnnotationRepositoryConfigurationSource extends RepositoryConfigura
return hasExplicitFilters;
}
/*
* (non-Javadoc)
* @see org.springframework.data.repository.config.RepositoryConfigurationSource#getBootstrapMode()
*/
@Override
public BootstrapMode getBootstrapMode() {
try {
return attributes.getEnum(BOOTSTRAP_MODE);
} catch (IllegalArgumentException o_O) {
return BootstrapMode.DEFAULT;
}
}
/**
* Safely reads the {@code pattern} attribute from the given {@link AnnotationAttributes} and returns an empty list if
* the attribute is not present.

View File

@@ -0,0 +1,45 @@
/*
* Copyright 2018 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.data.repository.config;
/**
* Enumeration to define in which way repositories are bootstrapped.
*
* @author Oliver Gierke
* @see RepositoryConfigurationSource#getBootstrapMode()
* @since 2.1
* @soundtrack Dave Matthews Band - She (Come Tomorrow)
*/
public enum BootstrapMode {
/**
* Repository proxies are instantiated eagerly, just like any other Spring bean, except explicitly marked as lazy.
* Thus, injection into repository clients will trigger initialization.
*/
DEFAULT,
/**
* Repository bean definitions are considered lazy and clients will get repository proxies injected that will
* initialize on first access. Repository initialization is triggered on application context bootstrap completion.
*/
DEFERRED,
/**
* Repository bean definitions are considered lazy, lazily inject and only initialized on first use, i.e. the
* application might have fully started without the repositories initialized.
*/
LAZY;
}

View File

@@ -170,7 +170,7 @@ public class DefaultRepositoryConfiguration<T extends RepositoryConfigurationSou
*/
@Override
public boolean isLazyInit() {
return definition.isLazyInit();
return definition.isLazyInit() || !configurationSource.getBootstrapMode().equals(BootstrapMode.DEFAULT);
}
/*

View File

@@ -0,0 +1,62 @@
/*
* Copyright 2018 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.data.repository.config;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.ListableBeanFactory;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.core.Ordered;
import org.springframework.data.repository.Repository;
/**
* {@link ApplicationListener} to trigger the initialization of Spring Data repositories right before the application
* context is started.
*
* @author Oliver Gierke
* @since 2.1
* @soundtrack Dave Matthews Band - Here On Out (Come Tomorrow)
*/
@Slf4j
@RequiredArgsConstructor
class DeferredRepositoryInitializationListener implements ApplicationListener<ContextRefreshedEvent>, Ordered {
private final @NonNull ListableBeanFactory beanFactory;
/*
* (non-Javadoc)
* @see org.springframework.context.ApplicationListener#onApplicationEvent(org.springframework.context.ApplicationEvent)
*/
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
LOG.debug("Triggering deferred initialization of Spring Data repositories…");
beanFactory.getBeansOfType(Repository.class);
}
/*
* (non-Javadoc)
* @see org.springframework.core.Ordered#getOrder()
*/
@Override
public int getOrder() {
return Ordered.HIGHEST_PRECEDENCE;
}
}

View File

@@ -101,6 +101,7 @@ class RepositoryBeanDefinitionBuilder {
builder.addConstructorArgValue(configuration.getRepositoryInterface());
builder.addPropertyValue("queryLookupStrategyKey", configuration.getQueryLookupStrategyKey());
builder.addPropertyValue("lazyInit", configuration.isLazyInit());
builder.setLazyInit(configuration.isLazyInit());
configuration.getRepositoryBaseClassName()//
.ifPresent(it -> builder.addPropertyValue("repositoryBaseClass", it));

View File

@@ -15,18 +15,26 @@
*/
package org.springframework.data.repository.config;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import javax.annotation.Nullable;
import org.springframework.beans.factory.config.DependencyDescriptor;
import org.springframework.beans.factory.parsing.BeanComponentDefinition;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.AutowireCandidateResolver;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.context.annotation.ContextAnnotationAutowireCandidateResolver;
import org.springframework.core.env.Environment;
import org.springframework.core.env.EnvironmentCapable;
import org.springframework.core.env.StandardEnvironment;
@@ -49,6 +57,7 @@ public class RepositoryConfigurationDelegate {
private static final String REPOSITORY_REGISTRATION = "Spring Data {} - Registering repository: {} - Interface: {} - Factory: {}";
private static final String MULTIPLE_MODULES = "Multiple Spring Data modules found, entering strict repository configuration mode!";
private static final String NON_DEFAULT_AUTOWIRE_CANDIDATE_RESOLVER = "Non-default AutowireCandidateResolver ({}) detected. Skipping the registration of LazyRepositoryInjectionPointResolver. Lazy repository injection will not be working!";
static final String FACTORY_BEAN_OBJECT_TYPE = "factoryBeanObjectType";
@@ -122,9 +131,13 @@ public class RepositoryConfigurationDelegate {
configurationSource.getBasePackages().stream().collect(Collectors.joining(", ")));
}
Map<String, RepositoryConfiguration<?>> configurations = new HashMap<>();
for (RepositoryConfiguration<? extends RepositoryConfigurationSource> configuration : extension
.getRepositoryConfigurations(configurationSource, resourceLoader, inMultiStoreMode)) {
configurations.put(configuration.getRepositoryInterface(), configuration);
BeanDefinitionBuilder definitionBuilder = builder.build(configuration);
extension.postProcess(definitionBuilder, configurationSource);
@@ -149,6 +162,8 @@ public class RepositoryConfigurationDelegate {
definitions.add(new BeanComponentDefinition(beanDefinition, beanName));
}
potentiallyLazifyRepositories(configurations, registry, configurationSource.getBootstrapMode());
if (LOG.isDebugEnabled()) {
LOG.debug("Finished repository scanning.");
}
@@ -156,6 +171,45 @@ public class RepositoryConfigurationDelegate {
return definitions;
}
/**
* Registers a {@link LazyRepositoryInjectionPointResolver} over the default
* {@link ContextAnnotationAutowireCandidateResolver} to make injection points of lazy repositories lazy, too. Will
* augment the {@link LazyRepositoryInjectionPointResolver}'s configuration if there already is one configured.
*
* @param configurations must not be {@literal null}.
* @param registry must not be {@literal null}.
*/
private static void potentiallyLazifyRepositories(Map<String, RepositoryConfiguration<?>> configurations,
BeanDefinitionRegistry registry, BootstrapMode mode) {
if (!DefaultListableBeanFactory.class.isInstance(registry) || mode.equals(BootstrapMode.DEFAULT)) {
return;
}
DefaultListableBeanFactory beanFactory = DefaultListableBeanFactory.class.cast(registry);
AutowireCandidateResolver resolver = beanFactory.getAutowireCandidateResolver();
if (!Arrays.asList(ContextAnnotationAutowireCandidateResolver.class, LazyRepositoryInjectionPointResolver.class)
.contains(resolver.getClass())) {
LOG.warn(NON_DEFAULT_AUTOWIRE_CANDIDATE_RESOLVER, resolver.getClass().getName());
return;
}
AutowireCandidateResolver newResolver = LazyRepositoryInjectionPointResolver.class.isInstance(resolver) //
? LazyRepositoryInjectionPointResolver.class.cast(resolver).withAdditionalConfigurations(configurations) //
: new LazyRepositoryInjectionPointResolver(configurations);
beanFactory.setAutowireCandidateResolver(newResolver);
if (mode.equals(BootstrapMode.DEFERRED)) {
beanFactory.registerSingleton(DeferredRepositoryInitializationListener.class.getName(),
new DeferredRepositoryInitializationListener(beanFactory));
}
}
/**
* Scans {@code repository.support} packages for implementations of {@link RepositoryFactorySupport}. Finding more
* than a single type is considered a multi-store configuration scenario which will trigger stricter repository
@@ -174,4 +228,58 @@ public class RepositoryConfigurationDelegate {
return multipleModulesFound;
}
/**
* Customer {@link ContextAnnotationAutowireCandidateResolver} that also considers all injection points for lazy
* repositories lazy.
*
* @author Oliver Gierke
* @since 2.1
*/
@Slf4j
@RequiredArgsConstructor
static class LazyRepositoryInjectionPointResolver extends ContextAnnotationAutowireCandidateResolver {
private final Map<String, RepositoryConfiguration<?>> configurations;
/**
* Returns a new {@link LazyRepositoryInjectionPointResolver} that will have its configurations augmented with the
* given ones.
*
* @param configurations must not be {@literal null}.
* @return
*/
LazyRepositoryInjectionPointResolver withAdditionalConfigurations(
Map<String, RepositoryConfiguration<?>> configurations) {
Map<String, RepositoryConfiguration<?>> map = new HashMap<>(this.configurations);
map.putAll(configurations);
return new LazyRepositoryInjectionPointResolver(map);
}
/*
* (non-Javadoc)
* @see org.springframework.context.annotation.ContextAnnotationAutowireCandidateResolver#isLazy(org.springframework.beans.factory.config.DependencyDescriptor)
*/
@Override
protected boolean isLazy(DependencyDescriptor descriptor) {
Class<?> type = descriptor.getDependencyType();
RepositoryConfiguration<?> configuration = configurations.get(type.getName());
if (configuration == null) {
return super.isLazy(descriptor);
}
boolean lazyInit = configuration.isLazyInit();
if (lazyInit) {
LOG.debug("Creating lazy injection proxy for {}…", configuration.getRepositoryInterface());
}
return lazyInit;
}
}
}

View File

@@ -22,6 +22,7 @@ import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.function.Supplier;
import javax.annotation.Nullable;
@@ -193,13 +194,17 @@ public abstract class RepositoryConfigurationExtensionSupport implements Reposit
* {@link BeanDefinitionRegistry}. For {@link BeanDefinition}s to be registered once-and-only-once for all
* configuration elements (annotation or XML), prefer calling
* {@link #registerIfNotAlreadyRegistered(AbstractBeanDefinition, BeanDefinitionRegistry, String, Object)} with a
* dedicated bean name to avoid the bead definition being registered multiple times. *
* dedicated bean name to avoid the bead definition being registered multiple times.
*
* @param registry must not be {@literal null}.
* @param bean must not be {@literal null}.
* @param source must not be {@literal null}.
* @return the bean name generated for the given {@link BeanDefinition}
* @deprecated since 2.1, use
* {@link #registerWithSourceAndGeneratedBeanName(AbstractBeanDefinition, BeanDefinitionRegistry, Object)}
* instead.
*/
@Deprecated
public static String registerWithSourceAndGeneratedBeanName(BeanDefinitionRegistry registry,
AbstractBeanDefinition bean, Object source) {
@@ -211,6 +216,29 @@ public abstract class RepositoryConfigurationExtensionSupport implements Reposit
return beanName;
}
/**
* Sets the given source on the given {@link AbstractBeanDefinition} and registers it inside the given
* {@link BeanDefinitionRegistry}. For {@link BeanDefinition}s to be registered once-and-only-once for all
* configuration elements (annotation or XML), prefer calling
* {@link #registerIfNotAlreadyRegistered(AbstractBeanDefinition, BeanDefinitionRegistry, String, Object)} with a
* dedicated bean name to avoid the bead definition being registered multiple times.
*
* @param bean must not be {@literal null}.
* @param registry must not be {@literal null}.
* @param source must not be {@literal null}.
* @return the bean name generated for the given {@link BeanDefinition}
*/
public static String registerWithSourceAndGeneratedBeanName(AbstractBeanDefinition bean,
BeanDefinitionRegistry registry, Object source) {
bean.setSource(source);
String beanName = generateBeanName(bean, registry);
registry.registerBeanDefinition(beanName, bean);
return beanName;
}
/**
* Registers the given {@link AbstractBeanDefinition} with the given registry with the given bean name unless the
* registry already contains a bean with that name.
@@ -219,18 +247,62 @@ public abstract class RepositoryConfigurationExtensionSupport implements Reposit
* @param registry must not be {@literal null}.
* @param beanName must not be {@literal null} or empty.
* @param source must not be {@literal null}.
* @deprecated since 2.1, prefer
* {@link #registerIfNotAlreadyRegistered(Supplier, BeanDefinitionRegistry, String, Object)}
*/
@Deprecated
public static void registerIfNotAlreadyRegistered(AbstractBeanDefinition bean, BeanDefinitionRegistry registry,
String beanName, Object source) {
registerIfNotAlreadyRegistered(() -> bean, registry, beanName, source);
}
/**
* Registers the {@link AbstractBeanDefinition} produced by the given {@link Supplier} with the given registry with
* the given bean name unless the registry already contains a bean with that name.
*
* @param supplier must not be {@literal null}.
* @param registry must not be {@literal null}.
* @param beanName must not be {@literal null} or empty.
* @param source must not be {@literal null}.
* @since 2.1
*/
public static void registerIfNotAlreadyRegistered(Supplier<AbstractBeanDefinition> supplier,
BeanDefinitionRegistry registry, String beanName, Object source) {
if (registry.containsBeanDefinition(beanName)) {
return;
}
AbstractBeanDefinition bean = supplier.get();
bean.setSource(source);
registry.registerBeanDefinition(beanName, bean);
}
/**
* Registers the {@link AbstractBeanDefinition} produced by the given {@link Supplier} as lazy bean definition with
* the given registry with the given bean name unless the registry already contains a bean with that name.
*
* @param supplier must not be {@literal null}.
* @param registry must not be {@literal null}.
* @param beanName must not be {@literal null} or empty.
* @param source must not be {@literal null}.
* @since 2.1
*/
public static void registerLazyIfNotAlreadyRegistered(Supplier<AbstractBeanDefinition> supplier,
BeanDefinitionRegistry registry, String beanName, Object source) {
if (registry.containsBeanDefinition(beanName)) {
return;
}
AbstractBeanDefinition definition = supplier.get();
definition.setSource(source);
definition.setLazyInit(true);
registry.registerBeanDefinition(beanName, definition);
}
/**
* Returns whether the given {@link BeanDefinitionRegistry} already contains a bean of the given type assuming the
* bean name has been auto-generated.

View File

@@ -137,4 +137,12 @@ public interface RepositoryConfigurationSource {
* @since 2.1
*/
ImplementationDetectionConfiguration toImplementationDetectionConfiguration(MetadataReaderFactory factory);
/**
* Defines the repository {@link BootstrapMode} to be used.
*
* @return
* @since 2.1
*/
BootstrapMode getBootstrapMode();
}

View File

@@ -16,6 +16,7 @@
package org.springframework.data.repository.config;
import java.util.Collection;
import java.util.Locale;
import java.util.Optional;
import org.springframework.beans.factory.xml.ParserContext;
@@ -50,6 +51,7 @@ public class XmlRepositoryConfigurationSource extends RepositoryConfigurationSou
private static final String REPOSITORY_FACTORY_BEAN_CLASS_NAME = "factory-class";
private static final String REPOSITORY_BASE_CLASS_NAME = "base-class";
private static final String CONSIDER_NESTED_REPOSITORIES = "consider-nested-repositories";
private static final String BOOTSTRAP_MODE = "bootstrap-mode";
private final Element element;
private final ParserContext context;
@@ -211,4 +213,18 @@ public class XmlRepositoryConfigurationSource extends RepositoryConfigurationSou
public boolean usesExplicitFilters() {
return !(this.includeFilters.isEmpty() && this.excludeFilters.isEmpty());
}
/*
* (non-Javadoc)
* @see org.springframework.data.repository.config.RepositoryConfigurationSource#getBootstrapMode()
*/
@Override
public BootstrapMode getBootstrapMode() {
String attribute = element.getAttribute(BOOTSTRAP_MODE);
return StringUtils.hasText(attribute) //
? BootstrapMode.valueOf(attribute.toUpperCase(Locale.US)) //
: BootstrapMode.DEFAULT;
}
}

View File

@@ -5,4 +5,5 @@ http\://www.springframework.org/schema/data/repository/spring-repository-1.6.xsd
http\://www.springframework.org/schema/data/repository/spring-repository-1.7.xsd=org/springframework/data/repository/config/spring-repository-1.7.xsd
http\://www.springframework.org/schema/data/repository/spring-repository-1.8.xsd=org/springframework/data/repository/config/spring-repository-1.8.xsd
http\://www.springframework.org/schema/data/repository/spring-repository-1.11.xsd=org/springframework/data/repository/config/spring-repository-1.11.xsd
http\://www.springframework.org/schema/data/repository/spring-repository.xsd=org/springframework/data/repository/config/spring-repository-1.11.xsd
http\://www.springframework.org/schema/data/repository/spring-repository-2.1.xsd=org/springframework/data/repository/config/spring-repository-2.1.xsd
http\://www.springframework.org/schema/data/repository/spring-repository.xsd=org/springframework/data/repository/config/spring-repository-2.1.xsd

View File

@@ -0,0 +1,272 @@
<?xml version="1.0" encoding="UTF-8" ?>
<xsd:schema xmlns="http://www.springframework.org/schema/data/repository"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:tool="http://www.springframework.org/schema/tool"
xmlns:context="http://www.springframework.org/schema/context"
targetNamespace="http://www.springframework.org/schema/data/repository"
elementFormDefault="qualified" attributeFormDefault="unqualified">
<xsd:import namespace="http://www.springframework.org/schema/tool" />
<xsd:import namespace="http://www.springframework.org/schema/context"
schemaLocation="http://www.springframework.org/schema/context/spring-context.xsd" />
<xsd:complexType name="repositories">
<xsd:sequence>
<xsd:element name="include-filter" type="context:filterType" minOccurs="0" maxOccurs="unbounded">
<xsd:annotation>
<xsd:documentation><![CDATA[
Controls which eligible types to include for component scanning.
]]></xsd:documentation>
</xsd:annotation>
</xsd:element>
<xsd:element name="exclude-filter" type="context:filterType" minOccurs="0" maxOccurs="unbounded">
<xsd:annotation>
<xsd:documentation><![CDATA[
Controls which eligible types to exclude for component scanning.
]]></xsd:documentation>
</xsd:annotation>
</xsd:element>
</xsd:sequence>
<xsd:attribute name="base-package" type="xsd:string" use="required">
<xsd:annotation>
<xsd:documentation><![CDATA[
Defines the base package where the DAO interface will be tried to be detected.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="named-queries-location" type="xsd:string">
<xsd:annotation>
<xsd:documentation><![CDATA[
Defines the location to look for a Properties file containing externally defined queries.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="consider-nested-repositories" type="xsd:boolean" default="false">
<xsd:annotation>
<xsd:documentation><![CDATA[
Controls whether nested repository interface definitions should be considered.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
</xsd:complexType>
<xsd:complexType name="populator">
<xsd:attribute name="locations" type="xsd:string" use="required">
<xsd:annotation>
<xsd:documentation>
Where to find the files to read the objects from the repository shall be populated with.
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="id" type="xsd:string" />
</xsd:complexType>
<!-- XML (Unmarshaller) initializer -->
<xsd:element name="unmarshaller-populator">
<xsd:complexType>
<xsd:complexContent>
<xsd:extension base="populator">
<xsd:attribute name="unmarshaller-ref" type="unmarshallerRefType" use="required" />
</xsd:extension>
</xsd:complexContent>
</xsd:complexType>
</xsd:element>
<xsd:simpleType name="unmarshallerRefType">
<xsd:annotation>
<xsd:appinfo>
<tool:expected-type type="org.springframework.oxm.Unmarshaller" />
</xsd:appinfo>
</xsd:annotation>
<xsd:union memberTypes="xsd:string" />
</xsd:simpleType>
<!-- JSON (Jackson2) initializer -->
<xsd:element name="jackson2-populator">
<xsd:complexType>
<xsd:complexContent>
<xsd:extension base="populator">
<xsd:attribute name="object-mapper-ref" type="objectMapper2Type" />
</xsd:extension>
</xsd:complexContent>
</xsd:complexType>
</xsd:element>
<xsd:simpleType name="objectMapper2Type">
<xsd:annotation>
<xsd:appinfo>
<tool:expected-type type="com.fasterxml.jackson.databind.ObjectMapper" />
</xsd:appinfo>
</xsd:annotation>
<xsd:union memberTypes="xsd:string" />
</xsd:simpleType>
<xsd:attributeGroup name="repository-attributes">
<xsd:attribute name="repository-impl-postfix" type="xsd:string"/>
<xsd:attribute name="query-lookup-strategy" type="query-strategy"/>
<xsd:attribute name="factory-class" type="classType">
<xsd:annotation>
<xsd:documentation>Deprecated as of version 1.11. Configure a dedicated base-class instead.</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="base-class" type="classType" />
<xsd:attribute name="bootstrap-mode" type="bootstrap-mode" />
</xsd:attributeGroup>
<xsd:attributeGroup name="transactional-repository-attributes">
<xsd:attributeGroup ref="repository-attributes"/>
<xsd:attribute name="transaction-manager-ref" type="transactionManagerRef"/>
</xsd:attributeGroup>
<xsd:attributeGroup name="auditing-attributes">
<xsd:attribute name="auditor-aware-ref">
<xsd:annotation>
<xsd:documentation><![CDATA[
References a bean of type AuditorAware to represent the current principal.
]]></xsd:documentation>
<xsd:appinfo>
<tool:annotation kind="ref">
<tool:assignable-to type="org.springframework.data.domain.AuditorAware" />
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="set-dates" default="true" type="xsd:boolean">
<xsd:annotation>
<xsd:documentation><![CDATA[
Configures whether the creation and modification dates are set.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="date-time-provider-ref">
<xsd:annotation>
<xsd:documentation><![CDATA[
Configures a DateTimeProvider that allows customizing which DateTime shall be used for setting
creation and modification dates.
]]></xsd:documentation>
<xsd:appinfo>
<tool:annotation kind="ref">
<tool:assignable-to type="org.springframework.data.jpa.domain.support.DateTimeProvider" />
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="modify-on-creation" default="true" type="xsd:boolean">
<xsd:annotation>
<xsd:documentation><![CDATA[
Configures whether the entity shall be marked as modified on creation.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
</xsd:attributeGroup>
<xsd:simpleType name="query-strategy">
<xsd:annotation>
<xsd:documentation><![CDATA[
Determines the way query methods are being executed.
]]></xsd:documentation>
</xsd:annotation>
<xsd:restriction base="xsd:string">
<xsd:enumeration value="create-if-not-found">
<xsd:annotation>
<xsd:documentation><![CDATA[
Tries to find a named query but creates a custom query if
none can be found. (Default)
]]></xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="create">
<xsd:annotation>
<xsd:documentation><![CDATA[
Creates a query from the query method's name.
]]></xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="use-declared-query">
<xsd:annotation>
<xsd:documentation><![CDATA[
Uses a declared query to execute. Fails if no
declared query (either through named query or through @Query)
is defined.
]]></xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
</xsd:restriction>
</xsd:simpleType>
<xsd:simpleType name="customImplementationReference">
<xsd:annotation>
<xsd:appinfo>
<tool:annotation kind="ref"/>
</xsd:appinfo>
</xsd:annotation>
<xsd:union memberTypes="xsd:string"/>
</xsd:simpleType>
<xsd:simpleType name="entityManagerFactoryRef">
<xsd:annotation>
<xsd:appinfo>
<tool:annotation kind="ref">
<tool:assignable-to type="org.springframework.orm.jpa.AbstractEntityManagerFactoryBean"/>
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>
<xsd:union memberTypes="xsd:string"/>
</xsd:simpleType>
<xsd:simpleType name="transactionManagerRef">
<xsd:annotation>
<xsd:appinfo>
<tool:annotation kind="ref">
<tool:assignable-to type="org.springframework.transaction.PlatformTransactionManager"/>
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>
<xsd:union memberTypes="xsd:string"/>
</xsd:simpleType>
<xsd:simpleType name="classType">
<xsd:annotation>
<xsd:appinfo>
<tool:annotation kind="direct">
<tool:expected-type type="java.lang.Class"/>
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>
<xsd:union memberTypes="xsd:string"/>
</xsd:simpleType>
<xsd:simpleType name="bootstrap-mode">
<xsd:annotation>
<xsd:documentation><![CDATA[
Defines the bootstrap strategy for repositories.
]]></xsd:documentation>
</xsd:annotation>
<xsd:restriction base="xsd:string">
<xsd:enumeration value="default">
<xsd:annotation>
<xsd:documentation><![CDATA[
Repository proxies are instantiated eagerly, just like any other Spring bean, except explicitly marked as lazy. Thus, injection into repository clients will trigger initialization.
]]></xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="deferred">
<xsd:annotation>
<xsd:documentation><![CDATA[
Repository bean definitions are considered lazy and clients will get repository proxies injected that will initialize on first access. Repository initialization is triggered on application context bootstrap completion.
]]></xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="lazy">
<xsd:annotation>
<xsd:documentation><![CDATA[
Repository bean definitions are considered lazy, lazily inject and only initialized on first use, i.e. the application might have fully started without the repositories initialized.
]]></xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
</xsd:restriction>
</xsd:simpleType>
</xsd:schema>

View File

@@ -23,6 +23,7 @@ import lombok.Value;
import java.util.Optional;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
@@ -45,6 +46,11 @@ public class DefaultRepositoryConfigurationUnitTests {
RepositoryConfigurationExtension extension = new SimplerRepositoryConfigurationExtension("factory", "module");
@Before
public void before() {
when(source.getBootstrapMode()).thenReturn(BootstrapMode.DEFAULT);
}
@Test
public void supportsBasicConfiguration() {

View File

@@ -51,4 +51,6 @@ public @interface EnableRepositories {
boolean considerNestedRepositories() default false;
boolean limitImplementationBasePackages() default true;
BootstrapMode bootstrapMode() default BootstrapMode.DEFAULT;
}

View File

@@ -49,7 +49,7 @@ public class RepositoryComponentProviderUnitTests {
RepositoryComponentProvider provider = new RepositoryComponentProvider(Collections.emptyList(), registry);
Set<BeanDefinition> components = provider.findCandidateComponents("org.springframework.data.repository.sample");
assertThat(components).hasSize(3);
assertThat(components).hasSize(4);
assertThat(components).extracting(BeanDefinition::getBeanClassName)
.contains(SampleAnnotatedRepository.class.getName());
}

View File

@@ -15,18 +15,29 @@
*/
package org.springframework.data.repository.config;
import static org.assertj.core.api.Assertions.assertThat;
import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*;
import static org.junit.Assert.assertThat;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
import org.springframework.aop.TargetSource;
import org.springframework.aop.framework.Advised;
import org.springframework.beans.factory.ListableBeanFactory;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.parsing.BeanComponentDefinition;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.ComponentScan.Filter;
import org.springframework.context.annotation.FilterType;
import org.springframework.context.support.GenericApplicationContext;
import org.springframework.core.env.StandardEnvironment;
import org.springframework.core.type.StandardAnnotationMetadata;
import org.springframework.data.repository.config.RepositoryConfigurationDelegate.LazyRepositoryInjectionPointResolver;
import org.springframework.data.repository.sample.AddressRepository;
import org.springframework.data.repository.sample.AddressRepositoryClient;
import org.springframework.data.repository.sample.ProductRepository;
/**
@@ -35,7 +46,7 @@ import org.springframework.data.repository.sample.ProductRepository;
* @author Oliver Gierke
* @soundtrack Richard Spaven - Tribute (Whole Other*)
*/
@RunWith(MockitoJUnitRunner.class)
@RunWith(MockitoJUnitRunner.Silent.class)
public class RepositoryConfigurationDelegateUnitTests {
@Mock RepositoryConfigurationExtension extension;
@@ -61,6 +72,51 @@ public class RepositoryConfigurationDelegateUnitTests {
}
}
@Test // DATACMNS-1368
public void registersLazyAutowireCandidateResolver() {
assertLazyRepositoryBeanSetup(LazyConfig.class);
}
@Test // DATACMNS-1368
public void registersDeferredRepositoryInitializationListener() {
ListableBeanFactory beanFactory = assertLazyRepositoryBeanSetup(DeferredConfig.class);
assertThat(beanFactory.getBeanNamesForType(DeferredRepositoryInitializationListener.class)).isNotEmpty();
}
private static ListableBeanFactory assertLazyRepositoryBeanSetup(Class<?> configClass) {
StandardEnvironment environment = new StandardEnvironment();
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(configClass);
assertThat(context.getDefaultListableBeanFactory().getAutowireCandidateResolver())
.isInstanceOf(LazyRepositoryInjectionPointResolver.class);
AddressRepositoryClient client = context.getBean(AddressRepositoryClient.class);
AddressRepository repository = client.getRepository();
assertThat(Advised.class.isInstance(repository)).isTrue();
TargetSource targetSource = Advised.class.cast(repository).getTargetSource();
assertThat(targetSource).isNotNull();
return context.getDefaultListableBeanFactory();
}
@EnableRepositories(basePackageClasses = ProductRepository.class)
static class TestConfig {}
@ComponentScan(basePackageClasses = AddressRepository.class)
@EnableRepositories(basePackageClasses = AddressRepository.class,
includeFilters = @Filter(type = FilterType.ASSIGNABLE_TYPE, classes = AddressRepository.class),
bootstrapMode = BootstrapMode.LAZY)
static class LazyConfig {}
@ComponentScan(basePackageClasses = AddressRepository.class)
@EnableRepositories(basePackageClasses = AddressRepository.class,
includeFilters = @Filter(type = FilterType.ASSIGNABLE_TYPE, classes = AddressRepository.class),
bootstrapMode = BootstrapMode.DEFERRED)
static class DeferredConfig {}
}

View File

@@ -0,0 +1,27 @@
/*
* Copyright 2018 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.data.repository.sample;
import org.springframework.data.repository.Repository;
import org.springframework.data.repository.sample.AddressRepository.Address;
/**
* @author Oliver Gierke
*/
public interface AddressRepository extends Repository<Address, Long> {
static class Address {}
}

View File

@@ -0,0 +1,31 @@
/*
* Copyright 2018 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.data.repository.sample;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
/**
* @author Oliver Gierke
*/
@Component
@RequiredArgsConstructor
public class AddressRepositoryClient {
private final @Getter AddressRepository repository;
}