Polishing.
Avoid nullability in RepositoryMethodContextHolder.getContext(). Introduce shortcut in RepositoryMethodContext to obtain the current thread-local context. Update documentation. See #3175. Original pull request: #3176
This commit is contained in:
@@ -271,7 +271,7 @@ package com.acme.search;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.data.domain.Limit;
|
||||
import org.springframework.data.repository.core.support.RepositoryMethodContext;
|
||||
import org.springframework.data.repository.core.RepositoryMethodContext;
|
||||
|
||||
class DefaultSearchExtension<T> implements SearchExtension<T> {
|
||||
|
||||
@@ -282,7 +282,7 @@ class DefaultSearchExtension<T> implements SearchExtension<T> {
|
||||
}
|
||||
|
||||
public List<T> search(String text, Limit limit) {
|
||||
return search(RepositoryMethodContext.currentMethod(), text, limit);
|
||||
return search(RepositoryMethodContext.getContext(), text, limit);
|
||||
}
|
||||
|
||||
List<T> search(RepositoryMethodContext metadata, String text, Limit limit) {
|
||||
@@ -297,12 +297,12 @@ class DefaultSearchExtension<T> implements SearchExtension<T> {
|
||||
}
|
||||
----
|
||||
|
||||
In the example above `RepositoryMethodContext.currentMethod()` is used to retrieve metadata for the actual method invocation.
|
||||
In the example above `RepositoryMethodContext.getContext()` is used to retrieve metadata for the actual method invocation.
|
||||
`RepositoryMethodContext` exposes information attached to the repository such as the domain type.
|
||||
In this case we use the repository domain type to identify the name of the index to be searched.
|
||||
|
||||
Exposing invocation metadata is costly, hence it is disabled by default.
|
||||
To access `RepositoryMethodContext.currentMethod()` you need to advise the repository factory responsible for creating the actual repository to expose method metadata.
|
||||
To access `RepositoryMethodContext.getContext()` you need to advise the repository factory responsible for creating the actual repository to expose method metadata.
|
||||
|
||||
.Expose Repository Metadata
|
||||
[tabs]
|
||||
@@ -319,7 +319,7 @@ package com.acme.search;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.data.domain.Limit;
|
||||
import org.springframework.data.repository.core.support.RepositoryMetadataAccess;
|
||||
import org.springframework.data.repository.core.support.RepositoryMethodContext;
|
||||
import org.springframework.data.repository.core.RepositoryMethodContext;
|
||||
|
||||
class DefaultSearchExtension<T> implements SearchExtension<T>, RepositoryMetadataAccess {
|
||||
|
||||
|
||||
@@ -33,10 +33,24 @@ import java.lang.reflect.Method;
|
||||
* @author Christoph Strobl
|
||||
* @author Mark Paluch
|
||||
* @author Oliver Drotbohm
|
||||
* @since 3.4.0
|
||||
* @since 3.4
|
||||
*/
|
||||
public interface RepositoryMethodContext {
|
||||
|
||||
/**
|
||||
* Try to return the current repository method metadata. This method is usable only if the calling method has been
|
||||
* invoked via a repository method, and the repository factory has been set to expose metadata. Otherwise, this method
|
||||
* will throw an IllegalStateException.
|
||||
*
|
||||
* @return the current repository method metadata (never returns {@code null})
|
||||
* @throws IllegalStateException if the repository method metadata cannot be found, because the method was invoked
|
||||
* outside a repository method invocation context, or because the repository has not been configured to
|
||||
* expose its metadata.
|
||||
*/
|
||||
static RepositoryMethodContext getContext() throws IllegalStateException {
|
||||
return RepositoryMethodContextHolder.getContext();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the metadata for the repository.
|
||||
*
|
||||
|
||||
@@ -19,74 +19,68 @@ import org.springframework.core.NamedThreadLocal;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* Associates a given {@link RepositoryMethodContext} with the current execution thread.
|
||||
* <p>
|
||||
* This class provides a series of static methods that interact with a thread-local storage of
|
||||
* {@link RepositoryMethodContext}. The purpose of the class is to provide a convenient way to be used for an
|
||||
* application.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @since 3.4.0
|
||||
* @author Mark Paluch
|
||||
* @since 3.4
|
||||
* @see RepositoryMethodContext
|
||||
*/
|
||||
public class RepositoryMethodContextHolder {
|
||||
|
||||
private static ContextProvider contextSupplier;
|
||||
|
||||
static {
|
||||
contextSupplier = new ThreadLocalContextProvider();
|
||||
}
|
||||
/**
|
||||
* ThreadLocal holder for repository method associated with this thread. Will contain {@code null} unless the
|
||||
* "exposeMetadata" property on the controlling repository factory configuration has been set to {@code true}.
|
||||
*/
|
||||
private static final ThreadLocal<RepositoryMethodContext> currentMethod = new NamedThreadLocal<>(
|
||||
"Current Repository Method");
|
||||
|
||||
/**
|
||||
* Make the given repository method metadata available via the {@link #getContext()} method.
|
||||
* <p>
|
||||
* Note that the caller should be careful to keep the old value as appropriate.
|
||||
*
|
||||
* @param context the metadata to expose (or {@code null} to reset it)
|
||||
* @return the old metadata, which may be {@code null} if none was bound
|
||||
* @see #getContext()
|
||||
*/
|
||||
@Nullable
|
||||
public static RepositoryMethodContext setContext(@Nullable RepositoryMethodContext context) {
|
||||
return contextSupplier.set(context);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static RepositoryMethodContext current() {
|
||||
return contextSupplier.get();
|
||||
}
|
||||
|
||||
public static void clearContext() {
|
||||
contextSupplier.clear();
|
||||
}
|
||||
|
||||
interface ContextProvider {
|
||||
|
||||
@Nullable
|
||||
RepositoryMethodContext get();
|
||||
|
||||
@Nullable
|
||||
RepositoryMethodContext set(@Nullable RepositoryMethodContext context);
|
||||
|
||||
void clear();
|
||||
}
|
||||
|
||||
static class ThreadLocalContextProvider implements ContextProvider {
|
||||
|
||||
/**
|
||||
* ThreadLocal holder for repository method associated with this thread. Will contain {@code null} unless the
|
||||
* "exposeMetadata" property on the controlling repository factory configuration has been set to "true".
|
||||
*/
|
||||
private static final ThreadLocal<RepositoryMethodContext> currentMethod = new NamedThreadLocal<>(
|
||||
"Current Repository Method");
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public RepositoryMethodContext get() {
|
||||
return currentMethod.get();
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
RepositoryMethodContext old = currentMethod.get();
|
||||
if (context != null) {
|
||||
currentMethod.set(context);
|
||||
} else {
|
||||
currentMethod.remove();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public RepositoryMethodContext set(@Nullable RepositoryMethodContext context) {
|
||||
return old;
|
||||
}
|
||||
|
||||
RepositoryMethodContext old = currentMethod.get();
|
||||
/**
|
||||
* Try to return the current repository method metadata. This method is usable only if the calling method has been
|
||||
* invoked via a repository method, and the repository factory has been set to expose metadata. Otherwise, this method
|
||||
* will throw an IllegalStateException.
|
||||
*
|
||||
* @return the current repository method metadata (never returns {@code null})
|
||||
* @throws IllegalStateException if the repository method metadata cannot be found, because the method was invoked
|
||||
* outside a repository method invocation context, or because the repository has not been configured to
|
||||
* expose its metadata.
|
||||
*/
|
||||
public static RepositoryMethodContext getContext() {
|
||||
|
||||
if (context != null) {
|
||||
currentMethod.set(context);
|
||||
} else {
|
||||
currentMethod.remove();
|
||||
}
|
||||
RepositoryMethodContext metadata = currentMethod.get();
|
||||
|
||||
return old;
|
||||
if (metadata == null) {
|
||||
throw new IllegalStateException(
|
||||
"Cannot find current repository method: Set 'exposeMetadata' property on RepositoryFactorySupport to 'true' to make it available, and "
|
||||
+ "ensure that RepositoryMethodContext.getContext() is invoked in the same thread as the repository invocation.");
|
||||
}
|
||||
|
||||
return metadata;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ import org.springframework.util.Assert;
|
||||
* @author Christoph Strobl
|
||||
* @author Mark Paluch
|
||||
* @author Oliver Drotbohm
|
||||
* @since 3.4.0
|
||||
* @since 3.4
|
||||
*/
|
||||
public class DefaultRepositoryMethodContext implements RepositoryMethodContext {
|
||||
|
||||
|
||||
@@ -1,79 +0,0 @@
|
||||
/*
|
||||
* Copyright 2024 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
|
||||
*
|
||||
* https://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.core.support;
|
||||
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.springframework.aop.TargetSource;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* A {@link TargetSource}, that will re-obtain an instance using the configured supplier.
|
||||
*
|
||||
* @author Oliver Drotbohm
|
||||
* @since 3.4.0
|
||||
*/
|
||||
class DynamicLookupTargetSource<T> implements TargetSource {
|
||||
|
||||
private final Class<T> type;
|
||||
private final Supplier<? extends T> supplier;
|
||||
|
||||
/**
|
||||
* Creates a new {@link DynamicLookupTargetSource} for the given type and {@link Supplier}.
|
||||
*
|
||||
* @param type must not be {@literal null}.
|
||||
* @param supplier must not be {@literal null}.
|
||||
*/
|
||||
public DynamicLookupTargetSource(Class<T> type, Supplier<? extends T> supplier) {
|
||||
|
||||
Assert.notNull(type, "Type must not be null!");
|
||||
Assert.notNull(supplier, "Supplier must not be null!");
|
||||
|
||||
this.type = type;
|
||||
this.supplier = supplier;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.aop.TargetSource#isStatic()
|
||||
*/
|
||||
@Override
|
||||
public boolean isStatic() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.aop.TargetSource#getTarget()
|
||||
*/
|
||||
@Override
|
||||
@Nullable
|
||||
public Object getTarget() throws Exception {
|
||||
return supplier.get();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.aop.TargetSource#getTargetClass()
|
||||
*/
|
||||
@Override
|
||||
@NonNull
|
||||
public Class<?> getTargetClass() {
|
||||
return type;
|
||||
}
|
||||
}
|
||||
@@ -121,7 +121,7 @@ public abstract class RepositoryFactoryBeanSupport<T extends Repository<S, ID>,
|
||||
* Default is "false", in order to avoid unnecessary extra interception. This means that no guarantees are provided
|
||||
* that {@code RepositoryMethodContext} access will work consistently within any method of the advised object.
|
||||
*
|
||||
* @since 3.4.0
|
||||
* @since 3.4
|
||||
*/
|
||||
public void setExposeMetadata(boolean exposeMetadata) {
|
||||
this.exposeMetadata = exposeMetadata;
|
||||
|
||||
@@ -27,7 +27,7 @@ package org.springframework.data.repository.core.support;
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @since 3.4
|
||||
* @see RepositoryMethodContext
|
||||
* @see org.springframework.data.repository.core.RepositoryMethodContext
|
||||
*/
|
||||
public interface RepositoryMetadataAccess {
|
||||
|
||||
|
||||
@@ -256,7 +256,7 @@ class RepositoryFactorySupportUnitTests {
|
||||
record Metadata(RepositoryMethodContext context, MethodInvocation methodInvocation) {}
|
||||
|
||||
when(factory.queryOne.execute(any(Object[].class)))
|
||||
.then(invocation -> new Metadata(RepositoryMethodContextHolder.current(),
|
||||
.then(invocation -> new Metadata(RepositoryMethodContextHolder.getContext(),
|
||||
ExposeInvocationInterceptor.currentInvocation()));
|
||||
|
||||
factory.setExposeMetadata(true);
|
||||
@@ -279,7 +279,7 @@ class RepositoryFactorySupportUnitTests {
|
||||
}
|
||||
|
||||
when(factory.queryOne.execute(any(Object[].class)))
|
||||
.then(invocation -> new Metadata(RepositoryMethodContextHolder.current(),
|
||||
.then(invocation -> new Metadata(RepositoryMethodContextHolder.getContext(),
|
||||
ExposeInvocationInterceptor.currentInvocation()));
|
||||
|
||||
var repository = factory.getRepository(ObjectRepository.class, new RepositoryMetadataAccess() {});
|
||||
|
||||
Reference in New Issue
Block a user