Skip to content

Commit d9bdd2b

Browse files
Polishing.
Update documentation. Additional logging for repository bootstrap procedure. Limit usage of Optional in RepositoryFragment. Original Pull Request: #3145
1 parent f7895eb commit d9bdd2b

File tree

4 files changed

+92
-46
lines changed

4 files changed

+92
-46
lines changed

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

+39-17
Original file line numberDiff line numberDiff line change
@@ -250,7 +250,7 @@ Imagine you'd like to provide some custom search functionality usable across mul
250250
First all you need is the fragment interface.
251251
Note the generic `<T>` parameter to align the fragment with the repository domain type.
252252

253-
====
253+
.Fragment Interface
254254
[source,java]
255255
----
256256
package com.acme.search;
@@ -260,12 +260,11 @@ public interface SearchExtension<T> {
260260
List<T> search(String text, Limit limit);
261261
}
262262
----
263-
====
264263

265264
Let's assume the actual full-text search is available via a `SearchService` that is registered as a `Bean` within the context so you can consume it in our `SearchExtension` implementation.
266265
All you need to run the search is the collection (or index) name and an object mapper that converts the search results into actual domain objects as sketched out below.
267266

268-
====
267+
.Fragment implementation
269268
[source,java]
270269
----
271270
package com.acme.search;
@@ -297,27 +296,44 @@ class DefaultSearchExtension<T> implements SearchExtension<T> {
297296
}
298297
}
299298
----
300-
====
301299

302300
In the example above `RepositoryMethodContext.currentMethod()` is used to retrieve metadata for the actual method invocation.
303301
`RepositoryMethodContext` exposes information attached to the repository such as the domain type.
304302
In this case we use the repository domain type to identify the name of the index to be searched.
305303

306-
Now that you've got both, the fragment declaration and implementation you can register it in the `META-INF/spring.factories` file, package things up if needed, and you're almost good to go.
304+
Exposing invocation metadata is costly, hence it is disabled by default.
305+
To access `RepositoryMethodContext.currentMethod()` you need to advise the repository factory responsible for creating the actual repository to expose method metadata.
307306

308-
.Registering a fragment implementation through `META-INF/spring.factories`
307+
.Expose Repository Metadata
308+
[tabs]
309+
======
310+
Marker Interface::
311+
+
309312
====
310-
[source,properties]
313+
Adding the `RepositoryMetadataAccess` marker interface to the fragments implementation will trigger the infrastructure and enable metadata exposure for those repositories using the fragment.
314+
315+
[source,java,role="primary"]
311316
----
312-
com.acme.search.SearchExtension=com.acme.search.DefaultSearchExtension
317+
package com.acme.search;
318+
319+
import org.springframework.beans.factory.annotation.Autowired;
320+
import org.springframework.data.domain.Limit;
321+
import org.springframework.data.repository.core.support.RepositoryMetadataAccess;
322+
import org.springframework.data.repository.core.support.RepositoryMethodContext;
323+
324+
class DefaultSearchExtension<T> implements SearchExtension<T>, RepositoryMetadataAccess {
325+
326+
// ...
327+
}
313328
----
314329
====
315330
316-
Exposing invocation metadata is costly, hence it is disabled by default.
317-
To access `RepositoryMethodContext.currentMethod()` you need to advise the repository factory responsible for creating the actual repository to expose method metadata by setting the `exposeMetadata` flag.
318-
331+
Bean Post Processor::
332+
+
319333
====
320-
[source,java]
334+
The `exposeMetadata` flag can be set directly on the repository factory bean via a `BeanPostProcessor`.
335+
336+
[source,java,role="secondary"]
321337
----
322338
import org.springframework.beans.factory.config.BeanPostProcessor;
323339
import org.springframework.context.annotation.Configuration;
@@ -345,14 +361,21 @@ class MyConfiguration {
345361
}
346362
----
347363

348-
The above example outlines how to enable metadata exposure by setting the `exposeMetadata` flag using a `BeanPostProcessor`.
349364
Please do not just copy/paste the above but consider your actual use case which may require a more fine-grained approach as the above will simply enable the flag on every repository.
350-
You may want to have a look at our https://github.com/spring-projects/spring-data-examples/tree/main/bom[spring-data-examples] project to draw inspiration.
351365
====
366+
======
352367

353-
Now you are ready to make use of your extension; Simply add the interface to your repository:
368+
Having both, the fragment declaration and implementation in place you can register the extension in the `META-INF/spring.factories` file and package things up if needed.
354369

355-
====
370+
.Register the fragment in `META-INF/spring.factories`
371+
[source,properties]
372+
----
373+
com.acme.search.SearchExtension=com.acme.search.DefaultSearchExtension
374+
----
375+
376+
Now you are ready to make use of your extension; Simply add the interface to your repository.
377+
378+
.Using it
356379
[source,java]
357380
----
358381
package io.my.movies;
@@ -364,7 +387,6 @@ interface MovieRepository extends CrudRepository<Movie, String>, SearchExtension
364387
365388
}
366389
----
367-
====
368390

369391
[[repositories.customize-base-repository]]
370392
== Customize the Base Repository

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

+16
Original file line numberDiff line numberDiff line change
@@ -348,10 +348,16 @@ public <T> T getRepository(Class<T> repositoryInterface, RepositoryFragments fra
348348
result.setInterfaces(repositoryInterface, Repository.class, TransactionalProxy.class);
349349

350350
if (MethodInvocationValidator.supports(repositoryInterface)) {
351+
if (logger.isTraceEnabled()) {
352+
logger.trace(LogMessage.format("Register MethodInvocationValidator for %s…", repositoryInterface.getName()));
353+
}
351354
result.addAdvice(new MethodInvocationValidator());
352355
}
353356

354357
if (this.exposeMetadata || shouldExposeMetadata(fragments)) {
358+
if (logger.isTraceEnabled()) {
359+
logger.trace(LogMessage.format("Register ExposeMetadataInterceptor for %s…", repositoryInterface.getName()));
360+
}
355361
result.addAdvice(new ExposeMetadataInterceptor(metadata));
356362
result.addAdvisor(ExposeInvocationInterceptor.ADVISOR);
357363
}
@@ -371,6 +377,9 @@ public <T> T getRepository(Class<T> repositoryInterface, RepositoryFragments fra
371377
}
372378

373379
if (DefaultMethodInvokingMethodInterceptor.hasDefaultMethods(repositoryInterface)) {
380+
if (logger.isTraceEnabled()) {
381+
logger.trace(LogMessage.format("Register DefaultMethodInvokingMethodInterceptor for %s…", repositoryInterface.getName()));
382+
}
374383
result.addAdvice(new DefaultMethodInvokingMethodInterceptor());
375384
}
376385

@@ -622,6 +631,13 @@ private Lazy<ProjectionFactory> createProjectionFactory() {
622631
return Lazy.of(() -> getProjectionFactory(this.classLoader, this.beanFactory));
623632
}
624633

634+
/**
635+
* Checks if at least one {@link RepositoryFragment} indicates need to access to {@link RepositoryMetadata} by being
636+
* flagged with {@link RepositoryMetadataAccess}.
637+
*
638+
* @param fragments
639+
* @return {@literal true} if access to metadata is required.
640+
*/
625641
private static boolean shouldExposeMetadata(RepositoryFragments fragments) {
626642

627643
for (RepositoryFragment<?> fragment : fragments) {

Diff for: src/main/java/org/springframework/data/repository/core/support/RepositoryFragment.java

+36-28
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import java.util.Optional;
2121
import java.util.stream.Stream;
2222

23+
import org.springframework.lang.Nullable;
2324
import org.springframework.util.Assert;
2425
import org.springframework.util.ClassUtils;
2526
import org.springframework.util.ObjectUtils;
@@ -41,6 +42,7 @@
4142
* Fragments are immutable.
4243
*
4344
* @author Mark Paluch
45+
* @author Christoph Strobl
4446
* @since 2.0
4547
* @see RepositoryComposition
4648
*/
@@ -53,7 +55,7 @@ public interface RepositoryFragment<T> {
5355
* @return
5456
*/
5557
static <T> RepositoryFragment<T> implemented(T implementation) {
56-
return new ImplementedRepositoryFragment<T>(Optional.empty(), implementation);
58+
return new ImplementedRepositoryFragment<>((Class<T>) null, implementation);
5759
}
5860

5961
/**
@@ -64,7 +66,7 @@ static <T> RepositoryFragment<T> implemented(T implementation) {
6466
* @return
6567
*/
6668
static <T> RepositoryFragment<T> implemented(Class<T> interfaceClass, T implementation) {
67-
return new ImplementedRepositoryFragment<>(Optional.of(interfaceClass), implementation);
69+
return new ImplementedRepositoryFragment<>(interfaceClass, implementation);
6870
}
6971

7072
/**
@@ -134,7 +136,7 @@ public Class<?> getSignatureContributor() {
134136

135137
@Override
136138
public RepositoryFragment<T> withImplementation(T implementation) {
137-
return new ImplementedRepositoryFragment<>(Optional.of(interfaceOrImplementation), implementation);
139+
return new ImplementedRepositoryFragment<>(interfaceOrImplementation, implementation);
138140
}
139141

140142
@Override
@@ -164,47 +166,58 @@ public int hashCode() {
164166

165167
class ImplementedRepositoryFragment<T> implements RepositoryFragment<T> {
166168

167-
private final Optional<Class<T>> interfaceClass;
169+
private final @Nullable Class<T> interfaceClass;
168170
private final T implementation;
169-
private final Optional<T> optionalImplementation;
171+
172+
/**
173+
* Creates a new {@link ImplementedRepositoryFragment} for the given interface class and implementation.
174+
*
175+
* @param interfaceClass
176+
* @param implementation
177+
* @deprecated since 3.4 - use {@link ImplementedRepositoryFragment(Class, Object)} instead.
178+
*/
179+
@Deprecated(since = "3.4", forRemoval = true)
180+
public ImplementedRepositoryFragment(Optional<Class<T>> interfaceClass, T implementation) {
181+
this(interfaceClass.orElse(null), implementation);
182+
}
170183

171184
/**
172185
* Creates a new {@link ImplementedRepositoryFragment} for the given interface class and implementation.
173186
*
174187
* @param interfaceClass must not be {@literal null}.
175188
* @param implementation must not be {@literal null}.
176189
*/
177-
public ImplementedRepositoryFragment(Optional<Class<T>> interfaceClass, T implementation) {
190+
public ImplementedRepositoryFragment(@Nullable Class<T> interfaceClass, T implementation) {
178191

179-
Assert.notNull(interfaceClass, "Interface class must not be null");
180192
Assert.notNull(implementation, "Implementation object must not be null");
181193

182-
interfaceClass.ifPresent(it -> {
194+
if (interfaceClass != null) {
183195

184-
Assert.isTrue(ClassUtils.isAssignableValue(it, implementation),
185-
() -> String.format("Fragment implementation %s does not implement %s",
186-
ClassUtils.getQualifiedName(implementation.getClass()), ClassUtils.getQualifiedName(it)));
187-
});
196+
Assert.isTrue(ClassUtils.isAssignableValue(interfaceClass, implementation),
197+
() -> "Fragment implementation %s does not implement %s".formatted(
198+
ClassUtils.getQualifiedName(implementation.getClass()),
199+
ClassUtils.getQualifiedName(interfaceClass)));
200+
}
188201

189202
this.interfaceClass = interfaceClass;
190203
this.implementation = implementation;
191-
this.optionalImplementation = Optional.of(implementation);
192204
}
193205

194-
@SuppressWarnings({ "rawtypes", "unchecked" })
195206
public Class<?> getSignatureContributor() {
196-
return interfaceClass.orElseGet(() -> {
197207

198-
if(implementation instanceof Class type) {
199-
return type;
200-
}
201-
return (Class<T>) implementation.getClass();
202-
});
208+
if (interfaceClass != null) {
209+
return interfaceClass;
210+
}
211+
212+
if (implementation instanceof Class<?> type) {
213+
return type;
214+
}
215+
return implementation.getClass();
203216
}
204217

205218
@Override
206219
public Optional<T> getImplementation() {
207-
return optionalImplementation;
220+
return Optional.of(implementation);
208221
}
209222

210223
@Override
@@ -216,7 +229,7 @@ public RepositoryFragment<T> withImplementation(T implementation) {
216229
public String toString() {
217230

218231
return String.format("ImplementedRepositoryFragment %s%s",
219-
interfaceClass.map(ClassUtils::getShortName).map(it -> it + ":").orElse(""),
232+
interfaceClass != null ? (ClassUtils.getShortName(interfaceClass) + ":") : "",
220233
ClassUtils.getShortName(implementation.getClass()));
221234
}
222235

@@ -235,18 +248,13 @@ public boolean equals(Object o) {
235248
return false;
236249
}
237250

238-
if (!ObjectUtils.nullSafeEquals(implementation, that.implementation)) {
239-
return false;
240-
}
241-
242-
return ObjectUtils.nullSafeEquals(optionalImplementation, that.optionalImplementation);
251+
return ObjectUtils.nullSafeEquals(implementation, that.implementation);
243252
}
244253

245254
@Override
246255
public int hashCode() {
247256
int result = ObjectUtils.nullSafeHashCode(interfaceClass);
248257
result = 31 * result + ObjectUtils.nullSafeHashCode(implementation);
249-
result = 31 * result + ObjectUtils.nullSafeHashCode(optionalImplementation);
250258
return result;
251259
}
252260
}

Diff for: src/main/java/org/springframework/data/repository/core/support/RepositoryMetadataAccess.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
* Marker for repository fragment implementation that intend to access repository method invocation metadata.
2020
* <p>
2121
* Note that this is a marker interface in the style of {@link java.io.Serializable}, semantically applying to a
22-
* fragment implementation class rather. In other words, this marker applies to a particular repository composition that
22+
* fragment implementation class. In other words, this marker applies to a particular repository composition that
2323
* enables metadata access for the repository proxy when the composition contain fragments implementing this interface.
2424
* <p>
2525
* Ideally, in a repository composition only the fragment implementation uses this interface while the fragment

0 commit comments

Comments
 (0)