Memory leak when @Retryable is used on a @Scope("prorotype") bean. #141
Replaced hashmap with soft reference map
This commit is contained in:
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user