Skip to content

Commit 167350d

Browse files
committed
Merge branch '6.2.x'
# Conflicts: # spring-aop/src/main/java/org/springframework/aop/aspectj/AspectJExpressionPointcut.java # spring-aop/src/main/java/org/springframework/aop/aspectj/ShadowMatchUtils.java
2 parents 826041d + f1ddd05 commit 167350d

File tree

4 files changed

+117
-83
lines changed

4 files changed

+117
-83
lines changed

spring-aop/src/main/java/org/springframework/aop/aspectj/AspectJExpressionPointcut.java

Lines changed: 65 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2024 the original author or authors.
2+
* Copyright 2002-2025 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -109,11 +109,11 @@ public class AspectJExpressionPointcut extends AbstractExpressionPointcut
109109

110110
private @Nullable BeanFactory beanFactory;
111111

112-
private transient @Nullable ClassLoader pointcutClassLoader;
112+
private transient volatile @Nullable ClassLoader pointcutClassLoader;
113113

114-
private transient @Nullable PointcutExpression pointcutExpression;
114+
private transient volatile @Nullable PointcutExpression pointcutExpression;
115115

116-
private transient boolean pointcutParsingFailed = false;
116+
private transient volatile boolean pointcutParsingFailed;
117117

118118

119119
/**
@@ -193,11 +193,14 @@ private void checkExpression() {
193193
* Lazily build the underlying AspectJ pointcut expression.
194194
*/
195195
private PointcutExpression obtainPointcutExpression() {
196-
if (this.pointcutExpression == null) {
197-
this.pointcutClassLoader = determinePointcutClassLoader();
198-
this.pointcutExpression = buildPointcutExpression(this.pointcutClassLoader);
196+
PointcutExpression pointcutExpression = this.pointcutExpression;
197+
if (pointcutExpression == null) {
198+
ClassLoader pointcutClassLoader = determinePointcutClassLoader();
199+
pointcutExpression = buildPointcutExpression(pointcutClassLoader);
200+
this.pointcutClassLoader = pointcutClassLoader;
201+
this.pointcutExpression = pointcutExpression;
199202
}
200-
return this.pointcutExpression;
203+
return pointcutExpression;
201204
}
202205

203206
/**
@@ -460,40 +463,24 @@ private ShadowMatch getTargetShadowMatch(Method method, Class<?> targetClass) {
460463
}
461464

462465
private ShadowMatch getShadowMatch(Method targetMethod, Method originalMethod) {
463-
ShadowMatch shadowMatch = ShadowMatchUtils.getShadowMatch(this, targetMethod);
466+
ShadowMatchKey key = new ShadowMatchKey(this, targetMethod);
467+
ShadowMatch shadowMatch = ShadowMatchUtils.getShadowMatch(key);
464468
if (shadowMatch == null) {
465-
PointcutExpression fallbackExpression = null;
466-
Method methodToMatch = targetMethod;
467-
try {
468-
try {
469-
shadowMatch = obtainPointcutExpression().matchesMethodExecution(methodToMatch);
470-
}
471-
catch (ReflectionWorldException ex) {
472-
// Failed to introspect target method, probably because it has been loaded
473-
// in a special ClassLoader. Let's try the declaring ClassLoader instead...
474-
try {
475-
fallbackExpression = getFallbackPointcutExpression(methodToMatch.getDeclaringClass());
476-
if (fallbackExpression != null) {
477-
shadowMatch = fallbackExpression.matchesMethodExecution(methodToMatch);
478-
}
479-
}
480-
catch (ReflectionWorldException ex2) {
481-
fallbackExpression = null;
482-
}
469+
PointcutExpression pointcutExpression = obtainPointcutExpression();
470+
synchronized (pointcutExpression) {
471+
shadowMatch = ShadowMatchUtils.getShadowMatch(key);
472+
if (shadowMatch != null) {
473+
return shadowMatch;
483474
}
484-
if (targetMethod != originalMethod && (shadowMatch == null ||
485-
(Proxy.isProxyClass(targetMethod.getDeclaringClass()) &&
486-
(shadowMatch.neverMatches() || containsAnnotationPointcut())))) {
487-
// Fall back to the plain original method in case of no resolvable match or a
488-
// negative match on a proxy class (which doesn't carry any annotations on its
489-
// redeclared methods), as well as for annotation pointcuts.
490-
methodToMatch = originalMethod;
475+
PointcutExpression fallbackExpression = null;
476+
Method methodToMatch = targetMethod;
477+
try {
491478
try {
492-
shadowMatch = obtainPointcutExpression().matchesMethodExecution(methodToMatch);
479+
shadowMatch = pointcutExpression.matchesMethodExecution(methodToMatch);
493480
}
494481
catch (ReflectionWorldException ex) {
495-
// Could neither introspect the target class nor the proxy class ->
496-
// let's try the original method's declaring class before we give up...
482+
// Failed to introspect target method, probably because it has been loaded
483+
// in a special ClassLoader. Let's try the declaring ClassLoader instead...
497484
try {
498485
fallbackExpression = getFallbackPointcutExpression(methodToMatch.getDeclaringClass());
499486
if (fallbackExpression != null) {
@@ -504,21 +491,45 @@ private ShadowMatch getShadowMatch(Method targetMethod, Method originalMethod) {
504491
fallbackExpression = null;
505492
}
506493
}
494+
if (targetMethod != originalMethod && (shadowMatch == null ||
495+
(Proxy.isProxyClass(targetMethod.getDeclaringClass()) &&
496+
(shadowMatch.neverMatches() || containsAnnotationPointcut())))) {
497+
// Fall back to the plain original method in case of no resolvable match or a
498+
// negative match on a proxy class (which doesn't carry any annotations on its
499+
// redeclared methods), as well as for annotation pointcuts.
500+
methodToMatch = originalMethod;
501+
try {
502+
shadowMatch = pointcutExpression.matchesMethodExecution(methodToMatch);
503+
}
504+
catch (ReflectionWorldException ex) {
505+
// Could neither introspect the target class nor the proxy class ->
506+
// let's try the original method's declaring class before we give up...
507+
try {
508+
fallbackExpression = getFallbackPointcutExpression(methodToMatch.getDeclaringClass());
509+
if (fallbackExpression != null) {
510+
shadowMatch = fallbackExpression.matchesMethodExecution(methodToMatch);
511+
}
512+
}
513+
catch (ReflectionWorldException ex2) {
514+
fallbackExpression = null;
515+
}
516+
}
517+
}
507518
}
519+
catch (Throwable ex) {
520+
// Possibly AspectJ 1.8.10 encountering an invalid signature
521+
logger.debug("PointcutExpression matching rejected target method", ex);
522+
fallbackExpression = null;
523+
}
524+
if (shadowMatch == null) {
525+
shadowMatch = new ShadowMatchImpl(org.aspectj.util.FuzzyBoolean.NO, null, null, null);
526+
}
527+
else if (shadowMatch.maybeMatches() && fallbackExpression != null) {
528+
shadowMatch = new DefensiveShadowMatch(shadowMatch,
529+
fallbackExpression.matchesMethodExecution(methodToMatch));
530+
}
531+
shadowMatch = ShadowMatchUtils.setShadowMatch(key, shadowMatch);
508532
}
509-
catch (Throwable ex) {
510-
// Possibly AspectJ 1.8.10 encountering an invalid signature
511-
logger.debug("PointcutExpression matching rejected target method", ex);
512-
fallbackExpression = null;
513-
}
514-
if (shadowMatch == null) {
515-
shadowMatch = new ShadowMatchImpl(org.aspectj.util.FuzzyBoolean.NO, null, null, null);
516-
}
517-
else if (shadowMatch.maybeMatches() && fallbackExpression != null) {
518-
shadowMatch = new DefensiveShadowMatch(shadowMatch,
519-
fallbackExpression.matchesMethodExecution(methodToMatch));
520-
}
521-
shadowMatch = ShadowMatchUtils.setShadowMatch(this, targetMethod, shadowMatch);
522533
}
523534
return shadowMatch;
524535
}
@@ -713,4 +724,8 @@ public void setMatchingContext(MatchingContext aMatchContext) {
713724
}
714725
}
715726

727+
728+
private record ShadowMatchKey(AspectJExpressionPointcut expression, Method method) {
729+
}
730+
716731
}
Lines changed: 22 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2024 the original author or authors.
2+
* Copyright 2002-2025 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -16,59 +16,52 @@
1616

1717
package org.springframework.aop.aspectj;
1818

19-
import java.lang.reflect.Method;
2019
import java.util.Map;
2120
import java.util.concurrent.ConcurrentHashMap;
2221

2322
import org.aspectj.weaver.tools.ShadowMatch;
2423
import org.jspecify.annotations.Nullable;
2524

26-
import org.springframework.aop.support.ExpressionPointcut;
27-
2825
/**
2926
* Internal {@link ShadowMatch} utilities.
3027
*
3128
* @author Stephane Nicoll
29+
* @author Juergen Hoeller
3230
* @since 6.2
3331
*/
3432
public abstract class ShadowMatchUtils {
3533

36-
private static final Map<Key, ShadowMatch> shadowMatchCache = new ConcurrentHashMap<>(256);
34+
private static final Map<Object, ShadowMatch> shadowMatchCache = new ConcurrentHashMap<>(256);
3735

38-
/**
39-
* Clear the cache of computed {@link ShadowMatch} instances.
40-
*/
41-
public static void clearCache() {
42-
shadowMatchCache.clear();
43-
}
4436

4537
/**
46-
* Return the {@link ShadowMatch} for the specified {@link ExpressionPointcut}
47-
* and {@link Method} or {@code null} if none is found.
48-
* @param expression the expression
49-
* @param method the method
50-
* @return the {@code ShadowMatch} to use for the specified expression and method
38+
* Find a {@link ShadowMatch} for the specified key.
39+
* @param key the key to use
40+
* @return the {@code ShadowMatch} to use for the specified key,
41+
* or {@code null} if none found
5142
*/
52-
static @Nullable ShadowMatch getShadowMatch(ExpressionPointcut expression, Method method) {
53-
return shadowMatchCache.get(new Key(expression, method));
43+
static @Nullable ShadowMatch getShadowMatch(Object key) {
44+
return shadowMatchCache.get(key);
5445
}
5546

5647
/**
57-
* Associate the {@link ShadowMatch} to the specified {@link ExpressionPointcut}
58-
* and method. If an entry already exists, the given {@code shadowMatch} is
59-
* ignored.
60-
* @param expression the expression
61-
* @param method the method
62-
* @param shadowMatch the shadow match to use for this expression and method
48+
* Associate the {@link ShadowMatch} with the specified key.
49+
* If an entry already exists, the given {@code shadowMatch} is ignored.
50+
* @param key the key to use
51+
* @param shadowMatch the shadow match to use for this key
6352
* if none already exists
64-
* @return the shadow match to use for the specified expression and method
53+
* @return the shadow match to use for the specified key
6554
*/
66-
static ShadowMatch setShadowMatch(ExpressionPointcut expression, Method method, ShadowMatch shadowMatch) {
67-
ShadowMatch existing = shadowMatchCache.putIfAbsent(new Key(expression, method), shadowMatch);
55+
static ShadowMatch setShadowMatch(Object key, ShadowMatch shadowMatch) {
56+
ShadowMatch existing = shadowMatchCache.putIfAbsent(key, shadowMatch);
6857
return (existing != null ? existing : shadowMatch);
6958
}
7059

71-
72-
private record Key(ExpressionPointcut expression, Method method) {}
60+
/**
61+
* Clear the cache of computed {@link ShadowMatch} instances.
62+
*/
63+
public static void clearCache() {
64+
shadowMatchCache.clear();
65+
}
7366

7467
}

spring-core/src/main/java/org/springframework/core/io/AbstractFileResolvingResource.java

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,6 @@
4545
*/
4646
public abstract class AbstractFileResolvingResource extends AbstractResource {
4747

48-
@SuppressWarnings("try")
4948
@Override
5049
public boolean exists() {
5150
try {
@@ -90,9 +89,15 @@ else if (code == HttpURLConnection.HTTP_NOT_FOUND) {
9089
// existence of the entry (or the jar root in case of no entryName).
9190
// getJarFile() called for enforced presence check of the jar file,
9291
// throwing a NoSuchFileException otherwise (turned to false below).
93-
try (JarFile jarFile = jarCon.getJarFile()) {
92+
JarFile jarFile = jarCon.getJarFile();
93+
try {
9494
return (jarCon.getEntryName() == null || jarCon.getJarEntry() != null);
9595
}
96+
finally {
97+
if (!jarCon.getUseCaches()) {
98+
jarFile.close();
99+
}
100+
}
96101
}
97102
else if (con.getContentLengthLong() > 0) {
98103
return true;

spring-core/src/test/java/org/springframework/core/io/support/PathMatchingResourcePatternResolverTests.java

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,8 @@
4444
import java.util.zip.ZipEntry;
4545

4646
import org.apache.commons.logging.LogFactory;
47+
import org.junit.jupiter.api.AfterAll;
48+
import org.junit.jupiter.api.BeforeAll;
4749
import org.junit.jupiter.api.Nested;
4850
import org.junit.jupiter.api.Test;
4951
import org.junit.jupiter.api.io.TempDir;
@@ -298,8 +300,8 @@ void classpathStarWithPatternInJar() {
298300
@Test
299301
void rootPatternRetrievalInJarFiles() throws IOException {
300302
assertThat(resolver.getResources("classpath*:aspectj*.dtd")).extracting(Resource::getFilename)
301-
.as("Could not find aspectj_1_5_0.dtd in the root of the aspectjweaver jar")
302-
.containsExactly("aspectj_1_5_0.dtd");
303+
.as("Could not find aspectj_1_5_0.dtd in the root of the aspectjweaver jar")
304+
.containsExactly("aspectj_1_5_0.dtd");
303305
}
304306
}
305307

@@ -310,6 +312,16 @@ class ClassPathManifestEntries {
310312
@TempDir
311313
Path temp;
312314

315+
@BeforeAll
316+
static void suppressJarCaches() {
317+
URLConnection.setDefaultUseCaches("jar", false);
318+
}
319+
320+
@AfterAll
321+
static void restoreJarCaches() {
322+
URLConnection.setDefaultUseCaches("jar", true);
323+
}
324+
313325
@Test
314326
void javaDashJarFindsClassPathManifestEntries() throws Exception {
315327
Path lib = this.temp.resolve("lib");
@@ -333,13 +345,22 @@ private void writeAssetJar(Path path) throws Exception {
333345
StreamUtils.copy("test", StandardCharsets.UTF_8, jar);
334346
jar.closeEntry();
335347
}
348+
336349
assertThat(new FileSystemResource(path).exists()).isTrue();
337350
assertThat(new UrlResource(ResourceUtils.JAR_URL_PREFIX + ResourceUtils.FILE_URL_PREFIX + path + ResourceUtils.JAR_URL_SEPARATOR).exists()).isTrue();
338351
assertThat(new UrlResource(ResourceUtils.JAR_URL_PREFIX + ResourceUtils.FILE_URL_PREFIX + path + ResourceUtils.JAR_URL_SEPARATOR + "assets/file.txt").exists()).isTrue();
339352
assertThat(new UrlResource(ResourceUtils.JAR_URL_PREFIX + ResourceUtils.FILE_URL_PREFIX + path + ResourceUtils.JAR_URL_SEPARATOR + "assets/none.txt").exists()).isFalse();
340353
assertThat(new UrlResource(ResourceUtils.JAR_URL_PREFIX + ResourceUtils.FILE_URL_PREFIX + "X" + path + ResourceUtils.JAR_URL_SEPARATOR).exists()).isFalse();
341354
assertThat(new UrlResource(ResourceUtils.JAR_URL_PREFIX + ResourceUtils.FILE_URL_PREFIX + "X" + path + ResourceUtils.JAR_URL_SEPARATOR + "assets/file.txt").exists()).isFalse();
342355
assertThat(new UrlResource(ResourceUtils.JAR_URL_PREFIX + ResourceUtils.FILE_URL_PREFIX + "X" + path + ResourceUtils.JAR_URL_SEPARATOR + "assets/none.txt").exists()).isFalse();
356+
357+
Resource resource = new UrlResource(ResourceUtils.JAR_URL_PREFIX + ResourceUtils.FILE_URL_PREFIX + path + ResourceUtils.JAR_URL_SEPARATOR + "assets/file.txt");
358+
try (InputStream is = resource.getInputStream()) {
359+
assertThat(resource.exists()).isTrue();
360+
assertThat(resource.createRelative("file.txt").exists()).isTrue();
361+
assertThat(new UrlResource(ResourceUtils.JAR_URL_PREFIX + ResourceUtils.FILE_URL_PREFIX + path + ResourceUtils.JAR_URL_SEPARATOR).exists()).isTrue();
362+
is.readAllBytes();
363+
}
343364
}
344365

345366
private void writeApplicationJar(Path path) throws Exception {

0 commit comments

Comments
 (0)