Memory leak when @Retryable is used on a @Scope("prorotype") bean. #141

Replaced hashmap with soft reference map
This commit is contained in:
krolen
2019-02-10 11:12:51 -05:00
committed by Dave Syer
parent d1b505c9f4
commit b279131e40

View File

@@ -22,8 +22,12 @@ import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.atomic.AtomicBoolean;
import javax.naming.OperationNotSupportedException;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
@@ -58,6 +62,7 @@ import org.springframework.retry.policy.MapRetryContextCache;
import org.springframework.retry.policy.RetryContextCache;
import org.springframework.retry.policy.SimpleRetryPolicy;
import org.springframework.retry.support.RetryTemplate;
import org.springframework.util.ConcurrentReferenceHashMap;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.ReflectionUtils.MethodCallback;
import org.springframework.util.StringUtils;
@@ -70,18 +75,24 @@ import org.springframework.util.StringUtils;
* @author Artem Bilan
* @author Gary Russell
* @since 1.1
*
*/
public class AnnotationAwareRetryOperationsInterceptor implements IntroductionInterceptor, BeanFactoryAware {
private static final TemplateParserContext PARSER_CONTEXT = new TemplateParserContext();
private static final TemplateParserContext PARSER_CONTEXT = new TemplateParserContext();
private static final SpelExpressionParser PARSER = new SpelExpressionParser();
private static final SpelExpressionParser PARSER = new SpelExpressionParser();
private final StandardEvaluationContext evaluationContext = new StandardEvaluationContext();
private static final MethodInterceptor NULL_INTERCEPTOR = new MethodInterceptor() {
@Override
public Object invoke(MethodInvocation methodInvocation) throws Throwable {
throw new OperationNotSupportedException("Not supported");
}
};
private final Map<Object, Map<Method, MethodInterceptor>> delegates =
new HashMap<Object, Map<Method, MethodInterceptor>>();
private final StandardEvaluationContext evaluationContext = new StandardEvaluationContext();
private final ConcurrentReferenceHashMap<Object, ConcurrentMap<Method, MethodInterceptor>> delegates =
new ConcurrentReferenceHashMap<Object, ConcurrentMap<Method, MethodInterceptor>>();
private RetryContextCache retryContextCache = new MapRetryContextCache();
@@ -157,40 +168,36 @@ public class AnnotationAwareRetryOperationsInterceptor implements IntroductionIn
}
}
private MethodInterceptor getDelegate(Object target, Method method) {
if (!this.delegates.containsKey(target) || !this.delegates.get(target).containsKey(method)) {
synchronized (this.delegates) {
if (!this.delegates.containsKey(target)) {
this.delegates.put(target, new HashMap<Method, MethodInterceptor>());
}
Map<Method, MethodInterceptor> delegatesForTarget = this.delegates.get(target);
if (!delegatesForTarget.containsKey(method)) {
Retryable retryable = AnnotationUtils.findAnnotation(method, Retryable.class);
if (retryable == null) {
retryable = AnnotationUtils.findAnnotation(method.getDeclaringClass(), Retryable.class);
}
if (retryable == null) {
retryable = findAnnotationOnTarget(target, method);
}
if (retryable == null) {
return delegatesForTarget.put(method, null);
}
MethodInterceptor delegate;
if (StringUtils.hasText(retryable.interceptor())) {
delegate = this.beanFactory.getBean(retryable.interceptor(), MethodInterceptor.class);
}
else if (retryable.stateful()) {
delegate = getStatefulInterceptor(target, method, retryable);
}
else {
delegate = getStatelessInterceptor(target, method, retryable);
}
delegatesForTarget.put(method, delegate);
}
}
}
return this.delegates.get(target).get(method);
}
private MethodInterceptor getDelegate(Object target, Method method) {
ConcurrentMap<Method, MethodInterceptor> cachedMethods = delegates.get(target);
if (cachedMethods == null) {
cachedMethods = new ConcurrentHashMap<Method, MethodInterceptor>();
}
MethodInterceptor delegate = cachedMethods.get(method);
if (delegate == null) {
MethodInterceptor interceptor = NULL_INTERCEPTOR;
Retryable retryable = AnnotationUtils.findAnnotation(method, Retryable.class);
if (retryable == null) {
retryable = AnnotationUtils.findAnnotation(method.getDeclaringClass(), Retryable.class);
}
if (retryable == null) {
retryable = findAnnotationOnTarget(target, method);
}
if (retryable != null) {
if (StringUtils.hasText(retryable.interceptor())) {
interceptor = this.beanFactory.getBean(retryable.interceptor(), MethodInterceptor.class);
} else if (retryable.stateful()) {
interceptor = getStatefulInterceptor(target, method, retryable);
} else {
interceptor = getStatelessInterceptor(target, method, retryable);
}
}
cachedMethods.putIfAbsent(method, interceptor);
delegate = cachedMethods.get(method);
}
delegates.putIfAbsent(target, cachedMethods);
return delegate == NULL_INTERCEPTOR ? null : delegate;
}
private Retryable findAnnotationOnTarget(Object target, Method method) {
try {