15
15
*/
16
16
package org .springframework .data .jpa .repository .support ;
17
17
18
- import java .io .Serializable ;
19
18
import java .lang .reflect .Method ;
20
19
import java .util .Collections ;
21
20
import java .util .HashMap ;
21
+ import java .util .HashSet ;
22
22
import java .util .Map ;
23
23
import java .util .Optional ;
24
+ import java .util .Set ;
24
25
import java .util .concurrent .ConcurrentHashMap ;
25
26
import java .util .concurrent .ConcurrentMap ;
26
27
import java .util .function .Predicate ;
30
31
31
32
import org .aopalliance .intercept .MethodInterceptor ;
32
33
import org .aopalliance .intercept .MethodInvocation ;
33
- import org . springframework . aop . Advisor ;
34
+
34
35
import org .springframework .aop .TargetSource ;
35
36
import org .springframework .aop .framework .ProxyFactory ;
36
- import org .springframework .aop .interceptor .ExposeInvocationInterceptor ;
37
- import org .springframework .aop .support .DefaultPointcutAdvisor ;
38
37
import org .springframework .beans .factory .BeanClassLoaderAware ;
39
38
import org .springframework .core .NamedThreadLocal ;
40
- import org .springframework .core .PriorityOrdered ;
41
39
import org .springframework .core .annotation .AnnotatedElementUtils ;
42
40
import org .springframework .core .annotation .AnnotationUtils ;
43
41
import org .springframework .data .jpa .repository .EntityGraph ;
49
47
import org .springframework .transaction .support .TransactionSynchronizationManager ;
50
48
import org .springframework .util .Assert ;
51
49
import org .springframework .util .ClassUtils ;
50
+ import org .springframework .util .ReflectionUtils ;
52
51
53
52
/**
54
53
* {@link RepositoryProxyPostProcessor} that sets up interceptors to read metadata information from the invoked method.
@@ -80,9 +79,7 @@ public void setBeanClassLoader(ClassLoader classLoader) {
80
79
*/
81
80
@ Override
82
81
public void postProcess (ProxyFactory factory , RepositoryInformation repositoryInformation ) {
83
-
84
- factory .addAdvisor (ExposeRepositoryInvocationInterceptor .ADVISOR );
85
- factory .addAdvice (CrudMethodMetadataPopulatingMethodInterceptor .INSTANCE );
82
+ factory .addAdvice (new CrudMethodMetadataPopulatingMethodInterceptor (repositoryInformation ));
86
83
}
87
84
88
85
/**
@@ -107,11 +104,37 @@ CrudMethodMetadata getCrudMethodMetadata() {
107
104
* @author Oliver Gierke
108
105
* @author Thomas Darimont
109
106
*/
110
- enum CrudMethodMetadataPopulatingMethodInterceptor implements MethodInterceptor {
107
+ static class CrudMethodMetadataPopulatingMethodInterceptor implements MethodInterceptor {
111
108
112
- INSTANCE ;
109
+ private static final ThreadLocal <MethodInvocation > currentInvocation = new NamedThreadLocal <>(
110
+ "Current AOP method invocation" );
113
111
114
112
private final ConcurrentMap <Method , CrudMethodMetadata > metadataCache = new ConcurrentHashMap <>();
113
+ private final Set <Method > implementations = new HashSet <>();
114
+
115
+ CrudMethodMetadataPopulatingMethodInterceptor (RepositoryInformation repositoryInformation ) {
116
+
117
+ ReflectionUtils .doWithMethods (repositoryInformation .getRepositoryInterface (), implementations ::add ,
118
+ method -> !repositoryInformation .isQueryMethod (method ));
119
+ }
120
+
121
+ /**
122
+ * Return the AOP Alliance {@link MethodInvocation} object associated with the current invocation.
123
+ *
124
+ * @return the invocation object associated with the current invocation.
125
+ * @throws IllegalStateException if there is no AOP invocation in progress, or if the
126
+ * {@link CrudMethodMetadataPopulatingMethodInterceptor} was not added to this interceptor chain.
127
+ */
128
+ static MethodInvocation currentInvocation () throws IllegalStateException {
129
+
130
+ MethodInvocation mi = currentInvocation .get ();
131
+
132
+ if (mi == null )
133
+ throw new IllegalStateException (
134
+ "No MethodInvocation found: Check that an AOP invocation is in progress, and that the "
135
+ + "CrudMethodMetadataPopulatingMethodInterceptor is upfront in the interceptor chain." );
136
+ return mi ;
137
+ }
115
138
116
139
/*
117
140
* (non-Javadoc)
@@ -121,30 +144,43 @@ enum CrudMethodMetadataPopulatingMethodInterceptor implements MethodInterceptor
121
144
public Object invoke (MethodInvocation invocation ) throws Throwable {
122
145
123
146
Method method = invocation .getMethod ();
124
- CrudMethodMetadata metadata = (CrudMethodMetadata ) TransactionSynchronizationManager .getResource (method );
125
147
126
- if (metadata != null ) {
148
+ if (! implementations . contains ( method ) ) {
127
149
return invocation .proceed ();
128
150
}
129
151
130
- CrudMethodMetadata methodMetadata = metadataCache .get (method );
152
+ MethodInvocation oldInvocation = currentInvocation .get ();
153
+ currentInvocation .set (invocation );
131
154
132
- if ( methodMetadata == null ) {
155
+ try {
133
156
134
- methodMetadata = new DefaultCrudMethodMetadata (method );
135
- CrudMethodMetadata tmp = metadataCache .putIfAbsent (method , methodMetadata );
157
+ CrudMethodMetadata metadata = (CrudMethodMetadata ) TransactionSynchronizationManager .getResource (method );
136
158
137
- if (tmp != null ) {
138
- methodMetadata = tmp ;
159
+ if (metadata != null ) {
160
+ return invocation . proceed () ;
139
161
}
140
- }
141
162
142
- TransactionSynchronizationManager . bindResource (method , methodMetadata );
163
+ CrudMethodMetadata methodMetadata = metadataCache . get (method );
143
164
144
- try {
145
- return invocation .proceed ();
165
+ if (methodMetadata == null ) {
166
+
167
+ methodMetadata = new DefaultCrudMethodMetadata (method );
168
+ CrudMethodMetadata tmp = metadataCache .putIfAbsent (method , methodMetadata );
169
+
170
+ if (tmp != null ) {
171
+ methodMetadata = tmp ;
172
+ }
173
+ }
174
+
175
+ TransactionSynchronizationManager .bindResource (method , methodMetadata );
176
+
177
+ try {
178
+ return invocation .proceed ();
179
+ } finally {
180
+ TransactionSynchronizationManager .unbindResource (method );
181
+ }
146
182
} finally {
147
- TransactionSynchronizationManager . unbindResource ( method );
183
+ currentInvocation . set ( oldInvocation );
148
184
}
149
185
}
150
186
}
@@ -285,7 +321,7 @@ public boolean isStatic() {
285
321
@ Override
286
322
public Object getTarget () {
287
323
288
- MethodInvocation invocation = ExposeRepositoryInvocationInterceptor .currentInvocation ();
324
+ MethodInvocation invocation = CrudMethodMetadataPopulatingMethodInterceptor .currentInvocation ();
289
325
return TransactionSynchronizationManager .getResource (invocation .getMethod ());
290
326
}
291
327
@@ -296,95 +332,4 @@ public Object getTarget() {
296
332
@ Override
297
333
public void releaseTarget (Object target ) {}
298
334
}
299
-
300
- /**
301
- * Own copy of {@link ExposeInvocationInterceptor} scoped to repository proxy method usage to not conflict with
302
- * {@link ExposeInvocationInterceptor} that might expose nested proxy calls to e.g. proxied transaction managers.
303
- *
304
- * @author Mark Paluch
305
- * @since 2.2.0
306
- * @see ExposeInvocationInterceptor
307
- */
308
- @ SuppressWarnings ("serial" )
309
- static class ExposeRepositoryInvocationInterceptor implements MethodInterceptor , PriorityOrdered , Serializable {
310
-
311
- /**
312
- * Singleton instance of this class
313
- */
314
- static final ExposeRepositoryInvocationInterceptor INSTANCE = new ExposeRepositoryInvocationInterceptor ();
315
-
316
- private static final ThreadLocal <MethodInvocation > invocation = new NamedThreadLocal <>(
317
- "Current AOP method invocation" );
318
-
319
- /**
320
- * Singleton advisor for this class. Use in preference to {@code INSTANCE} when using Spring AOP, as it prevents the
321
- * need to create a new Advisor to wrap the instance.
322
- */
323
- static final Advisor ADVISOR = new DefaultPointcutAdvisor (INSTANCE ) {
324
- @ Override
325
- public String toString () {
326
- return ExposeRepositoryInvocationInterceptor .class .getName () + ".ADVISOR" ;
327
- }
328
- };
329
-
330
- /**
331
- * Ensures that only the canonical instance can be created.
332
- */
333
- private ExposeRepositoryInvocationInterceptor () {}
334
-
335
- /**
336
- * Return the AOP Alliance {@link MethodInvocation} object associated with the current invocation.
337
- *
338
- * @return the invocation object associated with the current invocation.
339
- * @throws IllegalStateException if there is no AOP invocation in progress, or if the
340
- * {@link ExposeRepositoryInvocationInterceptor} was not added to this interceptor chain.
341
- */
342
- static MethodInvocation currentInvocation () throws IllegalStateException {
343
-
344
- MethodInvocation mi = invocation .get ();
345
-
346
- if (mi == null )
347
- throw new IllegalStateException (
348
- "No MethodInvocation found: Check that an AOP invocation is in progress, and that the "
349
- + "ExposeRepositoryInvocationInterceptor is upfront in the interceptor chain. Specifically, note that "
350
- + "advices with order HIGHEST_PRECEDENCE will execute before ExposeRepositoryMethodInvocationInterceptor!" );
351
- return mi ;
352
- }
353
-
354
- /*
355
- * (non-Javadoc)
356
- * @see org.aopalliance.intercept.MethodInterceptor#invoke(org.aopalliance.intercept.MethodInvocation)
357
- */
358
- @ Override
359
- public Object invoke (MethodInvocation mi ) throws Throwable {
360
-
361
- MethodInvocation oldInvocation = invocation .get ();
362
- invocation .set (mi );
363
-
364
- try {
365
- return mi .proceed ();
366
- } finally {
367
- invocation .set (oldInvocation );
368
- }
369
- }
370
-
371
- /*
372
- * (non-Javadoc)
373
- * @see org.springframework.core.Ordered#getOrder()
374
- */
375
- @ Override
376
- public int getOrder () {
377
- return PriorityOrdered .HIGHEST_PRECEDENCE + 1 ;
378
- }
379
-
380
- /**
381
- * Required to support serialization. Replaces with canonical instance on deserialization, protecting Singleton
382
- * pattern.
383
- * <p>
384
- * Alternative to overriding the {@code equals} method.
385
- */
386
- private Object readResolve () {
387
- return INSTANCE ;
388
- }
389
- }
390
335
}
0 commit comments