diff --git a/src/main/java/org/springframework/retry/annotation/RetryConfiguration.java b/src/main/java/org/springframework/retry/annotation/RetryConfiguration.java index d65ec62..dfb5ee4 100644 --- a/src/main/java/org/springframework/retry/annotation/RetryConfiguration.java +++ b/src/main/java/org/springframework/retry/annotation/RetryConfiguration.java @@ -43,6 +43,7 @@ import org.springframework.retry.backoff.Sleeper; import org.springframework.retry.interceptor.MethodArgumentsKeyGenerator; import org.springframework.retry.interceptor.NewMethodArgumentsIdentifier; import org.springframework.retry.policy.RetryContextCache; +import org.springframework.util.ObjectUtils; import org.springframework.util.ReflectionUtils; import org.springframework.util.ReflectionUtils.MethodCallback; @@ -172,6 +173,18 @@ public class RetryConfiguration extends AbstractPointcutAdvisor implements Intro public boolean matches(Method method, Class targetClass) { return getClassFilter().matches(targetClass) || this.methodResolver.matches(method, targetClass); } + + @Override + public boolean equals(Object other) { + if (this == other) { + return true; + } + if (!(other instanceof AnnotationClassOrMethodPointcut)) { + return false; + } + AnnotationClassOrMethodPointcut otherAdvisor = (AnnotationClassOrMethodPointcut) other; + return ObjectUtils.nullSafeEquals(this.methodResolver, otherAdvisor.methodResolver); + } } diff --git a/src/test/java/org/springframework/retry/annotation/ProxyApplicationTests.java b/src/test/java/org/springframework/retry/annotation/ProxyApplicationTests.java new file mode 100644 index 0000000..38030c6 --- /dev/null +++ b/src/test/java/org/springframework/retry/annotation/ProxyApplicationTests.java @@ -0,0 +1,88 @@ +package org.springframework.retry.annotation; + +import static org.junit.Assert.assertEquals; + +import java.net.URLClassLoader; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Set; +import java.util.Vector; + +import org.junit.Test; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.stereotype.Component; +import org.springframework.test.util.ReflectionTestUtils; + +public class ProxyApplicationTests { + + private Set> classes = new HashSet>(); + + @Test + // See gh-53 + public void contextLoads() { + int count = count(); + runAndClose(); + int base = count(); + runAndClose(); + count = count(); + assertEquals("Class leak", base, count); + runAndClose(); + count = count(); + assertEquals("Class leak", base, count); + runAndClose(); + count = count(); + assertEquals("Class leak", base, count); + } + + @SuppressWarnings("resource") + private void runAndClose() { + ConfigurableApplicationContext run = new AnnotationConfigApplicationContext( + Empty.class); + run.close(); + while (run.getParent() != null) { + ((ConfigurableApplicationContext) run.getParent()).close(); + run = (ConfigurableApplicationContext) run.getParent(); + } + } + + private int count() { + URLClassLoader classLoader = (URLClassLoader) getClass().getClassLoader(); + @SuppressWarnings("unchecked") + Vector> classes = (Vector>) ReflectionTestUtils + .getField(classLoader, "classes"); + Set> news = new HashSet>(); + for (Iterator> iterator = classes.iterator(); iterator.hasNext();) { + Class cls = (Class) iterator.next(); + if (!this.classes.contains(cls)) { + news.add(cls); + } + } + this.classes.addAll(classes); + return classes.size(); + } + + @Configuration + @EnableRetry(proxyTargetClass = true) + protected static class Empty { + + @Bean + public Service service() { + return new Service(); + } + + } + + @Component + static class Service { + + @Retryable + public void handle() { + System.err.println("Handling"); + } + + } + +}