Restore synchronization against AspectJ race condition behind PointcutExpression
Closes gh-34735
This commit is contained in:
@@ -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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@@ -112,12 +112,12 @@ public class AspectJExpressionPointcut extends AbstractExpressionPointcut
|
|||||||
private BeanFactory beanFactory;
|
private BeanFactory beanFactory;
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
private transient ClassLoader pointcutClassLoader;
|
private transient volatile ClassLoader pointcutClassLoader;
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
private transient PointcutExpression pointcutExpression;
|
private transient volatile PointcutExpression pointcutExpression;
|
||||||
|
|
||||||
private transient boolean pointcutParsingFailed = false;
|
private transient volatile boolean pointcutParsingFailed;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -197,11 +197,14 @@ public class AspectJExpressionPointcut extends AbstractExpressionPointcut
|
|||||||
* Lazily build the underlying AspectJ pointcut expression.
|
* Lazily build the underlying AspectJ pointcut expression.
|
||||||
*/
|
*/
|
||||||
private PointcutExpression obtainPointcutExpression() {
|
private PointcutExpression obtainPointcutExpression() {
|
||||||
if (this.pointcutExpression == null) {
|
PointcutExpression pointcutExpression = this.pointcutExpression;
|
||||||
this.pointcutClassLoader = determinePointcutClassLoader();
|
if (pointcutExpression == null) {
|
||||||
this.pointcutExpression = buildPointcutExpression(this.pointcutClassLoader);
|
ClassLoader pointcutClassLoader = determinePointcutClassLoader();
|
||||||
|
pointcutExpression = buildPointcutExpression(pointcutClassLoader);
|
||||||
|
this.pointcutClassLoader = pointcutClassLoader;
|
||||||
|
this.pointcutExpression = pointcutExpression;
|
||||||
}
|
}
|
||||||
return this.pointcutExpression;
|
return pointcutExpression;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -467,40 +470,24 @@ public class AspectJExpressionPointcut extends AbstractExpressionPointcut
|
|||||||
}
|
}
|
||||||
|
|
||||||
private ShadowMatch getShadowMatch(Method targetMethod, Method originalMethod) {
|
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) {
|
if (shadowMatch == null) {
|
||||||
PointcutExpression fallbackExpression = null;
|
PointcutExpression pointcutExpression = obtainPointcutExpression();
|
||||||
Method methodToMatch = targetMethod;
|
synchronized (pointcutExpression) {
|
||||||
try {
|
shadowMatch = ShadowMatchUtils.getShadowMatch(key);
|
||||||
|
if (shadowMatch != null) {
|
||||||
|
return shadowMatch;
|
||||||
|
}
|
||||||
|
PointcutExpression fallbackExpression = null;
|
||||||
|
Method methodToMatch = targetMethod;
|
||||||
try {
|
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 {
|
try {
|
||||||
fallbackExpression = getFallbackPointcutExpression(methodToMatch.getDeclaringClass());
|
shadowMatch = pointcutExpression.matchesMethodExecution(methodToMatch);
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
catch (ReflectionWorldException ex) {
|
catch (ReflectionWorldException ex) {
|
||||||
// Could neither introspect the target class nor the proxy class ->
|
// Failed to introspect target method, probably because it has been loaded
|
||||||
// let's try the original method's declaring class before we give up...
|
// in a special ClassLoader. Let's try the declaring ClassLoader instead...
|
||||||
try {
|
try {
|
||||||
fallbackExpression = getFallbackPointcutExpression(methodToMatch.getDeclaringClass());
|
fallbackExpression = getFallbackPointcutExpression(methodToMatch.getDeclaringClass());
|
||||||
if (fallbackExpression != null) {
|
if (fallbackExpression != null) {
|
||||||
@@ -511,21 +498,45 @@ public class AspectJExpressionPointcut extends AbstractExpressionPointcut
|
|||||||
fallbackExpression = null;
|
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;
|
return shadowMatch;
|
||||||
}
|
}
|
||||||
@@ -720,4 +731,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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@@ -16,24 +16,48 @@
|
|||||||
|
|
||||||
package org.springframework.aop.aspectj;
|
package org.springframework.aop.aspectj;
|
||||||
|
|
||||||
import java.lang.reflect.Method;
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
import org.aspectj.weaver.tools.ShadowMatch;
|
import org.aspectj.weaver.tools.ShadowMatch;
|
||||||
|
|
||||||
import org.springframework.aop.support.ExpressionPointcut;
|
|
||||||
import org.springframework.lang.Nullable;
|
import org.springframework.lang.Nullable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Internal {@link ShadowMatch} utilities.
|
* Internal {@link ShadowMatch} utilities.
|
||||||
*
|
*
|
||||||
* @author Stephane Nicoll
|
* @author Stephane Nicoll
|
||||||
|
* @author Juergen Hoeller
|
||||||
* @since 6.2
|
* @since 6.2
|
||||||
*/
|
*/
|
||||||
public abstract class ShadowMatchUtils {
|
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
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
static 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.
|
* Clear the cache of computed {@link ShadowMatch} instances.
|
||||||
@@ -42,34 +66,4 @@ public abstract class ShadowMatchUtils {
|
|||||||
shadowMatchCache.clear();
|
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
|
|
||||||
*/
|
|
||||||
@Nullable
|
|
||||||
static 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) {}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user