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
This commit is contained in:
commit
167350d408
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
* Copyright 2002-2025 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.
|
||||
|
|
@ -109,11 +109,11 @@ public class AspectJExpressionPointcut extends AbstractExpressionPointcut
|
|||
|
||||
private @Nullable BeanFactory beanFactory;
|
||||
|
||||
private transient @Nullable ClassLoader pointcutClassLoader;
|
||||
private transient volatile @Nullable ClassLoader pointcutClassLoader;
|
||||
|
||||
private transient @Nullable PointcutExpression pointcutExpression;
|
||||
private transient volatile @Nullable PointcutExpression pointcutExpression;
|
||||
|
||||
private transient boolean pointcutParsingFailed = false;
|
||||
private transient volatile boolean pointcutParsingFailed;
|
||||
|
||||
|
||||
/**
|
||||
|
|
@ -193,11 +193,14 @@ public class AspectJExpressionPointcut extends AbstractExpressionPointcut
|
|||
* Lazily build the underlying AspectJ pointcut expression.
|
||||
*/
|
||||
private PointcutExpression obtainPointcutExpression() {
|
||||
if (this.pointcutExpression == null) {
|
||||
this.pointcutClassLoader = determinePointcutClassLoader();
|
||||
this.pointcutExpression = buildPointcutExpression(this.pointcutClassLoader);
|
||||
PointcutExpression pointcutExpression = this.pointcutExpression;
|
||||
if (pointcutExpression == null) {
|
||||
ClassLoader pointcutClassLoader = determinePointcutClassLoader();
|
||||
pointcutExpression = buildPointcutExpression(pointcutClassLoader);
|
||||
this.pointcutClassLoader = pointcutClassLoader;
|
||||
this.pointcutExpression = pointcutExpression;
|
||||
}
|
||||
return this.pointcutExpression;
|
||||
return pointcutExpression;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -460,40 +463,24 @@ public class AspectJExpressionPointcut extends AbstractExpressionPointcut
|
|||
}
|
||||
|
||||
private ShadowMatch getShadowMatch(Method targetMethod, Method originalMethod) {
|
||||
ShadowMatch shadowMatch = ShadowMatchUtils.getShadowMatch(this, targetMethod);
|
||||
ShadowMatchKey key = new ShadowMatchKey(this, targetMethod);
|
||||
ShadowMatch shadowMatch = ShadowMatchUtils.getShadowMatch(key);
|
||||
if (shadowMatch == null) {
|
||||
PointcutExpression fallbackExpression = null;
|
||||
Method methodToMatch = targetMethod;
|
||||
try {
|
||||
PointcutExpression pointcutExpression = obtainPointcutExpression();
|
||||
synchronized (pointcutExpression) {
|
||||
shadowMatch = ShadowMatchUtils.getShadowMatch(key);
|
||||
if (shadowMatch != null) {
|
||||
return shadowMatch;
|
||||
}
|
||||
PointcutExpression fallbackExpression = null;
|
||||
Method methodToMatch = targetMethod;
|
||||
try {
|
||||
shadowMatch = obtainPointcutExpression().matchesMethodExecution(methodToMatch);
|
||||
}
|
||||
catch (ReflectionWorldException ex) {
|
||||
// Failed to introspect target method, probably because it has been loaded
|
||||
// in a special ClassLoader. Let's try the declaring ClassLoader instead...
|
||||
try {
|
||||
fallbackExpression = getFallbackPointcutExpression(methodToMatch.getDeclaringClass());
|
||||
if (fallbackExpression != null) {
|
||||
shadowMatch = fallbackExpression.matchesMethodExecution(methodToMatch);
|
||||
}
|
||||
}
|
||||
catch (ReflectionWorldException ex2) {
|
||||
fallbackExpression = null;
|
||||
}
|
||||
}
|
||||
if (targetMethod != originalMethod && (shadowMatch == null ||
|
||||
(Proxy.isProxyClass(targetMethod.getDeclaringClass()) &&
|
||||
(shadowMatch.neverMatches() || containsAnnotationPointcut())))) {
|
||||
// Fall back to the plain original method in case of no resolvable match or a
|
||||
// negative match on a proxy class (which doesn't carry any annotations on its
|
||||
// redeclared methods), as well as for annotation pointcuts.
|
||||
methodToMatch = originalMethod;
|
||||
try {
|
||||
shadowMatch = obtainPointcutExpression().matchesMethodExecution(methodToMatch);
|
||||
shadowMatch = pointcutExpression.matchesMethodExecution(methodToMatch);
|
||||
}
|
||||
catch (ReflectionWorldException ex) {
|
||||
// Could neither introspect the target class nor the proxy class ->
|
||||
// let's try the original method's declaring class before we give up...
|
||||
// Failed to introspect target method, probably because it has been loaded
|
||||
// in a special ClassLoader. Let's try the declaring ClassLoader instead...
|
||||
try {
|
||||
fallbackExpression = getFallbackPointcutExpression(methodToMatch.getDeclaringClass());
|
||||
if (fallbackExpression != null) {
|
||||
|
|
@ -504,21 +491,45 @@ public class AspectJExpressionPointcut extends AbstractExpressionPointcut
|
|||
fallbackExpression = null;
|
||||
}
|
||||
}
|
||||
if (targetMethod != originalMethod && (shadowMatch == null ||
|
||||
(Proxy.isProxyClass(targetMethod.getDeclaringClass()) &&
|
||||
(shadowMatch.neverMatches() || containsAnnotationPointcut())))) {
|
||||
// Fall back to the plain original method in case of no resolvable match or a
|
||||
// negative match on a proxy class (which doesn't carry any annotations on its
|
||||
// redeclared methods), as well as for annotation pointcuts.
|
||||
methodToMatch = originalMethod;
|
||||
try {
|
||||
shadowMatch = pointcutExpression.matchesMethodExecution(methodToMatch);
|
||||
}
|
||||
catch (ReflectionWorldException ex) {
|
||||
// Could neither introspect the target class nor the proxy class ->
|
||||
// let's try the original method's declaring class before we give up...
|
||||
try {
|
||||
fallbackExpression = getFallbackPointcutExpression(methodToMatch.getDeclaringClass());
|
||||
if (fallbackExpression != null) {
|
||||
shadowMatch = fallbackExpression.matchesMethodExecution(methodToMatch);
|
||||
}
|
||||
}
|
||||
catch (ReflectionWorldException ex2) {
|
||||
fallbackExpression = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Throwable ex) {
|
||||
// Possibly AspectJ 1.8.10 encountering an invalid signature
|
||||
logger.debug("PointcutExpression matching rejected target method", ex);
|
||||
fallbackExpression = null;
|
||||
}
|
||||
if (shadowMatch == null) {
|
||||
shadowMatch = new ShadowMatchImpl(org.aspectj.util.FuzzyBoolean.NO, null, null, null);
|
||||
}
|
||||
else if (shadowMatch.maybeMatches() && fallbackExpression != null) {
|
||||
shadowMatch = new DefensiveShadowMatch(shadowMatch,
|
||||
fallbackExpression.matchesMethodExecution(methodToMatch));
|
||||
}
|
||||
shadowMatch = ShadowMatchUtils.setShadowMatch(key, shadowMatch);
|
||||
}
|
||||
catch (Throwable ex) {
|
||||
// Possibly AspectJ 1.8.10 encountering an invalid signature
|
||||
logger.debug("PointcutExpression matching rejected target method", ex);
|
||||
fallbackExpression = null;
|
||||
}
|
||||
if (shadowMatch == null) {
|
||||
shadowMatch = new ShadowMatchImpl(org.aspectj.util.FuzzyBoolean.NO, null, null, null);
|
||||
}
|
||||
else if (shadowMatch.maybeMatches() && fallbackExpression != null) {
|
||||
shadowMatch = new DefensiveShadowMatch(shadowMatch,
|
||||
fallbackExpression.matchesMethodExecution(methodToMatch));
|
||||
}
|
||||
shadowMatch = ShadowMatchUtils.setShadowMatch(this, targetMethod, shadowMatch);
|
||||
}
|
||||
return shadowMatch;
|
||||
}
|
||||
|
|
@ -713,4 +724,8 @@ public class AspectJExpressionPointcut extends AbstractExpressionPointcut
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
private record ShadowMatchKey(AspectJExpressionPointcut expression, Method method) {
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
* Copyright 2002-2025 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.
|
||||
|
|
@ -16,24 +16,46 @@
|
|||
|
||||
package org.springframework.aop.aspectj;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import org.aspectj.weaver.tools.ShadowMatch;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
import org.springframework.aop.support.ExpressionPointcut;
|
||||
|
||||
/**
|
||||
* Internal {@link ShadowMatch} utilities.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
* @author Juergen Hoeller
|
||||
* @since 6.2
|
||||
*/
|
||||
public abstract class ShadowMatchUtils {
|
||||
|
||||
private static final Map<Key, ShadowMatch> shadowMatchCache = new ConcurrentHashMap<>(256);
|
||||
private static final Map<Object, ShadowMatch> shadowMatchCache = new ConcurrentHashMap<>(256);
|
||||
|
||||
|
||||
/**
|
||||
* Find a {@link ShadowMatch} for the specified key.
|
||||
* @param key the key to use
|
||||
* @return the {@code ShadowMatch} to use for the specified key,
|
||||
* or {@code null} if none found
|
||||
*/
|
||||
static @Nullable ShadowMatch getShadowMatch(Object key) {
|
||||
return shadowMatchCache.get(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Associate the {@link ShadowMatch} with the specified key.
|
||||
* If an entry already exists, the given {@code shadowMatch} is ignored.
|
||||
* @param key the key to use
|
||||
* @param shadowMatch the shadow match to use for this key
|
||||
* if none already exists
|
||||
* @return the shadow match to use for the specified key
|
||||
*/
|
||||
static ShadowMatch setShadowMatch(Object key, ShadowMatch shadowMatch) {
|
||||
ShadowMatch existing = shadowMatchCache.putIfAbsent(key, shadowMatch);
|
||||
return (existing != null ? existing : shadowMatch);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear the cache of computed {@link ShadowMatch} instances.
|
||||
|
|
@ -42,33 +64,4 @@ public abstract class ShadowMatchUtils {
|
|||
shadowMatchCache.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the {@link ShadowMatch} for the specified {@link ExpressionPointcut}
|
||||
* and {@link Method} or {@code null} if none is found.
|
||||
* @param expression the expression
|
||||
* @param method the method
|
||||
* @return the {@code ShadowMatch} to use for the specified expression and method
|
||||
*/
|
||||
static @Nullable ShadowMatch getShadowMatch(ExpressionPointcut expression, Method method) {
|
||||
return shadowMatchCache.get(new Key(expression, method));
|
||||
}
|
||||
|
||||
/**
|
||||
* Associate the {@link ShadowMatch} to the specified {@link ExpressionPointcut}
|
||||
* and method. If an entry already exists, the given {@code shadowMatch} is
|
||||
* ignored.
|
||||
* @param expression the expression
|
||||
* @param method the method
|
||||
* @param shadowMatch the shadow match to use for this expression and method
|
||||
* if none already exists
|
||||
* @return the shadow match to use for the specified expression and method
|
||||
*/
|
||||
static ShadowMatch setShadowMatch(ExpressionPointcut expression, Method method, ShadowMatch shadowMatch) {
|
||||
ShadowMatch existing = shadowMatchCache.putIfAbsent(new Key(expression, method), shadowMatch);
|
||||
return (existing != null ? existing : shadowMatch);
|
||||
}
|
||||
|
||||
|
||||
private record Key(ExpressionPointcut expression, Method method) {}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -45,7 +45,6 @@ import org.springframework.util.ResourceUtils;
|
|||
*/
|
||||
public abstract class AbstractFileResolvingResource extends AbstractResource {
|
||||
|
||||
@SuppressWarnings("try")
|
||||
@Override
|
||||
public boolean exists() {
|
||||
try {
|
||||
|
|
@ -90,9 +89,15 @@ public abstract class AbstractFileResolvingResource extends AbstractResource {
|
|||
// existence of the entry (or the jar root in case of no entryName).
|
||||
// getJarFile() called for enforced presence check of the jar file,
|
||||
// throwing a NoSuchFileException otherwise (turned to false below).
|
||||
try (JarFile jarFile = jarCon.getJarFile()) {
|
||||
JarFile jarFile = jarCon.getJarFile();
|
||||
try {
|
||||
return (jarCon.getEntryName() == null || jarCon.getJarEntry() != null);
|
||||
}
|
||||
finally {
|
||||
if (!jarCon.getUseCaches()) {
|
||||
jarFile.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (con.getContentLengthLong() > 0) {
|
||||
return true;
|
||||
|
|
|
|||
|
|
@ -44,6 +44,8 @@ import java.util.stream.Collectors;
|
|||
import java.util.zip.ZipEntry;
|
||||
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.junit.jupiter.api.AfterAll;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Nested;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.io.TempDir;
|
||||
|
|
@ -298,8 +300,8 @@ class PathMatchingResourcePatternResolverTests {
|
|||
@Test
|
||||
void rootPatternRetrievalInJarFiles() throws IOException {
|
||||
assertThat(resolver.getResources("classpath*:aspectj*.dtd")).extracting(Resource::getFilename)
|
||||
.as("Could not find aspectj_1_5_0.dtd in the root of the aspectjweaver jar")
|
||||
.containsExactly("aspectj_1_5_0.dtd");
|
||||
.as("Could not find aspectj_1_5_0.dtd in the root of the aspectjweaver jar")
|
||||
.containsExactly("aspectj_1_5_0.dtd");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -310,6 +312,16 @@ class PathMatchingResourcePatternResolverTests {
|
|||
@TempDir
|
||||
Path temp;
|
||||
|
||||
@BeforeAll
|
||||
static void suppressJarCaches() {
|
||||
URLConnection.setDefaultUseCaches("jar", false);
|
||||
}
|
||||
|
||||
@AfterAll
|
||||
static void restoreJarCaches() {
|
||||
URLConnection.setDefaultUseCaches("jar", true);
|
||||
}
|
||||
|
||||
@Test
|
||||
void javaDashJarFindsClassPathManifestEntries() throws Exception {
|
||||
Path lib = this.temp.resolve("lib");
|
||||
|
|
@ -333,6 +345,7 @@ class PathMatchingResourcePatternResolverTests {
|
|||
StreamUtils.copy("test", StandardCharsets.UTF_8, jar);
|
||||
jar.closeEntry();
|
||||
}
|
||||
|
||||
assertThat(new FileSystemResource(path).exists()).isTrue();
|
||||
assertThat(new UrlResource(ResourceUtils.JAR_URL_PREFIX + ResourceUtils.FILE_URL_PREFIX + path + ResourceUtils.JAR_URL_SEPARATOR).exists()).isTrue();
|
||||
assertThat(new UrlResource(ResourceUtils.JAR_URL_PREFIX + ResourceUtils.FILE_URL_PREFIX + path + ResourceUtils.JAR_URL_SEPARATOR + "assets/file.txt").exists()).isTrue();
|
||||
|
|
@ -340,6 +353,14 @@ class PathMatchingResourcePatternResolverTests {
|
|||
assertThat(new UrlResource(ResourceUtils.JAR_URL_PREFIX + ResourceUtils.FILE_URL_PREFIX + "X" + path + ResourceUtils.JAR_URL_SEPARATOR).exists()).isFalse();
|
||||
assertThat(new UrlResource(ResourceUtils.JAR_URL_PREFIX + ResourceUtils.FILE_URL_PREFIX + "X" + path + ResourceUtils.JAR_URL_SEPARATOR + "assets/file.txt").exists()).isFalse();
|
||||
assertThat(new UrlResource(ResourceUtils.JAR_URL_PREFIX + ResourceUtils.FILE_URL_PREFIX + "X" + path + ResourceUtils.JAR_URL_SEPARATOR + "assets/none.txt").exists()).isFalse();
|
||||
|
||||
Resource resource = new UrlResource(ResourceUtils.JAR_URL_PREFIX + ResourceUtils.FILE_URL_PREFIX + path + ResourceUtils.JAR_URL_SEPARATOR + "assets/file.txt");
|
||||
try (InputStream is = resource.getInputStream()) {
|
||||
assertThat(resource.exists()).isTrue();
|
||||
assertThat(resource.createRelative("file.txt").exists()).isTrue();
|
||||
assertThat(new UrlResource(ResourceUtils.JAR_URL_PREFIX + ResourceUtils.FILE_URL_PREFIX + path + ResourceUtils.JAR_URL_SEPARATOR).exists()).isTrue();
|
||||
is.readAllBytes();
|
||||
}
|
||||
}
|
||||
|
||||
private void writeApplicationJar(Path path) throws Exception {
|
||||
|
|
|
|||
Loading…
Reference in New Issue