Browse Source

Refine `repositoryBaseClass` property configuration for `@Enable…Repositories` repository factory definitions.

Also extend documentation for repository factory and factory bean customization.

Closes #3423
pull/3426/head
Mark Paluch 2 weeks ago
parent
commit
7cb6301676
No known key found for this signature in database
GPG Key ID: 55BC6374BAA9D973
  1. 37
      src/main/antora/modules/ROOT/pages/repositories/custom-implementations.adoc
  2. 7
      src/main/java/org/springframework/data/repository/config/DefaultRepositoryConfiguration.java
  3. 4
      src/main/java/org/springframework/data/repository/config/RepositoryBeanDefinitionReader.java
  4. 5
      src/main/java/org/springframework/data/repository/core/support/RepositoryFactoryBeanSupport.java
  5. 10
      src/main/java/org/springframework/data/repository/core/support/RepositoryFactorySupport.java
  6. 4
      src/test/java/org/springframework/data/repository/config/RepositoryBeanDefinitionReaderTests.java

37
src/main/antora/modules/ROOT/pages/repositories/custom-implementations.adoc

@ -328,6 +328,7 @@ In this case we use the repository domain type to identify the name of the index @@ -328,6 +328,7 @@ In this case we use the repository domain type to identify the name of the index
Exposing invocation metadata is costly, hence it is disabled by default.
To access `RepositoryMethodContext.getContext()` you need to advise the repository factory responsible for creating the actual repository to expose method metadata.
[[expose-repository-metadata]]
.Expose Repository Metadata
[tabs]
======
@ -471,3 +472,39 @@ XML:: @@ -471,3 +472,39 @@ XML::
======
====
[[repositories.customize-repository-factory]]
== Customizing the Repository Factory
Customizing the javadoc:org.springframework.data.repository.core.support.RepositoryFactorySupport[repository factory] through javadoc:org.springframework.data.repository.core.support.RepositoryFactoryCustomizer[] provides direct access to components involved with repository instance creation.
This mechanism is useful when you want to adjust selected aspects of proxy creation without introducing a fully custom repository factory bean.
The following example, demonstrates registering additional listeners and proxy advisors:
[source,java]
----
factoryBean.addRepositoryFactoryCustomizer(repositoryFactory -> {
repositoryFactory.addInvocationListener(…);
repositoryFactory.addQueryCreationListener(…);
repositoryFactory.addRepositoryProxyPostProcessor((factory, repositoryInformation) -> factory.addAdvice(…)));
});
----
A `RepositoryFactoryCustomizer` is associated with a particular repository factory bean, ideally through `BeanPostProcessor` so that only specific repositories are affected.
Note that customizer beans are not applied automatically to prevent unwanted wiring that become especially relevant in multi-repository arrangements or when using multiple Spring Data modules.
[[repositories.customize-repository-factory-bean]]
== Customize the Repository Factory Bean
The most powerful approach to customize repository creation is to provide a custom repository factory bean, typically a subclass of javadoc:org.springframework.data.repository.core.support.RepositoryFactoryBeanSupport[], javadoc:org.springframework.data.repository.core.support.TransactionalRepositoryFactoryBeanSupport[] or the store-specific repository factory bean.
Customizing the repository factory bean allows you to change repository creation entirely with full access to the underlying repository factory.
Note that this approach requires the most effort and is typically only needed when you want to change core aspects of repository creation.
Also, you need to take in consideration repository metadata derivation that is used to identify query methods, base implementation methods and custom implementations.
The following summary outlines the key aspects:
* `repositoryBaseClass`: The repository base class defines which methods are implemented by the base class and which methods require additional handling through aspects or custom implementations.
* `repositoryFragmentsContributor`: A javadoc:org.springframework.data.repository.core.support.RepositoryFragmentsContributor[] allows contributions to repository composition after all standard fragments have been collected.
Store modules use this mechanism to add features such as Querydsl or Query-by-Example support.
It also serves as an SPI for third-party extensions.
* `exposeMetadata`: Controls whether <<expose-repository-metadata,invocation metadata>> is available through `RepositoryMethodContext.getContext()`.

7
src/main/java/org/springframework/data/repository/config/DefaultRepositoryConfiguration.java

@ -17,6 +17,7 @@ package org.springframework.data.repository.config; @@ -17,6 +17,7 @@ package org.springframework.data.repository.config;
import java.util.Optional;
import org.jspecify.annotations.NonNull;
import org.jspecify.annotations.Nullable;
import org.springframework.beans.factory.config.BeanDefinition;
@ -114,8 +115,7 @@ public class DefaultRepositoryConfiguration<T extends RepositoryConfigurationSou @@ -114,8 +115,7 @@ public class DefaultRepositoryConfiguration<T extends RepositoryConfigurationSou
@Override
public Optional<String> getRepositoryBaseClassName() {
return configurationSource.getRepositoryBaseClassName()
.or(() -> Optional.ofNullable(extension.getRepositoryBaseClassName()));
return configurationSource.getRepositoryBaseClassName();
}
@Override
@ -166,8 +166,7 @@ public class DefaultRepositoryConfiguration<T extends RepositoryConfigurationSou @@ -166,8 +166,7 @@ public class DefaultRepositoryConfiguration<T extends RepositoryConfigurationSou
}
@Override
@org.jspecify.annotations.NonNull
public String getResourceDescription() {
public @NonNull String getResourceDescription() {
return String.format("%s defined in %s", getRepositoryInterface(), configurationSource.getResourceDescription());
}
}

4
src/main/java/org/springframework/data/repository/config/RepositoryBeanDefinitionReader.java

@ -126,6 +126,10 @@ class RepositoryBeanDefinitionReader { @@ -126,6 +126,10 @@ class RepositoryBeanDefinitionReader {
Object repoBaseClassName = beanDefinition.getPropertyValues().get("repositoryBaseClass");
if (repoBaseClassName == null && extension != null) {
repoBaseClassName = extension.getRepositoryBaseClassName();
}
if (repoBaseClassName != null) {
return forName(repoBaseClassName.toString());
}

5
src/main/java/org/springframework/data/repository/core/support/RepositoryFactoryBeanSupport.java

@ -90,7 +90,6 @@ public abstract class RepositoryFactoryBeanSupport<T extends Repository<S, ID>, @@ -90,7 +90,6 @@ public abstract class RepositoryFactoryBeanSupport<T extends Repository<S, ID>,
private boolean lazyInit = Boolean.getBoolean(AbstractAotProcessor.AOT_PROCESSING); // use lazy-init in AOT processing
private @Nullable EvaluationContextProvider evaluationContextProvider;
private final List<RepositoryFactoryCustomizer> repositoryFactoryCustomizers = new ArrayList<>();
private RepositoryFragments cachedFragments = RepositoryFragments.empty();
private @Nullable Lazy<T> repository;
private @Nullable RepositoryMetadata repositoryMetadata;
@ -107,10 +106,12 @@ public abstract class RepositoryFactoryBeanSupport<T extends Repository<S, ID>, @@ -107,10 +106,12 @@ public abstract class RepositoryFactoryBeanSupport<T extends Repository<S, ID>,
}
/**
* Configures the repository base class to be used.
* Configures the repository base class to use when creating the repository. If not set, the factory will use the type
* returned by {@link RepositoryFactorySupport#getRepositoryBaseClass(RepositoryMetadata)} by default.
*
* @param repositoryBaseClass the repositoryBaseClass to set, can be {@literal null}.
* @since 1.11
* @see RepositoryFactorySupport#setRepositoryBaseClass(Class)
*/
public void setRepositoryBaseClass(Class<?> repositoryBaseClass) {
this.repositoryBaseClass = repositoryBaseClass;

10
src/main/java/org/springframework/data/repository/core/support/RepositoryFactorySupport.java

@ -139,10 +139,6 @@ public abstract class RepositoryFactorySupport @@ -139,10 +139,6 @@ public abstract class RepositoryFactorySupport
this.projectionFactory = createProjectionFactory();
}
EvaluationContextProvider getEvaluationContextProvider() {
return evaluationContextProvider;
}
/**
* Set whether the repository method metadata should be exposed by the repository factory as a ThreadLocal for
* retrieval via the {@code RepositoryMethodContext} class. This is useful if an advised object needs to obtain
@ -219,10 +215,10 @@ public abstract class RepositoryFactorySupport @@ -219,10 +215,10 @@ public abstract class RepositoryFactorySupport
}
/**
* Configures the repository base class to use when creating the repository proxy. If not set, the factory will use
* the type returned by {@link #getRepositoryBaseClass(RepositoryMetadata)} by default.
* Configures the repository base class to use when creating the repository. If not set, the factory will use the type
* returned by {@link #getRepositoryBaseClass(RepositoryMetadata)} by default.
*
* @param repositoryBaseClass the repository base class to back the repository proxy, can be {@literal null}.
* @param repositoryBaseClass the repository base class to back the repository, can be {@literal null}.
* @since 1.11
*/
public void setRepositoryBaseClass(@Nullable Class<?> repositoryBaseClass) {

4
src/test/java/org/springframework/data/repository/config/RepositoryBeanDefinitionReaderTests.java

@ -29,6 +29,7 @@ import org.springframework.data.aot.sample.ConfigWithCustomRepositoryBaseClass.C @@ -29,6 +29,7 @@ import org.springframework.data.aot.sample.ConfigWithCustomRepositoryBaseClass.C
import org.springframework.data.aot.sample.ConfigWithFragments;
import org.springframework.data.aot.sample.ConfigWithSimpleCrudRepository;
import org.springframework.data.aot.sample.ReactiveConfig;
import org.springframework.data.repository.PagingAndSortingRepository;
import org.springframework.data.repository.core.RepositoryInformation;
import org.springframework.data.repository.core.support.RepositoryFactoryBeanSupport;
import org.springframework.data.repository.core.support.RepositoryFragment;
@ -104,7 +105,7 @@ class RepositoryBeanDefinitionReaderTests { @@ -104,7 +105,7 @@ class RepositoryBeanDefinitionReaderTests {
assertThat(repositoryInformation.getFragments()).isEmpty();
}
@Test // GH-3279, GH-3282
@Test // GH-3279, GH-3282, GH-3423
void readsCustomImplementationFromBeanFactory() {
RegisteredBean repoFactoryBean = repositoryFactory(ConfigWithCustomImplementation.class);
@ -116,6 +117,7 @@ class RepositoryBeanDefinitionReaderTests { @@ -116,6 +117,7 @@ class RepositoryBeanDefinitionReaderTests {
RepositoryBeanDefinitionReader reader = new RepositoryBeanDefinitionReader(repoFactoryBean);
RepositoryInformation repositoryInformation = reader.getRepositoryInformation();
assertThat(repositoryInformation.getRepositoryBaseClass()).isEqualTo(PagingAndSortingRepository.class);
assertThat(repositoryInformation.getFragments()).satisfiesExactly(fragment -> {
assertThat(fragment.getImplementationClass())
.contains(ConfigWithCustomImplementation.RepositoryWithCustomImplementationImpl.class);

Loading…
Cancel
Save