Move responsibility for storing method context to dedicated component.
See #3175. Original pull request: #3176
This commit is contained in:
committed by
Mark Paluch
parent
323aa60808
commit
24c31bfd65
@@ -18,11 +18,14 @@ package org.springframework.data.repository.aot.hint;
|
||||
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 @@ class RepositoryRuntimeHints implements RuntimeHintsRegistrar {
|
||||
// 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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.config.BeanDefinition;
|
||||
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 abstract class RepositoryConfigurationExtensionSupport implements Reposit
|
||||
|
||||
@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.
|
||||
|
||||
@@ -20,9 +20,9 @@ import java.lang.reflect.Method;
|
||||
/**
|
||||
* Interface containing methods and value objects to obtain information about the current repository method invocation.
|
||||
* <p>
|
||||
* 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.
|
||||
* <p>
|
||||
* Spring Data's framework does not expose method metadata by default, as there is a performance cost in doing so.
|
||||
* <p>
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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<RepositoryMethodContext> 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -17,11 +17,8 @@ package org.springframework.data.repository.core.support;
|
||||
|
||||
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 @@ import org.springframework.util.Assert;
|
||||
*/
|
||||
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<RepositoryMethodContext> currentMethod = new NamedThreadLocal<>(
|
||||
"Current Repository Method");
|
||||
|
||||
private final RepositoryMetadata repositoryMetadata;
|
||||
private final Method method;
|
||||
|
||||
@@ -64,36 +54,6 @@ public class DefaultRepositoryMethodContext implements RepositoryMethodContext {
|
||||
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;
|
||||
|
||||
@@ -57,6 +57,7 @@ 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;
|
||||
@@ -776,13 +777,13 @@ public abstract class RepositoryFactorySupport
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -276,24 +276,6 @@ class RepositoryConfigurationDelegateUnitTests {
|
||||
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);
|
||||
|
||||
@@ -57,6 +57,8 @@ import org.springframework.data.repository.core.EntityInformation;
|
||||
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 @@ class RepositoryFactorySupportUnitTests {
|
||||
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 @@ class RepositoryFactorySupportUnitTests {
|
||||
}
|
||||
|
||||
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 @@ class RepositoryFactorySupportUnitTests {
|
||||
|
||||
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");
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user