, RepositoryMetadataAccess {
diff --git a/src/main/java/org/springframework/data/repository/aot/hint/RepositoryRuntimeHints.java b/src/main/java/org/springframework/data/repository/aot/hint/RepositoryRuntimeHints.java
index 5fafd13a1e..7658d9128f 100644
--- a/src/main/java/org/springframework/data/repository/aot/hint/RepositoryRuntimeHints.java
+++ b/src/main/java/org/springframework/data/repository/aot/hint/RepositoryRuntimeHints.java
@@ -18,11 +18,14 @@
import java.util.Arrays;
import java.util.Properties;
+import org.springframework.aop.SpringProxy;
+import org.springframework.aop.framework.Advised;
import org.springframework.aot.hint.MemberCategory;
import org.springframework.aot.hint.RuntimeHints;
import org.springframework.aot.hint.RuntimeHintsRegistrar;
import org.springframework.aot.hint.TypeReference;
import org.springframework.beans.factory.BeanFactory;
+import org.springframework.core.DecoratingProxy;
import org.springframework.core.io.InputStreamSource;
import org.springframework.data.domain.Example;
import org.springframework.data.mapping.context.MappingContext;
@@ -99,5 +102,13 @@ public void registerHints(RuntimeHints hints, @Nullable ClassLoader classLoader)
// annotated queries
hints.proxies().registerJdkProxy( //
TypeReference.of("org.springframework.data.annotation.QueryAnnotation"));
+
+ registerSpringProxy(TypeReference.of("org.springframework.data.repository.core.RepositoryMethodContext"), hints);
+ }
+
+ private static void registerSpringProxy(TypeReference type, RuntimeHints runtimeHints) {
+
+ runtimeHints.proxies().registerJdkProxy(type, TypeReference.of(SpringProxy.class),
+ TypeReference.of(Advised.class), TypeReference.of(DecoratingProxy.class));
}
}
diff --git a/src/main/java/org/springframework/data/repository/config/RepositoryConfigurationExtensionSupport.java b/src/main/java/org/springframework/data/repository/config/RepositoryConfigurationExtensionSupport.java
index 42f4de5cde..7efe44c9c1 100644
--- a/src/main/java/org/springframework/data/repository/config/RepositoryConfigurationExtensionSupport.java
+++ b/src/main/java/org/springframework/data/repository/config/RepositoryConfigurationExtensionSupport.java
@@ -15,7 +15,8 @@
*/
package org.springframework.data.repository.config;
-import static org.springframework.beans.factory.support.BeanDefinitionReaderUtils.*;
+import static org.springframework.beans.factory.support.BeanDefinitionReaderUtils.GENERATED_BEAN_NAME_SEPARATOR;
+import static org.springframework.beans.factory.support.BeanDefinitionReaderUtils.generateBeanName;
import java.lang.annotation.Annotation;
import java.util.Collection;
diff --git a/src/main/java/org/springframework/data/repository/core/support/RepositoryMethodContext.java b/src/main/java/org/springframework/data/repository/core/RepositoryMethodContext.java
similarity index 58%
rename from src/main/java/org/springframework/data/repository/core/support/RepositoryMethodContext.java
rename to src/main/java/org/springframework/data/repository/core/RepositoryMethodContext.java
index 516c8d95b6..bef865c893 100644
--- a/src/main/java/org/springframework/data/repository/core/support/RepositoryMethodContext.java
+++ b/src/main/java/org/springframework/data/repository/core/RepositoryMethodContext.java
@@ -13,19 +13,16 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package org.springframework.data.repository.core.support;
+package org.springframework.data.repository.core;
import java.lang.reflect.Method;
-import org.springframework.data.repository.core.RepositoryMetadata;
-import org.springframework.lang.Nullable;
-
/**
* Interface containing methods and value objects to obtain information about the current repository method invocation.
*
- * The {@link #currentMethod()} method is usable if the repository factory is configured to expose the current
- * repository method metadata (not the default). It returns the invoked repository method. Target objects or advice can
- * use this to make advised calls.
+ * The {@link #getMetadata()} method is usable if the repository factory is configured to expose the current repository
+ * method metadata (not the default). It returns the invoked repository method. Target objects or advice can use this to
+ * make advised calls.
*
* Spring Data's framework does not expose method metadata by default, as there is a performance cost in doing so.
*
@@ -35,7 +32,8 @@
*
* @author Christoph Strobl
* @author Mark Paluch
- * @since 3.4.0
+ * @author Oliver Drotbohm
+ * @since 3.4
*/
public interface RepositoryMethodContext {
@@ -49,37 +47,16 @@ public interface RepositoryMethodContext {
* outside a repository method invocation context, or because the repository has not been configured to
* expose its metadata.
*/
- static RepositoryMethodContext currentMethod() throws IllegalStateException {
-
- RepositoryMethodContext metadata = DefaultRepositoryMethodContext.getMetadata();
- 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.currentMethod() is invoked in the same thread as the repository invocation.");
- }
- return metadata;
- }
-
- /**
- * Make the given repository method metadata available via the {@link #currentMethod()} method.
- *
- * Note that the caller should be careful to keep the old value as appropriate.
- *
- * @param metadata the metadata to expose (or {@code null} to reset it)
- * @return the old metadata, which may be {@code null} if none was bound
- * @see #currentMethod()
- */
- @Nullable
- static RepositoryMethodContext setCurrentMetadata(@Nullable RepositoryMethodContext metadata) {
- return DefaultRepositoryMethodContext.setMetadata(metadata);
+ static RepositoryMethodContext getContext() throws IllegalStateException {
+ return RepositoryMethodContextHolder.getContext();
}
/**
* Returns the metadata for the repository.
*
- * @return the repository metadata.
+ * @return the repository metadata, will never be {@literal null}.
*/
- RepositoryMetadata getRepository();
+ RepositoryMetadata getMetadata();
/**
* Returns the current method that is being invoked.
@@ -87,8 +64,7 @@ static RepositoryMethodContext setCurrentMetadata(@Nullable RepositoryMethodCont
* The method object represents the method as being invoked on the repository interface. It doesn't match the backing
* repository implementation in case the method invocation is delegated to an implementation method.
*
- * @return the current method.
+ * @return the current method, will never be {@literal null}.
*/
Method getMethod();
-
}
diff --git a/src/main/java/org/springframework/data/repository/core/RepositoryMethodContextHolder.java b/src/main/java/org/springframework/data/repository/core/RepositoryMethodContextHolder.java
new file mode 100644
index 0000000000..bc57f152f7
--- /dev/null
+++ b/src/main/java/org/springframework/data/repository/core/RepositoryMethodContextHolder.java
@@ -0,0 +1,86 @@
+/*
+ * 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;
+
+import org.springframework.core.NamedThreadLocal;
+import org.springframework.lang.Nullable;
+
+/**
+ * Associates a given {@link RepositoryMethodContext} with the current execution thread.
+ *
+ * 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
+ * @author Mark Paluch
+ * @since 3.4
+ * @see RepositoryMethodContext
+ */
+public class RepositoryMethodContextHolder {
+
+ /**
+ * 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 currentMethod = new NamedThreadLocal<>(
+ "Current Repository Method");
+
+ /**
+ * Make the given repository method metadata available via the {@link #getContext()} method.
+ *
+ * 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) {
+
+ RepositoryMethodContext old = currentMethod.get();
+ if (context != null) {
+ currentMethod.set(context);
+ } else {
+ currentMethod.remove();
+ }
+
+ return old;
+ }
+
+ /**
+ * 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() {
+
+ RepositoryMethodContext metadata = currentMethod.get();
+
+ 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;
+ }
+}
diff --git a/src/main/java/org/springframework/data/repository/core/support/DefaultRepositoryMethodContext.java b/src/main/java/org/springframework/data/repository/core/support/DefaultRepositoryMethodContext.java
index 8f87c16785..13e14f6ea1 100644
--- a/src/main/java/org/springframework/data/repository/core/support/DefaultRepositoryMethodContext.java
+++ b/src/main/java/org/springframework/data/repository/core/support/DefaultRepositoryMethodContext.java
@@ -17,25 +17,19 @@
import java.lang.reflect.Method;
-import org.springframework.core.NamedThreadLocal;
import org.springframework.data.repository.core.RepositoryMetadata;
-import org.springframework.lang.Nullable;
+import org.springframework.data.repository.core.RepositoryMethodContext;
+import org.springframework.util.Assert;
/**
* Class containing value objects providing information about the current repository method invocation.
*
* @author Christoph Strobl
* @author Mark Paluch
- * @since 3.4.0
+ * @author Oliver Drotbohm
+ * @since 3.4
*/
-class DefaultRepositoryMethodContext implements RepositoryMethodContext {
-
- /**
- * 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 currentMethod = new NamedThreadLocal<>(
- "Current Repository Method");
+public class DefaultRepositoryMethodContext implements RepositoryMethodContext {
private final RepositoryMetadata repositoryMetadata;
private final Method method;
@@ -46,26 +40,22 @@ class DefaultRepositoryMethodContext implements RepositoryMethodContext {
this.method = method;
}
- @Nullable
- static RepositoryMethodContext getMetadata() {
- return currentMethod.get();
- }
-
- @Nullable
- static RepositoryMethodContext setMetadata(@Nullable RepositoryMethodContext metadata) {
+ /**
+ * Creates a new {@link RepositoryMethodContext} for the given {@link Method}.
+ *
+ * @param method must not be {@literal null}.
+ * @return will never be {@literal null}.
+ */
+ public static RepositoryMethodContext forMethod(Method method) {
- RepositoryMethodContext old = currentMethod.get();
- if (metadata != null) {
- currentMethod.set(metadata);
- } else {
- currentMethod.remove();
- }
+ Assert.notNull(method, "Method must not be null!");
- return old;
+ return new DefaultRepositoryMethodContext(AbstractRepositoryMetadata.getMetadata(method.getDeclaringClass()),
+ method);
}
@Override
- public RepositoryMetadata getRepository() {
+ public RepositoryMetadata getMetadata() {
return repositoryMetadata;
}
@@ -73,5 +63,4 @@ public RepositoryMetadata getRepository() {
public Method getMethod() {
return method;
}
-
}
diff --git a/src/main/java/org/springframework/data/repository/core/support/RepositoryFactoryBeanSupport.java b/src/main/java/org/springframework/data/repository/core/support/RepositoryFactoryBeanSupport.java
index 967d02d158..3059bbf209 100644
--- a/src/main/java/org/springframework/data/repository/core/support/RepositoryFactoryBeanSupport.java
+++ b/src/main/java/org/springframework/data/repository/core/support/RepositoryFactoryBeanSupport.java
@@ -121,7 +121,7 @@ public void setRepositoryBaseClass(Class> repositoryBaseClass) {
* 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;
diff --git a/src/main/java/org/springframework/data/repository/core/support/RepositoryFactorySupport.java b/src/main/java/org/springframework/data/repository/core/support/RepositoryFactorySupport.java
index 58b47ad62d..8730942c99 100644
--- a/src/main/java/org/springframework/data/repository/core/support/RepositoryFactorySupport.java
+++ b/src/main/java/org/springframework/data/repository/core/support/RepositoryFactorySupport.java
@@ -56,6 +56,8 @@
import org.springframework.data.repository.core.NamedQueries;
import org.springframework.data.repository.core.RepositoryInformation;
import org.springframework.data.repository.core.RepositoryMetadata;
+import org.springframework.data.repository.core.RepositoryMethodContext;
+import org.springframework.data.repository.core.RepositoryMethodContextHolder;
import org.springframework.data.repository.core.support.RepositoryComposition.RepositoryFragments;
import org.springframework.data.repository.core.support.RepositoryInvocationMulticaster.DefaultRepositoryInvocationMulticaster;
import org.springframework.data.repository.core.support.RepositoryInvocationMulticaster.NoOpRepositoryInvocationMulticaster;
@@ -772,15 +774,18 @@ public ExposeMetadataInterceptor(RepositoryMetadata repositoryMetadata) {
public Object invoke(MethodInvocation invocation) throws Throwable {
RepositoryMethodContext oldMetadata = null;
+
try {
- oldMetadata = RepositoryMethodContext
- .setCurrentMetadata(new DefaultRepositoryMethodContext(repositoryMetadata, invocation.getMethod()));
+
+ oldMetadata = RepositoryMethodContextHolder
+ .setContext(new DefaultRepositoryMethodContext(repositoryMetadata, invocation.getMethod()));
+
return invocation.proceed();
+
} finally {
- RepositoryMethodContext.setCurrentMetadata(oldMetadata);
+ RepositoryMethodContextHolder.setContext(oldMetadata);
}
}
-
}
/**
diff --git a/src/main/java/org/springframework/data/repository/core/support/RepositoryMetadataAccess.java b/src/main/java/org/springframework/data/repository/core/support/RepositoryMetadataAccess.java
index fb4a7b82ce..4122e20617 100644
--- a/src/main/java/org/springframework/data/repository/core/support/RepositoryMetadataAccess.java
+++ b/src/main/java/org/springframework/data/repository/core/support/RepositoryMetadataAccess.java
@@ -27,7 +27,7 @@
*
* @author Mark Paluch
* @since 3.4
- * @see RepositoryMethodContext
+ * @see org.springframework.data.repository.core.RepositoryMethodContext
*/
public interface RepositoryMetadataAccess {
diff --git a/src/test/java/org/springframework/data/repository/config/RepositoryConfigurationDelegateUnitTests.java b/src/test/java/org/springframework/data/repository/config/RepositoryConfigurationDelegateUnitTests.java
index 529671b228..ebdaddef42 100644
--- a/src/test/java/org/springframework/data/repository/config/RepositoryConfigurationDelegateUnitTests.java
+++ b/src/test/java/org/springframework/data/repository/config/RepositoryConfigurationDelegateUnitTests.java
@@ -28,7 +28,6 @@
import org.mockito.junit.jupiter.MockitoExtension;
import org.mockito.junit.jupiter.MockitoSettings;
import org.mockito.quality.Strictness;
-
import org.springframework.aop.framework.Advised;
import org.springframework.aot.hint.RuntimeHints;
import org.springframework.beans.factory.ListableBeanFactory;
diff --git a/src/test/java/org/springframework/data/repository/core/support/RepositoryFactorySupportUnitTests.java b/src/test/java/org/springframework/data/repository/core/support/RepositoryFactorySupportUnitTests.java
index 2661998cb2..f6371c1506 100755
--- a/src/test/java/org/springframework/data/repository/core/support/RepositoryFactorySupportUnitTests.java
+++ b/src/test/java/org/springframework/data/repository/core/support/RepositoryFactorySupportUnitTests.java
@@ -57,6 +57,8 @@
import org.springframework.data.repository.core.NamedQueries;
import org.springframework.data.repository.core.RepositoryInformation;
import org.springframework.data.repository.core.RepositoryMetadata;
+import org.springframework.data.repository.core.RepositoryMethodContext;
+import org.springframework.data.repository.core.RepositoryMethodContextHolder;
import org.springframework.data.repository.core.support.DummyRepositoryFactory.MyRepositoryQuery;
import org.springframework.data.repository.core.support.RepositoryComposition.RepositoryFragments;
import org.springframework.data.repository.core.support.RepositoryMethodInvocationListener.RepositoryMethodInvocation;
@@ -251,11 +253,10 @@ void capturesFailureFromInvocation() {
@Test // GH-3090
void capturesRepositoryMetadata() {
- record Metadata(RepositoryMethodContext context, MethodInvocation methodInvocation) {
- }
+ record Metadata(RepositoryMethodContext context, MethodInvocation methodInvocation) {}
when(factory.queryOne.execute(any(Object[].class)))
- .then(invocation -> new Metadata(RepositoryMethodContext.currentMethod(),
+ .then(invocation -> new Metadata(RepositoryMethodContextHolder.getContext(),
ExposeInvocationInterceptor.currentInvocation()));
factory.setExposeMetadata(true);
@@ -267,7 +268,7 @@ record Metadata(RepositoryMethodContext context, MethodInvocation methodInvocati
Metadata metadata = (Metadata) metadataByLastname;
assertThat(metadata.context().getMethod().getName()).isEqualTo("findMetadataByLastname");
- assertThat(metadata.context().getRepository().getDomainType()).isEqualTo(Object.class);
+ assertThat(metadata.context().getMetadata().getDomainType()).isEqualTo(Object.class);
assertThat(metadata.methodInvocation().getMethod().getName()).isEqualTo("findMetadataByLastname");
}
@@ -278,7 +279,7 @@ record Metadata(RepositoryMethodContext context, MethodInvocation methodInvocati
}
when(factory.queryOne.execute(any(Object[].class)))
- .then(invocation -> new Metadata(RepositoryMethodContext.currentMethod(),
+ .then(invocation -> new Metadata(RepositoryMethodContextHolder.getContext(),
ExposeInvocationInterceptor.currentInvocation()));
var repository = factory.getRepository(ObjectRepository.class, new RepositoryMetadataAccess() {});
@@ -288,7 +289,7 @@ record Metadata(RepositoryMethodContext context, MethodInvocation methodInvocati
Metadata metadata = (Metadata) metadataByLastname;
assertThat(metadata.context().getMethod().getName()).isEqualTo("findMetadataByLastname");
- assertThat(metadata.context().getRepository().getDomainType()).isEqualTo(Object.class);
+ assertThat(metadata.context().getMetadata().getDomainType()).isEqualTo(Object.class);
assertThat(metadata.methodInvocation().getMethod().getName()).isEqualTo("findMetadataByLastname");
}