diff --git a/spring-aop/src/main/java/org/springframework/aop/aspectj/AspectJExpressionPointcut.java b/spring-aop/src/main/java/org/springframework/aop/aspectj/AspectJExpressionPointcut.java index c1a0ee65042..b448b3f5b65 100644 --- a/spring-aop/src/main/java/org/springframework/aop/aspectj/AspectJExpressionPointcut.java +++ b/spring-aop/src/main/java/org/springframework/aop/aspectj/AspectJExpressionPointcut.java @@ -16,14 +16,10 @@ package org.springframework.aop.aspectj; -import java.io.IOException; -import java.io.ObjectInputStream; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.util.Arrays; -import java.util.Map; import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; import org.aopalliance.intercept.MethodInvocation; import org.apache.commons.logging.Log; @@ -115,8 +111,6 @@ public class AspectJExpressionPointcut extends AbstractExpressionPointcut @Nullable private transient PointcutExpression pointcutExpression; - private transient Map shadowMatchCache = new ConcurrentHashMap<>(32); - /** * Create a new default AspectJExpressionPointcut. @@ -447,72 +441,66 @@ public class AspectJExpressionPointcut extends AbstractExpressionPointcut } private ShadowMatch getShadowMatch(Method targetMethod, Method originalMethod) { - // Avoid lock contention for known Methods through concurrent access... - ShadowMatch shadowMatch = this.shadowMatchCache.get(targetMethod); + String expression = resolveExpression(); + ShadowMatch shadowMatch = ShadowMatchUtils.getShadowMatch(this, targetMethod); if (shadowMatch == null) { - synchronized (this.shadowMatchCache) { - // Not found - now check again with full lock... - PointcutExpression fallbackExpression = null; - shadowMatch = this.shadowMatchCache.get(targetMethod); - if (shadowMatch == null) { - Method methodToMatch = targetMethod; + 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 { - 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); - } - 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; - } - } + fallbackExpression = getFallbackPointcutExpression(methodToMatch.getDeclaringClass()); + if (fallbackExpression != null) { + shadowMatch = fallbackExpression.matchesMethodExecution(methodToMatch); } } - catch (Throwable ex) { - // Possibly AspectJ 1.8.10 encountering an invalid signature - logger.debug("PointcutExpression matching rejected target method", ex); + catch (ReflectionWorldException ex2) { fallbackExpression = null; } - if (shadowMatch == null) { - shadowMatch = new ShadowMatchImpl(org.aspectj.util.FuzzyBoolean.NO, null, null, 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); } - else if (shadowMatch.maybeMatches() && fallbackExpression != null) { - shadowMatch = new DefensiveShadowMatch(shadowMatch, - fallbackExpression.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; + } } - this.shadowMatchCache.put(targetMethod, 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; } @@ -558,19 +546,6 @@ public class AspectJExpressionPointcut extends AbstractExpressionPointcut return sb.toString(); } - //--------------------------------------------------------------------- - // Serialization support - //--------------------------------------------------------------------- - - private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException { - // Rely on default serialization, just initialize state after deserialization. - ois.defaultReadObject(); - - // Initialize transient fields. - // pointcutExpression will be initialized lazily by checkReadyToMatch() - this.shadowMatchCache = new ConcurrentHashMap<>(32); - } - /** * Handler for the Spring-specific {@code bean()} pointcut designator diff --git a/spring-aop/src/main/java/org/springframework/aop/aspectj/ShadowMatchUtils.java b/spring-aop/src/main/java/org/springframework/aop/aspectj/ShadowMatchUtils.java new file mode 100644 index 00000000000..beb3ac63bb9 --- /dev/null +++ b/spring-aop/src/main/java/org/springframework/aop/aspectj/ShadowMatchUtils.java @@ -0,0 +1,75 @@ +/* + * Copyright 2002-2024 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. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +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.springframework.aop.support.ExpressionPointcut; +import org.springframework.lang.Nullable; + +/** + * Internal {@link ShadowMatch} utilities. + * + * @author Stephane Nicoll + * @since 6.2 + */ +public abstract class ShadowMatchUtils { + + private static final Map shadowMatchCache = new ConcurrentHashMap<>(256); + + /** + * Clear the cache of computed {@link ShadowMatch} instances. + */ + public static void clearCache() { + 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) {} + +} diff --git a/spring-aop/src/main/java/org/springframework/aop/aspectj/autoproxy/AspectJAwareAdvisorAutoProxyCreator.java b/spring-aop/src/main/java/org/springframework/aop/aspectj/autoproxy/AspectJAwareAdvisorAutoProxyCreator.java index 3c56c872263..0c205d11464 100644 --- a/spring-aop/src/main/java/org/springframework/aop/aspectj/autoproxy/AspectJAwareAdvisorAutoProxyCreator.java +++ b/spring-aop/src/main/java/org/springframework/aop/aspectj/autoproxy/AspectJAwareAdvisorAutoProxyCreator.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2022 the original author or authors. + * Copyright 2002-2024 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. @@ -28,8 +28,11 @@ import org.springframework.aop.Advisor; import org.springframework.aop.aspectj.AbstractAspectJAdvice; import org.springframework.aop.aspectj.AspectJPointcutAdvisor; import org.springframework.aop.aspectj.AspectJProxyUtils; +import org.springframework.aop.aspectj.ShadowMatchUtils; import org.springframework.aop.framework.autoproxy.AbstractAdvisorAutoProxyCreator; import org.springframework.aop.interceptor.ExposeInvocationInterceptor; +import org.springframework.beans.factory.DisposableBean; +import org.springframework.beans.factory.SmartInitializingSingleton; import org.springframework.core.Ordered; import org.springframework.util.ClassUtils; @@ -44,7 +47,8 @@ import org.springframework.util.ClassUtils; * @since 2.0 */ @SuppressWarnings("serial") -public class AspectJAwareAdvisorAutoProxyCreator extends AbstractAdvisorAutoProxyCreator { +public class AspectJAwareAdvisorAutoProxyCreator extends AbstractAdvisorAutoProxyCreator + implements SmartInitializingSingleton, DisposableBean { private static final Comparator DEFAULT_PRECEDENCE_COMPARATOR = new AspectJPrecedenceComparator(); @@ -108,6 +112,15 @@ public class AspectJAwareAdvisorAutoProxyCreator extends AbstractAdvisorAutoProx return super.shouldSkip(beanClass, beanName); } + @Override + public void afterSingletonsInstantiated() { + ShadowMatchUtils.clearCache(); + } + + @Override + public void destroy() throws Exception { + ShadowMatchUtils.clearCache(); + } /** * Implements AspectJ's {@link PartialComparable} interface for defining partial orderings.