Skip to content

Commit a3cbc1e

Browse files
committed
Refine repositoryBaseClass property configuration for @Enable…Repositories repository factory definitions.
Also extend documentation for repository factory and factory bean customization. Closes #3423
1 parent d7808e6 commit a3cbc1e

File tree

6 files changed

+53
-14
lines changed

6 files changed

+53
-14
lines changed

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

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -328,6 +328,7 @@ In this case we use the repository domain type to identify the name of the index
328328
Exposing invocation metadata is costly, hence it is disabled by default.
329329
To access `RepositoryMethodContext.getContext()` you need to advise the repository factory responsible for creating the actual repository to expose method metadata.
330330

331+
[[expose-repository-metadata]]
331332
.Expose Repository Metadata
332333
[tabs]
333334
======
@@ -471,3 +472,39 @@ XML::
471472
======
472473
====
473474

475+
[[repositories.customize-repository-factory]]
476+
== Customizing the Repository Factory
477+
478+
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.
479+
This mechanism is useful when you want to adjust selected aspects of proxy creation without introducing a fully custom repository factory bean.
480+
The following example, demonstrates registering additional listeners and proxy advisors:
481+
482+
[source,java]
483+
----
484+
factoryBean.addRepositoryFactoryCustomizer(repositoryFactory -> {
485+
repositoryFactory.addInvocationListener(…);
486+
repositoryFactory.addQueryCreationListener(…);
487+
488+
repositoryFactory.addRepositoryProxyPostProcessor((factory, repositoryInformation) -> factory.addAdvice(…)));
489+
});
490+
----
491+
492+
A `RepositoryFactoryCustomizer` is associated with a particular repository factory bean, ideally through `BeanPostProcessor` so that only specific repositories are affected.
493+
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.
494+
495+
[[repositories.customize-repository-factory-bean]]
496+
== Customize the Repository Factory Bean
497+
498+
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.
499+
500+
Customizing the repository factory bean allows you to change repository creation entirely with full access to the underlying repository factory.
501+
502+
Note that this approach requires the most effort and is typically only needed when you want to change core aspects of repository creation.
503+
Also, you need to take in consideration repository metadata derivation that is used to identify query methods, base implementation methods and custom implementations.
504+
The following summary outlines the key aspects:
505+
506+
* `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.
507+
* `repositoryFragmentsContributor`: A javadoc:org.springframework.data.repository.core.support.RepositoryFragmentsContributor[] allows contributions to repository composition after all standard fragments have been collected.
508+
Store modules use this mechanism to add features such as Querydsl or Query-by-Example support.
509+
It also serves as an SPI for third-party extensions.
510+
* `exposeMetadata`: Controls whether <<expose-repository-metadata,invocation metadata>> is available through `RepositoryMethodContext.getContext()`.

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

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
import java.util.Optional;
1919

20+
import org.jspecify.annotations.NonNull;
2021
import org.jspecify.annotations.Nullable;
2122

2223
import org.springframework.beans.factory.config.BeanDefinition;
@@ -114,8 +115,7 @@ public T getConfigurationSource() {
114115

115116
@Override
116117
public Optional<String> getRepositoryBaseClassName() {
117-
return configurationSource.getRepositoryBaseClassName()
118-
.or(() -> Optional.ofNullable(extension.getRepositoryBaseClassName()));
118+
return configurationSource.getRepositoryBaseClassName();
119119
}
120120

121121
@Override
@@ -166,8 +166,7 @@ public ImplementationLookupConfiguration toLookupConfiguration(MetadataReaderFac
166166
}
167167

168168
@Override
169-
@org.jspecify.annotations.NonNull
170-
public String getResourceDescription() {
169+
public @NonNull String getResourceDescription() {
171170
return String.format("%s defined in %s", getRepositoryInterface(), configurationSource.getResourceDescription());
172171
}
173172
}

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,10 @@ private Class<?> getRepositoryBaseClass() {
126126

127127
Object repoBaseClassName = beanDefinition.getPropertyValues().get("repositoryBaseClass");
128128

129+
if (repoBaseClassName == null && extension != null) {
130+
repoBaseClassName = extension.getRepositoryBaseClassName();
131+
}
132+
129133
if (repoBaseClassName != null) {
130134
return forName(repoBaseClassName.toString());
131135
}

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,6 @@ public abstract class RepositoryFactoryBeanSupport<T extends Repository<S, ID>,
9090
private boolean lazyInit = Boolean.getBoolean(AbstractAotProcessor.AOT_PROCESSING); // use lazy-init in AOT processing
9191
private @Nullable EvaluationContextProvider evaluationContextProvider;
9292
private final List<RepositoryFactoryCustomizer> repositoryFactoryCustomizers = new ArrayList<>();
93-
9493
private RepositoryFragments cachedFragments = RepositoryFragments.empty();
9594
private @Nullable Lazy<T> repository;
9695
private @Nullable RepositoryMetadata repositoryMetadata;
@@ -107,10 +106,12 @@ protected RepositoryFactoryBeanSupport(Class<? extends T> repositoryInterface) {
107106
}
108107

109108
/**
110-
* Configures the repository base class to be used.
109+
* Configures the repository base class to use when creating the repository. If not set, the factory will use the type
110+
* returned by {@link RepositoryFactorySupport#getRepositoryBaseClass(RepositoryMetadata)} by default.
111111
*
112112
* @param repositoryBaseClass the repositoryBaseClass to set, can be {@literal null}.
113113
* @since 1.11
114+
* @see RepositoryFactorySupport#setRepositoryBaseClass(Class)
114115
*/
115116
public void setRepositoryBaseClass(Class<?> repositoryBaseClass) {
116117
this.repositoryBaseClass = repositoryBaseClass;

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

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -139,10 +139,6 @@ public RepositoryFactorySupport() {
139139
this.projectionFactory = createProjectionFactory();
140140
}
141141

142-
EvaluationContextProvider getEvaluationContextProvider() {
143-
return evaluationContextProvider;
144-
}
145-
146142
/**
147143
* Set whether the repository method metadata should be exposed by the repository factory as a ThreadLocal for
148144
* retrieval via the {@code RepositoryMethodContext} class. This is useful if an advised object needs to obtain
@@ -219,10 +215,10 @@ public void setEvaluationContextProvider(@Nullable EvaluationContextProvider eva
219215
}
220216

221217
/**
222-
* Configures the repository base class to use when creating the repository proxy. If not set, the factory will use
223-
* the type returned by {@link #getRepositoryBaseClass(RepositoryMetadata)} by default.
218+
* Configures the repository base class to use when creating the repository. If not set, the factory will use the type
219+
* returned by {@link #getRepositoryBaseClass(RepositoryMetadata)} by default.
224220
*
225-
* @param repositoryBaseClass the repository base class to back the repository proxy, can be {@literal null}.
221+
* @param repositoryBaseClass the repository base class to back the repository, can be {@literal null}.
226222
* @since 1.11
227223
*/
228224
public void setRepositoryBaseClass(@Nullable Class<?> repositoryBaseClass) {

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
import org.springframework.data.aot.sample.ConfigWithFragments;
3030
import org.springframework.data.aot.sample.ConfigWithSimpleCrudRepository;
3131
import org.springframework.data.aot.sample.ReactiveConfig;
32+
import org.springframework.data.repository.PagingAndSortingRepository;
3233
import org.springframework.data.repository.core.RepositoryInformation;
3334
import org.springframework.data.repository.core.support.RepositoryFactoryBeanSupport;
3435
import org.springframework.data.repository.core.support.RepositoryFragment;
@@ -104,7 +105,7 @@ void readsFragmentsContributorFromBeanFactory() {
104105
assertThat(repositoryInformation.getFragments()).isEmpty();
105106
}
106107

107-
@Test // GH-3279, GH-3282
108+
@Test // GH-3279, GH-3282, GH-3423
108109
void readsCustomImplementationFromBeanFactory() {
109110

110111
RegisteredBean repoFactoryBean = repositoryFactory(ConfigWithCustomImplementation.class);
@@ -116,6 +117,7 @@ void readsCustomImplementationFromBeanFactory() {
116117
RepositoryBeanDefinitionReader reader = new RepositoryBeanDefinitionReader(repoFactoryBean);
117118
RepositoryInformation repositoryInformation = reader.getRepositoryInformation();
118119

120+
assertThat(repositoryInformation.getRepositoryBaseClass()).isEqualTo(PagingAndSortingRepository.class);
119121
assertThat(repositoryInformation.getFragments()).satisfiesExactly(fragment -> {
120122
assertThat(fragment.getImplementationClass())
121123
.contains(ConfigWithCustomImplementation.RepositoryWithCustomImplementationImpl.class);

0 commit comments

Comments
 (0)