Clear ShadowMatch instances when they are no longer needed
This commit gathers the ShadowMatch instances that AspectJ requires in a dedicated class that can be used to clear the instances when they are no longer required. As those are mainly triggered via AspectJAwareAdvisorAutoProxyCreator, it now implements the necessary callbacks to clear the cache. Closes gh-12334
This commit is contained in:
parent
331bdb066e
commit
87855e2d34
|
|
@ -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<Method, ShadowMatch> 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
|
||||
|
|
|
|||
|
|
@ -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<Key, ShadowMatch> 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) {}
|
||||
|
||||
}
|
||||
|
|
@ -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<Advisor> 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.
|
||||
|
|
|
|||
Loading…
Reference in New Issue