Skip to content

Commit 24c31bf

Browse files
christophstroblmp911de
authored andcommitted
Move responsibility for storing method context to dedicated component.
See #3175. Original pull request: #3176
1 parent 323aa60 commit 24c31bf

File tree

8 files changed

+119
-79
lines changed

8 files changed

+119
-79
lines changed

src/main/java/org/springframework/data/repository/aot/hint/RepositoryRuntimeHints.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,14 @@
1818
import java.util.Arrays;
1919
import java.util.Properties;
2020

21+
import org.springframework.aop.SpringProxy;
22+
import org.springframework.aop.framework.Advised;
2123
import org.springframework.aot.hint.MemberCategory;
2224
import org.springframework.aot.hint.RuntimeHints;
2325
import org.springframework.aot.hint.RuntimeHintsRegistrar;
2426
import org.springframework.aot.hint.TypeReference;
2527
import org.springframework.beans.factory.BeanFactory;
28+
import org.springframework.core.DecoratingProxy;
2629
import org.springframework.core.io.InputStreamSource;
2730
import org.springframework.data.domain.Example;
2831
import org.springframework.data.mapping.context.MappingContext;
@@ -99,5 +102,13 @@ public void registerHints(RuntimeHints hints, @Nullable ClassLoader classLoader)
99102
// annotated queries
100103
hints.proxies().registerJdkProxy( //
101104
TypeReference.of("org.springframework.data.annotation.QueryAnnotation"));
105+
106+
registerSpringProxy(TypeReference.of("org.springframework.data.repository.core.RepositoryMethodContext"), hints);
107+
}
108+
109+
private static void registerSpringProxy(TypeReference type, RuntimeHints runtimeHints) {
110+
111+
runtimeHints.proxies().registerJdkProxy(type, TypeReference.of(SpringProxy.class),
112+
TypeReference.of(Advised.class), TypeReference.of(DecoratingProxy.class));
102113
}
103114
}

src/main/java/org/springframework/data/repository/config/RepositoryConfigurationExtensionSupport.java

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@
1515
*/
1616
package org.springframework.data.repository.config;
1717

18-
import static org.springframework.beans.factory.support.BeanDefinitionReaderUtils.*;
18+
import static org.springframework.beans.factory.support.BeanDefinitionReaderUtils.GENERATED_BEAN_NAME_SEPARATOR;
19+
import static org.springframework.beans.factory.support.BeanDefinitionReaderUtils.generateBeanName;
1920

2021
import java.lang.annotation.Annotation;
2122
import java.util.Collection;
@@ -31,15 +32,12 @@
3132
import org.springframework.beans.factory.support.AbstractBeanDefinition;
3233
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
3334
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
34-
import org.springframework.beans.factory.support.RootBeanDefinition;
3535
import org.springframework.core.annotation.AnnotationUtils;
3636
import org.springframework.core.io.ResourceLoader;
3737
import org.springframework.core.log.LogMessage;
3838
import org.springframework.dao.InvalidDataAccessApiUsageException;
3939
import org.springframework.data.repository.core.RepositoryMetadata;
40-
import org.springframework.data.repository.core.RepositoryMethodContext;
4140
import org.springframework.data.repository.core.support.AbstractRepositoryMetadata;
42-
import org.springframework.data.repository.core.support.DefaultRepositoryMethodContext;
4341
import org.springframework.lang.Nullable;
4442
import org.springframework.util.Assert;
4543
import org.springframework.util.StringUtils;
@@ -112,13 +110,7 @@ public String getDefaultNamedQueryLocation() {
112110

113111
@Override
114112
public void registerBeansForRoot(BeanDefinitionRegistry registry,
115-
RepositoryConfigurationSource configurationSource) {
116-
117-
// A proxy RepositoryMethodContext for dependency injection
118-
registerIfNotAlreadyRegistered(
119-
() -> new RootBeanDefinition(RepositoryMethodContext.class, DefaultRepositoryMethodContext::getInjectionProxy),
120-
registry, "repositoryMethodContextFactory", configurationSource);
121-
}
113+
RepositoryConfigurationSource configurationSource) {}
122114

123115
/**
124116
* Returns the prefix of the module to be used to create the default location for Spring Data named queries.

src/main/java/org/springframework/data/repository/core/RepositoryMethodContext.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,9 @@
2020
/**
2121
* Interface containing methods and value objects to obtain information about the current repository method invocation.
2222
* <p>
23-
* The {@link #currentMethod()} method is usable if the repository factory is configured to expose the current
24-
* repository method metadata (not the default). It returns the invoked repository method. Target objects or advice can
25-
* use this to make advised calls.
23+
* The {@link #getMetadata()} method is usable if the repository factory is configured to expose the current repository
24+
* method metadata (not the default). It returns the invoked repository method. Target objects or advice can use this to
25+
* make advised calls.
2626
* <p>
2727
* Spring Data's framework does not expose method metadata by default, as there is a performance cost in doing so.
2828
* <p>
@@ -50,7 +50,7 @@ public interface RepositoryMethodContext {
5050
* The method object represents the method as being invoked on the repository interface. It doesn't match the backing
5151
* repository implementation in case the method invocation is delegated to an implementation method.
5252
*
53-
* @return the current method, will never be {@literal null}..
53+
* @return the current method, will never be {@literal null}.
5454
*/
5555
Method getMethod();
5656
}
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
/*
2+
* Copyright 2024 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.data.repository.core;
17+
18+
import org.springframework.core.NamedThreadLocal;
19+
import org.springframework.lang.Nullable;
20+
21+
/**
22+
* @author Christoph Strobl
23+
* @since 3.4.0
24+
*/
25+
public class RepositoryMethodContextHolder {
26+
27+
private static ContextProvider contextSupplier;
28+
29+
static {
30+
contextSupplier = new ThreadLocalContextProvider();
31+
}
32+
33+
@Nullable
34+
public static RepositoryMethodContext setContext(@Nullable RepositoryMethodContext context) {
35+
return contextSupplier.set(context);
36+
}
37+
38+
@Nullable
39+
public static RepositoryMethodContext current() {
40+
return contextSupplier.get();
41+
}
42+
43+
public static void clearContext() {
44+
contextSupplier.clear();
45+
}
46+
47+
interface ContextProvider {
48+
49+
@Nullable
50+
RepositoryMethodContext get();
51+
52+
@Nullable
53+
RepositoryMethodContext set(@Nullable RepositoryMethodContext context);
54+
55+
void clear();
56+
}
57+
58+
static class ThreadLocalContextProvider implements ContextProvider {
59+
60+
/**
61+
* ThreadLocal holder for repository method associated with this thread. Will contain {@code null} unless the
62+
* "exposeMetadata" property on the controlling repository factory configuration has been set to "true".
63+
*/
64+
private static final ThreadLocal<RepositoryMethodContext> currentMethod = new NamedThreadLocal<>(
65+
"Current Repository Method");
66+
67+
@Override
68+
@Nullable
69+
public RepositoryMethodContext get() {
70+
return currentMethod.get();
71+
}
72+
73+
public void clear() {
74+
currentMethod.remove();
75+
}
76+
77+
@Override
78+
@Nullable
79+
public RepositoryMethodContext set(@Nullable RepositoryMethodContext context) {
80+
81+
RepositoryMethodContext old = currentMethod.get();
82+
83+
if (context != null) {
84+
currentMethod.set(context);
85+
} else {
86+
currentMethod.remove();
87+
}
88+
89+
return old;
90+
}
91+
}
92+
}

src/main/java/org/springframework/data/repository/core/support/DefaultRepositoryMethodContext.java

Lines changed: 0 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,8 @@
1717

1818
import java.lang.reflect.Method;
1919

20-
import org.springframework.aop.framework.ProxyFactory;
21-
import org.springframework.core.NamedThreadLocal;
2220
import org.springframework.data.repository.core.RepositoryMetadata;
2321
import org.springframework.data.repository.core.RepositoryMethodContext;
24-
import org.springframework.lang.Nullable;
2522
import org.springframework.util.Assert;
2623

2724
/**
@@ -34,13 +31,6 @@
3431
*/
3532
public class DefaultRepositoryMethodContext implements RepositoryMethodContext {
3633

37-
/**
38-
* ThreadLocal holder for repository method associated with this thread. Will contain {@code null} unless the
39-
* "exposeMetadata" property on the controlling repository factory configuration has been set to "true".
40-
*/
41-
private static final ThreadLocal<RepositoryMethodContext> currentMethod = new NamedThreadLocal<>(
42-
"Current Repository Method");
43-
4434
private final RepositoryMetadata repositoryMetadata;
4535
private final Method method;
4636

@@ -64,36 +54,6 @@ public static RepositoryMethodContext forMethod(Method method) {
6454
method);
6555
}
6656

67-
/**
68-
* Creates a proxy {@link RepositoryMethodContext} instance suitable for dependency injection.
69-
*
70-
* @return will never be {@literal null}.
71-
*/
72-
public static RepositoryMethodContext getInjectionProxy() {
73-
74-
return ProxyFactory.getProxy(RepositoryMethodContext.class,
75-
new DynamicLookupTargetSource<>(RepositoryMethodContext.class, () -> getInstance()));
76-
}
77-
78-
@Nullable
79-
static RepositoryMethodContext getInstance() {
80-
return currentMethod.get();
81-
}
82-
83-
@Nullable
84-
static RepositoryMethodContext setMetadata(@Nullable RepositoryMethodContext metadata) {
85-
86-
RepositoryMethodContext old = currentMethod.get();
87-
88-
if (metadata != null) {
89-
currentMethod.set(metadata);
90-
} else {
91-
currentMethod.remove();
92-
}
93-
94-
return old;
95-
}
96-
9757
@Override
9858
public RepositoryMetadata getMetadata() {
9959
return repositoryMetadata;

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

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@
5757
import org.springframework.data.repository.core.RepositoryInformation;
5858
import org.springframework.data.repository.core.RepositoryMetadata;
5959
import org.springframework.data.repository.core.RepositoryMethodContext;
60+
import org.springframework.data.repository.core.RepositoryMethodContextHolder;
6061
import org.springframework.data.repository.core.support.RepositoryComposition.RepositoryFragments;
6162
import org.springframework.data.repository.core.support.RepositoryInvocationMulticaster.DefaultRepositoryInvocationMulticaster;
6263
import org.springframework.data.repository.core.support.RepositoryInvocationMulticaster.NoOpRepositoryInvocationMulticaster;
@@ -776,13 +777,13 @@ public Object invoke(MethodInvocation invocation) throws Throwable {
776777

777778
try {
778779

779-
oldMetadata = DefaultRepositoryMethodContext
780-
.setMetadata(new DefaultRepositoryMethodContext(repositoryMetadata, invocation.getMethod()));
780+
oldMetadata = RepositoryMethodContextHolder
781+
.setContext(new DefaultRepositoryMethodContext(repositoryMetadata, invocation.getMethod()));
781782

782783
return invocation.proceed();
783784

784785
} finally {
785-
DefaultRepositoryMethodContext.setMetadata(oldMetadata);
786+
RepositoryMethodContextHolder.setContext(oldMetadata);
786787
}
787788
}
788789
}

src/test/java/org/springframework/data/repository/config/RepositoryConfigurationDelegateUnitTests.java

Lines changed: 0 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -276,24 +276,6 @@ void considersGenericLength() {
276276
assertThat(it.getGeneric(1).resolve()).isEqualTo(Person.class);
277277
}
278278

279-
@Test // GH-3175
280-
void registersRepositoryMethodContextForInjection() {
281-
282-
var environment = new StandardEnvironment();
283-
var context = new GenericApplicationContext();
284-
context.registerBean("fragment", MyFragmentImpl.class);
285-
286-
RepositoryConfigurationSource configSource = new AnnotationRepositoryConfigurationSource(
287-
AnnotationMetadata.introspect(TestConfig.class), EnableRepositories.class, context, environment,
288-
context.getDefaultListableBeanFactory(), new AnnotationBeanNameGenerator());
289-
290-
var delegate = new RepositoryConfigurationDelegate(configSource, context, environment);
291-
292-
delegate.registerRepositoriesIn(context, extension);
293-
294-
assertThat(context.containsBeanDefinition("repositoryMethodContextFactory")).isTrue();
295-
}
296-
297279
private static ListableBeanFactory assertLazyRepositoryBeanSetup(Class<?> configClass) {
298280

299281
var context = new AnnotationConfigApplicationContext(configClass);

src/test/java/org/springframework/data/repository/core/support/RepositoryFactorySupportUnitTests.java

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,8 @@
5757
import org.springframework.data.repository.core.NamedQueries;
5858
import org.springframework.data.repository.core.RepositoryInformation;
5959
import org.springframework.data.repository.core.RepositoryMetadata;
60+
import org.springframework.data.repository.core.RepositoryMethodContext;
61+
import org.springframework.data.repository.core.RepositoryMethodContextHolder;
6062
import org.springframework.data.repository.core.support.DummyRepositoryFactory.MyRepositoryQuery;
6163
import org.springframework.data.repository.core.support.RepositoryComposition.RepositoryFragments;
6264
import org.springframework.data.repository.core.support.RepositoryMethodInvocationListener.RepositoryMethodInvocation;
@@ -254,7 +256,7 @@ void capturesRepositoryMetadata() {
254256
record Metadata(RepositoryMethodContext context, MethodInvocation methodInvocation) {}
255257

256258
when(factory.queryOne.execute(any(Object[].class)))
257-
.then(invocation -> new Metadata(DefaultRepositoryMethodContext.getInstance(),
259+
.then(invocation -> new Metadata(RepositoryMethodContextHolder.current(),
258260
ExposeInvocationInterceptor.currentInvocation()));
259261

260262
factory.setExposeMetadata(true);
@@ -277,7 +279,7 @@ record Metadata(RepositoryMethodContext context, MethodInvocation methodInvocati
277279
}
278280

279281
when(factory.queryOne.execute(any(Object[].class)))
280-
.then(invocation -> new Metadata(RepositoryMethodContext.currentMethod(),
282+
.then(invocation -> new Metadata(RepositoryMethodContextHolder.current(),
281283
ExposeInvocationInterceptor.currentInvocation()));
282284

283285
var repository = factory.getRepository(ObjectRepository.class, new RepositoryMetadataAccess() {});
@@ -287,7 +289,7 @@ record Metadata(RepositoryMethodContext context, MethodInvocation methodInvocati
287289

288290
Metadata metadata = (Metadata) metadataByLastname;
289291
assertThat(metadata.context().getMethod().getName()).isEqualTo("findMetadataByLastname");
290-
assertThat(metadata.context().getRepository().getDomainType()).isEqualTo(Object.class);
292+
assertThat(metadata.context().getMetadata().getDomainType()).isEqualTo(Object.class);
291293
assertThat(metadata.methodInvocation().getMethod().getName()).isEqualTo("findMetadataByLastname");
292294
}
293295

0 commit comments

Comments
 (0)