Skip to content

Commit 045df81

Browse files
committed
Reduce ProxyCallbackFilter to key-only role after class generation
Avoids memory leaks from ProxyCallbackFilter-contained Advisors. Includes consistent ProxyCallbackFilter#equals/hashCode methods. Closes gh-26266 Closes gh-30615
1 parent e210f08 commit 045df81

File tree

3 files changed

+140
-71
lines changed

3 files changed

+140
-71
lines changed

spring-aop/src/main/java/org/springframework/aop/framework/AdvisedSupport.java

Lines changed: 79 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2022 the original author or authors.
2+
* Copyright 2002-2023 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.
@@ -22,6 +22,7 @@
2222
import java.util.ArrayList;
2323
import java.util.Arrays;
2424
import java.util.Collection;
25+
import java.util.Collections;
2526
import java.util.List;
2627
import java.util.Map;
2728
import java.util.concurrent.ConcurrentHashMap;
@@ -32,6 +33,8 @@
3233
import org.springframework.aop.DynamicIntroductionAdvice;
3334
import org.springframework.aop.IntroductionAdvisor;
3435
import org.springframework.aop.IntroductionInfo;
36+
import org.springframework.aop.Pointcut;
37+
import org.springframework.aop.PointcutAdvisor;
3538
import org.springframework.aop.TargetSource;
3639
import org.springframework.aop.support.DefaultIntroductionAdvisor;
3740
import org.springframework.aop.support.DefaultPointcutAdvisor;
@@ -41,6 +44,7 @@
4144
import org.springframework.util.Assert;
4245
import org.springframework.util.ClassUtils;
4346
import org.springframework.util.CollectionUtils;
47+
import org.springframework.util.ObjectUtils;
4448

4549
/**
4650
* Base class for AOP proxy configuration managers.
@@ -72,15 +76,13 @@ public class AdvisedSupport extends ProxyConfig implements Advised {
7276

7377

7478
/** Package-protected to allow direct access for efficiency. */
75-
@SuppressWarnings("serial")
7679
TargetSource targetSource = EMPTY_TARGET_SOURCE;
7780

7881
/** Whether the Advisors are already filtered for the specific target class. */
7982
private boolean preFiltered = false;
8083

8184
/** The AdvisorChainFactory to use. */
82-
@SuppressWarnings("serial")
83-
AdvisorChainFactory advisorChainFactory = new DefaultAdvisorChainFactory();
85+
private AdvisorChainFactory advisorChainFactory;
8486

8587
/** Cache with Method as key and advisor chain List as value. */
8688
private transient Map<MethodCacheKey, List<Object>> methodCache;
@@ -89,21 +91,22 @@ public class AdvisedSupport extends ProxyConfig implements Advised {
8991
* Interfaces to be implemented by the proxy. Held in List to keep the order
9092
* of registration, to create JDK proxy with specified order of interfaces.
9193
*/
92-
@SuppressWarnings("serial")
9394
private List<Class<?>> interfaces = new ArrayList<>();
9495

9596
/**
9697
* List of Advisors. If an Advice is added, it will be wrapped
9798
* in an Advisor before being added to this List.
9899
*/
99-
@SuppressWarnings("serial")
100100
private List<Advisor> advisors = new ArrayList<>();
101101

102+
private List<Advisor> advisorKey = this.advisors;
103+
102104

103105
/**
104106
* No-arg constructor for use as a JavaBean.
105107
*/
106108
public AdvisedSupport() {
109+
this.advisorChainFactory = DefaultAdvisorChainFactory.INSTANCE;
107110
this.methodCache = new ConcurrentHashMap<>(32);
108111
}
109112

@@ -116,6 +119,15 @@ public AdvisedSupport(Class<?>... interfaces) {
116119
setInterfaces(interfaces);
117120
}
118121

122+
/**
123+
* Internal constructor for {@link #getConfigurationOnlyCopy()}.
124+
* @since 6.0.10
125+
*/
126+
private AdvisedSupport(AdvisorChainFactory advisorChainFactory, Map<MethodCacheKey, List<Object>> methodCache) {
127+
this.advisorChainFactory = advisorChainFactory;
128+
this.methodCache = methodCache;
129+
}
130+
119131

120132
/**
121133
* Set the given object as target.
@@ -520,15 +532,27 @@ protected void copyConfigurationFrom(AdvisedSupport other, TargetSource targetSo
520532
* replacing the TargetSource.
521533
*/
522534
AdvisedSupport getConfigurationOnlyCopy() {
523-
AdvisedSupport copy = new AdvisedSupport();
535+
AdvisedSupport copy = new AdvisedSupport(this.advisorChainFactory, this.methodCache);
524536
copy.copyFrom(this);
525537
copy.targetSource = EmptyTargetSource.forClass(getTargetClass(), getTargetSource().isStatic());
526-
copy.advisorChainFactory = this.advisorChainFactory;
527538
copy.interfaces = new ArrayList<>(this.interfaces);
528539
copy.advisors = new ArrayList<>(this.advisors);
540+
copy.advisorKey = new ArrayList<>(this.advisors.size());
541+
for (Advisor advisor : this.advisors) {
542+
copy.advisorKey.add(new AdvisorKeyEntry(advisor));
543+
}
529544
return copy;
530545
}
531546

547+
void reduceToAdvisorKey() {
548+
this.advisors = this.advisorKey;
549+
this.methodCache = Collections.emptyMap();
550+
}
551+
552+
Object getAdvisorKey() {
553+
return this.advisorKey;
554+
}
555+
532556

533557
//---------------------------------------------------------------------
534558
// Serialization support
@@ -604,4 +628,51 @@ public int compareTo(MethodCacheKey other) {
604628
}
605629
}
606630

631+
632+
/**
633+
* Stub for an Advisor instance that is just needed for key purposes,
634+
* allowing for efficient equals and hashCode comparisons against the
635+
* advice class and the pointcut.
636+
* @since 6.0.10
637+
* @see #getConfigurationOnlyCopy()
638+
* @see #getAdvisorKey()
639+
*/
640+
private static class AdvisorKeyEntry implements Advisor {
641+
642+
private final Class<?> adviceType;
643+
644+
@Nullable
645+
private String classFilterKey;
646+
647+
@Nullable
648+
private String methodMatcherKey;
649+
650+
public AdvisorKeyEntry(Advisor advisor) {
651+
this.adviceType = advisor.getAdvice().getClass();
652+
if (advisor instanceof PointcutAdvisor pointcutAdvisor) {
653+
Pointcut pointcut = pointcutAdvisor.getPointcut();
654+
this.classFilterKey = ObjectUtils.identityToString(pointcut.getClassFilter());
655+
this.methodMatcherKey = ObjectUtils.identityToString(pointcut.getMethodMatcher());
656+
}
657+
}
658+
659+
@Override
660+
public Advice getAdvice() {
661+
throw new UnsupportedOperationException();
662+
}
663+
664+
@Override
665+
public boolean equals(Object other) {
666+
return (this == other || (other instanceof AdvisorKeyEntry otherEntry &&
667+
this.adviceType == otherEntry.adviceType &&
668+
ObjectUtils.nullSafeEquals(this.classFilterKey, otherEntry.classFilterKey) &&
669+
ObjectUtils.nullSafeEquals(this.methodMatcherKey, otherEntry.methodMatcherKey)));
670+
}
671+
672+
@Override
673+
public int hashCode() {
674+
return this.adviceType.hashCode();
675+
}
676+
}
677+
607678
}

spring-aop/src/main/java/org/springframework/aop/framework/CglibAopProxy.java

Lines changed: 24 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -26,14 +26,11 @@
2626
import java.util.Set;
2727
import java.util.WeakHashMap;
2828

29-
import org.aopalliance.aop.Advice;
3029
import org.aopalliance.intercept.MethodInvocation;
3130
import org.apache.commons.logging.Log;
3231
import org.apache.commons.logging.LogFactory;
3332

34-
import org.springframework.aop.Advisor;
3533
import org.springframework.aop.AopInvocationException;
36-
import org.springframework.aop.PointcutAdvisor;
3734
import org.springframework.aop.RawTargetAccess;
3835
import org.springframework.aop.TargetSource;
3936
import org.springframework.aop.support.AopUtils;
@@ -205,12 +202,21 @@ private Object buildProxy(@Nullable ClassLoader classLoader, boolean classOnly)
205202
types[x] = callbacks[x].getClass();
206203
}
207204
// fixedInterceptorMap only populated at this point, after getCallbacks call above
208-
enhancer.setCallbackFilter(new ProxyCallbackFilter(
209-
this.advised.getConfigurationOnlyCopy(), this.fixedInterceptorMap, this.fixedInterceptorOffset));
205+
ProxyCallbackFilter filter = new ProxyCallbackFilter(
206+
this.advised.getConfigurationOnlyCopy(), this.fixedInterceptorMap, this.fixedInterceptorOffset);
207+
enhancer.setCallbackFilter(filter);
210208
enhancer.setCallbackTypes(types);
211209

212210
// Generate the proxy class and create a proxy instance.
213-
return (classOnly ? createProxyClass(enhancer) : createProxyClassAndInstance(enhancer, callbacks));
211+
// ProxyCallbackFilter has method introspection capability with Advisor access.
212+
try {
213+
return (classOnly ? createProxyClass(enhancer) : createProxyClassAndInstance(enhancer, callbacks));
214+
}
215+
finally {
216+
// Reduce ProxyCallbackFilter to key-only state for its class cache role
217+
// in the CGLIB$CALLBACK_FILTER field, not leaking any Advisor state...
218+
filter.advised.reduceToAdvisorKey();
219+
}
214220
}
215221
catch (CodeGenerationException | IllegalArgumentException ex) {
216222
throw new AopConfigException("Could not generate CGLIB subclass of " + this.advised.getTargetClass() +
@@ -294,9 +300,9 @@ else if (logger.isDebugEnabled() && !Modifier.isPublic(mod) && !Modifier.isProte
294300

295301
private Callback[] getCallbacks(Class<?> rootClass) throws Exception {
296302
// Parameters used for optimization choices...
297-
boolean exposeProxy = this.advised.isExposeProxy();
298-
boolean isFrozen = this.advised.isFrozen();
299303
boolean isStatic = this.advised.getTargetSource().isStatic();
304+
boolean isFrozen = this.advised.isFrozen();
305+
boolean exposeProxy = this.advised.isExposeProxy();
300306

301307
// Choose an "aop" interceptor (used for AOP calls).
302308
Callback aopInterceptor = new DynamicAdvisedInterceptor(this.advised);
@@ -776,7 +782,7 @@ public Object proceed() throws Throwable {
776782
*/
777783
private static class ProxyCallbackFilter implements CallbackFilter {
778784

779-
private final AdvisedSupport advised;
785+
final AdvisedSupport advised;
780786

781787
private final Map<Method, Integer> fixedInterceptorMap;
782788

@@ -857,9 +863,9 @@ public int accept(Method method) {
857863
// Proxy is not yet available, but that shouldn't matter.
858864
List<?> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
859865
boolean haveAdvice = !chain.isEmpty();
860-
boolean exposeProxy = this.advised.isExposeProxy();
861866
boolean isStatic = this.advised.getTargetSource().isStatic();
862867
boolean isFrozen = this.advised.isFrozen();
868+
boolean exposeProxy = this.advised.isExposeProxy();
863869
if (haveAdvice || !isFrozen) {
864870
// If exposing the proxy, then AOP_PROXY must be used.
865871
if (exposeProxy) {
@@ -921,63 +927,18 @@ public boolean equals(@Nullable Object other) {
921927
return false;
922928
}
923929
AdvisedSupport otherAdvised = otherCallbackFilter.advised;
924-
if (this.advised.isFrozen() != otherAdvised.isFrozen()) {
925-
return false;
926-
}
927-
if (this.advised.isExposeProxy() != otherAdvised.isExposeProxy()) {
928-
return false;
929-
}
930-
if (this.advised.getTargetSource().isStatic() != otherAdvised.getTargetSource().isStatic()) {
931-
return false;
932-
}
933-
if (!AopProxyUtils.equalsProxiedInterfaces(this.advised, otherAdvised)) {
934-
return false;
935-
}
936-
// Advice instance identity is unimportant to the proxy class:
937-
// All that matters is type and ordering.
938-
if (this.advised.getAdvisorCount() != otherAdvised.getAdvisorCount()) {
939-
return false;
940-
}
941-
Advisor[] thisAdvisors = this.advised.getAdvisors();
942-
Advisor[] thatAdvisors = otherAdvised.getAdvisors();
943-
for (int i = 0; i < thisAdvisors.length; i++) {
944-
Advisor thisAdvisor = thisAdvisors[i];
945-
Advisor thatAdvisor = thatAdvisors[i];
946-
if (!equalsAdviceClasses(thisAdvisor, thatAdvisor)) {
947-
return false;
948-
}
949-
if (!equalsPointcuts(thisAdvisor, thatAdvisor)) {
950-
return false;
951-
}
952-
}
953-
return true;
954-
}
955-
956-
private static boolean equalsAdviceClasses(Advisor a, Advisor b) {
957-
return (a.getAdvice().getClass() == b.getAdvice().getClass());
958-
}
959-
960-
private static boolean equalsPointcuts(Advisor a, Advisor b) {
961-
// If only one of the advisor (but not both) is PointcutAdvisor, then it is a mismatch.
962-
// Takes care of the situations where an IntroductionAdvisor is used (see SPR-3959).
963-
return (!(a instanceof PointcutAdvisor pointcutAdvisor1) ||
964-
(b instanceof PointcutAdvisor pointcutAdvisor2 &&
965-
ObjectUtils.nullSafeEquals(pointcutAdvisor1.getPointcut(), pointcutAdvisor2.getPointcut())));
930+
return (this.advised.getAdvisorKey().equals(otherAdvised.getAdvisorKey()) &&
931+
AopProxyUtils.equalsProxiedInterfaces(this.advised, otherAdvised) &&
932+
ObjectUtils.nullSafeEquals(this.advised.getTargetClass(), otherAdvised.getTargetClass()) &&
933+
this.advised.getTargetSource().isStatic() == otherAdvised.getTargetSource().isStatic() &&
934+
this.advised.isFrozen() == otherAdvised.isFrozen() &&
935+
this.advised.isExposeProxy() == otherAdvised.isExposeProxy() &&
936+
this.advised.isOpaque() == otherAdvised.isOpaque());
966937
}
967938

968939
@Override
969940
public int hashCode() {
970-
int hashCode = 0;
971-
Advisor[] advisors = this.advised.getAdvisors();
972-
for (Advisor advisor : advisors) {
973-
Advice advice = advisor.getAdvice();
974-
hashCode = 13 * hashCode + advice.getClass().hashCode();
975-
}
976-
hashCode = 13 * hashCode + (this.advised.isFrozen() ? 1 : 0);
977-
hashCode = 13 * hashCode + (this.advised.isExposeProxy() ? 1 : 0);
978-
hashCode = 13 * hashCode + (this.advised.isOptimize() ? 1 : 0);
979-
hashCode = 13 * hashCode + (this.advised.isOpaque() ? 1 : 0);
980-
return hashCode;
941+
return this.advised.getAdvisorKey().hashCode();
981942
}
982943
}
983944

spring-context/src/test/java/org/springframework/aop/framework/CglibProxyTests.java

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,13 +28,15 @@
2828
import org.springframework.aop.Pointcut;
2929
import org.springframework.aop.support.AopUtils;
3030
import org.springframework.aop.support.DefaultPointcutAdvisor;
31+
import org.springframework.aop.support.annotation.AnnotationMatchingPointcut;
3132
import org.springframework.aop.testfixture.advice.CountingBeforeAdvice;
3233
import org.springframework.aop.testfixture.interceptor.NopInterceptor;
3334
import org.springframework.beans.testfixture.beans.ITestBean;
3435
import org.springframework.beans.testfixture.beans.TestBean;
3536
import org.springframework.context.ApplicationContext;
3637
import org.springframework.context.ApplicationContextException;
3738
import org.springframework.context.support.ClassPathXmlApplicationContext;
39+
import org.springframework.lang.NonNull;
3840
import org.springframework.lang.Nullable;
3941

4042
import static org.assertj.core.api.Assertions.assertThat;
@@ -304,6 +306,8 @@ public void testProxyAProxyWithAdditionalInterface() {
304306
CglibAopProxy cglib = new CglibAopProxy(as);
305307

306308
ITestBean proxy1 = (ITestBean) cglib.getProxy();
309+
ITestBean proxy1a = (ITestBean) cglib.getProxy();
310+
assertThat(proxy1a.getClass()).isSameAs(proxy1.getClass());
307311

308312
mockTargetSource.setTarget(proxy1);
309313
as = new AdvisedSupport(new Class<?>[]{});
@@ -313,6 +317,39 @@ public void testProxyAProxyWithAdditionalInterface() {
313317

314318
ITestBean proxy2 = (ITestBean) cglib.getProxy();
315319
assertThat(proxy2).isInstanceOf(Serializable.class);
320+
assertThat(proxy2.getClass()).isNotSameAs(proxy1.getClass());
321+
322+
ITestBean proxy2a = (ITestBean) cglib.getProxy();
323+
assertThat(proxy2a).isInstanceOf(Serializable.class);
324+
assertThat(proxy2a.getClass()).isSameAs(proxy2.getClass());
325+
326+
mockTargetSource.setTarget(proxy1);
327+
as = new AdvisedSupport(new Class<?>[]{});
328+
as.setTargetSource(mockTargetSource);
329+
as.addAdvisor(new DefaultPointcutAdvisor(new AnnotationMatchingPointcut(Nullable.class), new NopInterceptor()));
330+
cglib = new CglibAopProxy(as);
331+
332+
ITestBean proxy3 = (ITestBean) cglib.getProxy();
333+
assertThat(proxy3).isInstanceOf(Serializable.class);
334+
assertThat(proxy3.getClass()).isNotSameAs(proxy2.getClass());
335+
336+
ITestBean proxy3a = (ITestBean) cglib.getProxy();
337+
assertThat(proxy3a).isInstanceOf(Serializable.class);
338+
assertThat(proxy3a.getClass()).isSameAs(proxy3.getClass());
339+
340+
mockTargetSource.setTarget(proxy1);
341+
as = new AdvisedSupport(new Class<?>[]{});
342+
as.setTargetSource(mockTargetSource);
343+
as.addAdvisor(new DefaultPointcutAdvisor(new AnnotationMatchingPointcut(NonNull.class), new NopInterceptor()));
344+
cglib = new CglibAopProxy(as);
345+
346+
ITestBean proxy4 = (ITestBean) cglib.getProxy();
347+
assertThat(proxy4).isInstanceOf(Serializable.class);
348+
assertThat(proxy4.getClass()).isNotSameAs(proxy3.getClass());
349+
350+
ITestBean proxy4a = (ITestBean) cglib.getProxy();
351+
assertThat(proxy4a).isInstanceOf(Serializable.class);
352+
assertThat(proxy4a.getClass()).isSameAs(proxy4.getClass());
316353
}
317354

318355
@Test

0 commit comments

Comments
 (0)