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 ecd405442b..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; @@ -31,15 +32,12 @@ import org.springframework.beans.factory.support.AbstractBeanDefinition; import org.springframework.beans.factory.support.BeanDefinitionBuilder; import org.springframework.beans.factory.support.BeanDefinitionRegistry; -import org.springframework.beans.factory.support.RootBeanDefinition; import org.springframework.core.annotation.AnnotationUtils; import org.springframework.core.io.ResourceLoader; import org.springframework.core.log.LogMessage; import org.springframework.dao.InvalidDataAccessApiUsageException; import org.springframework.data.repository.core.RepositoryMetadata; -import org.springframework.data.repository.core.RepositoryMethodContext; import org.springframework.data.repository.core.support.AbstractRepositoryMetadata; -import org.springframework.data.repository.core.support.DefaultRepositoryMethodContext; import org.springframework.lang.Nullable; import org.springframework.util.Assert; import org.springframework.util.StringUtils; @@ -112,13 +110,7 @@ public String getDefaultNamedQueryLocation() { @Override public void registerBeansForRoot(BeanDefinitionRegistry registry, - RepositoryConfigurationSource configurationSource) { - - // A proxy RepositoryMethodContext for dependency injection - registerIfNotAlreadyRegistered( - () -> new RootBeanDefinition(RepositoryMethodContext.class, DefaultRepositoryMethodContext::getInjectionProxy), - registry, "repositoryMethodContextFactory", configurationSource); - } + RepositoryConfigurationSource configurationSource) {} /** * Returns the prefix of the module to be used to create the default location for Spring Data named queries. diff --git a/src/main/java/org/springframework/data/repository/core/RepositoryMethodContext.java b/src/main/java/org/springframework/data/repository/core/RepositoryMethodContext.java index da4b4da3de..12c7829ecf 100644 --- a/src/main/java/org/springframework/data/repository/core/RepositoryMethodContext.java +++ b/src/main/java/org/springframework/data/repository/core/RepositoryMethodContext.java @@ -20,9 +20,9 @@ /** * 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. *

@@ -50,7 +50,7 @@ public interface RepositoryMethodContext { * 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, will never be {@literal null}.. + * @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..bf3fda56d9 --- /dev/null +++ b/src/main/java/org/springframework/data/repository/core/RepositoryMethodContextHolder.java @@ -0,0 +1,92 @@ +/* + * 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; + +/** + * @author Christoph Strobl + * @since 3.4.0 + */ +public class RepositoryMethodContextHolder { + + private static ContextProvider contextSupplier; + + static { + contextSupplier = new ThreadLocalContextProvider(); + } + + @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 currentMethod = new NamedThreadLocal<>( + "Current Repository Method"); + + @Override + @Nullable + public RepositoryMethodContext get() { + return currentMethod.get(); + } + + public void clear() { + currentMethod.remove(); + } + + @Override + @Nullable + public RepositoryMethodContext set(@Nullable RepositoryMethodContext context) { + + RepositoryMethodContext old = currentMethod.get(); + + if (context != null) { + currentMethod.set(context); + } else { + currentMethod.remove(); + } + + return old; + } + } +} 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 cf47441a71..3d02cc2883 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,11 +17,8 @@ import java.lang.reflect.Method; -import org.springframework.aop.framework.ProxyFactory; -import org.springframework.core.NamedThreadLocal; import org.springframework.data.repository.core.RepositoryMetadata; import org.springframework.data.repository.core.RepositoryMethodContext; -import org.springframework.lang.Nullable; import org.springframework.util.Assert; /** @@ -34,13 +31,6 @@ */ public 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"); - private final RepositoryMetadata repositoryMetadata; private final Method method; @@ -64,36 +54,6 @@ public static RepositoryMethodContext forMethod(Method method) { method); } - /** - * Creates a proxy {@link RepositoryMethodContext} instance suitable for dependency injection. - * - * @return will never be {@literal null}. - */ - public static RepositoryMethodContext getInjectionProxy() { - - return ProxyFactory.getProxy(RepositoryMethodContext.class, - new DynamicLookupTargetSource<>(RepositoryMethodContext.class, () -> getInstance())); - } - - @Nullable - static RepositoryMethodContext getInstance() { - return currentMethod.get(); - } - - @Nullable - static RepositoryMethodContext setMetadata(@Nullable RepositoryMethodContext metadata) { - - RepositoryMethodContext old = currentMethod.get(); - - if (metadata != null) { - currentMethod.set(metadata); - } else { - currentMethod.remove(); - } - - return old; - } - @Override public RepositoryMetadata getMetadata() { return repositoryMetadata; 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 b7b0e48d5c..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 @@ -57,6 +57,7 @@ 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; @@ -776,13 +777,13 @@ public Object invoke(MethodInvocation invocation) throws Throwable { try { - oldMetadata = DefaultRepositoryMethodContext - .setMetadata(new DefaultRepositoryMethodContext(repositoryMetadata, invocation.getMethod())); + oldMetadata = RepositoryMethodContextHolder + .setContext(new DefaultRepositoryMethodContext(repositoryMetadata, invocation.getMethod())); return invocation.proceed(); } finally { - DefaultRepositoryMethodContext.setMetadata(oldMetadata); + RepositoryMethodContextHolder.setContext(oldMetadata); } } } 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 a2be6d322e..ebdaddef42 100644 --- a/src/test/java/org/springframework/data/repository/config/RepositoryConfigurationDelegateUnitTests.java +++ b/src/test/java/org/springframework/data/repository/config/RepositoryConfigurationDelegateUnitTests.java @@ -276,24 +276,6 @@ void considersGenericLength() { assertThat(it.getGeneric(1).resolve()).isEqualTo(Person.class); } - @Test // GH-3175 - void registersRepositoryMethodContextForInjection() { - - var environment = new StandardEnvironment(); - var context = new GenericApplicationContext(); - context.registerBean("fragment", MyFragmentImpl.class); - - RepositoryConfigurationSource configSource = new AnnotationRepositoryConfigurationSource( - AnnotationMetadata.introspect(TestConfig.class), EnableRepositories.class, context, environment, - context.getDefaultListableBeanFactory(), new AnnotationBeanNameGenerator()); - - var delegate = new RepositoryConfigurationDelegate(configSource, context, environment); - - delegate.registerRepositoriesIn(context, extension); - - assertThat(context.containsBeanDefinition("repositoryMethodContextFactory")).isTrue(); - } - private static ListableBeanFactory assertLazyRepositoryBeanSetup(Class configClass) { var context = new AnnotationConfigApplicationContext(configClass); 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 6823989c94..6de556045d 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; @@ -254,7 +256,7 @@ void capturesRepositoryMetadata() { record Metadata(RepositoryMethodContext context, MethodInvocation methodInvocation) {} when(factory.queryOne.execute(any(Object[].class))) - .then(invocation -> new Metadata(DefaultRepositoryMethodContext.getInstance(), + .then(invocation -> new Metadata(RepositoryMethodContextHolder.current(), ExposeInvocationInterceptor.currentInvocation())); factory.setExposeMetadata(true); @@ -277,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.current(), ExposeInvocationInterceptor.currentInvocation())); var repository = factory.getRepository(ObjectRepository.class, new RepositoryMetadataAccess() {}); @@ -287,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"); }