diff --git a/spring-aop/src/main/java/org/springframework/aop/support/annotation/AnnotationMatchingPointcut.java b/spring-aop/src/main/java/org/springframework/aop/support/annotation/AnnotationMatchingPointcut.java index 20af20810f0..d734fbc0f14 100644 --- a/spring-aop/src/main/java/org/springframework/aop/support/annotation/AnnotationMatchingPointcut.java +++ b/spring-aop/src/main/java/org/springframework/aop/support/annotation/AnnotationMatchingPointcut.java @@ -31,6 +31,7 @@ import org.springframework.util.Assert; * {@link #forMethodAnnotation method}. * * @author Juergen Hoeller + * @author Sam Brannen * @since 2.0 * @see AnnotationClassFilter * @see AnnotationMethodMatcher @@ -63,7 +64,7 @@ public class AnnotationMatchingPointcut implements Pointcut { } /** - * Create a new AnnotationMatchingPointcut for the given annotation type. + * Create a new AnnotationMatchingPointcut for the given annotation types. * @param classAnnotationType the annotation type to look for at the class level * (can be {@code null}) * @param methodAnnotationType the annotation type to look for at the method level @@ -76,7 +77,7 @@ public class AnnotationMatchingPointcut implements Pointcut { } /** - * Create a new AnnotationMatchingPointcut for the given annotation type. + * Create a new AnnotationMatchingPointcut for the given annotation types. * @param classAnnotationType the annotation type to look for at the class level * (can be {@code null}) * @param methodAnnotationType the annotation type to look for at the method level @@ -139,10 +140,9 @@ public class AnnotationMatchingPointcut implements Pointcut { @Override public String toString() { - return "AnnotationMatchingPointcut: " + this.classFilter + ", " +this.methodMatcher; + return "AnnotationMatchingPointcut: " + this.classFilter + ", " + this.methodMatcher; } - /** * Factory method for an AnnotationMatchingPointcut that matches * for the specified annotation at the class level. @@ -169,12 +169,13 @@ public class AnnotationMatchingPointcut implements Pointcut { /** * {@link ClassFilter} that delegates to {@link AnnotationUtils#isCandidateClass} * for filtering classes whose methods are not worth searching to begin with. + * @since 5.2 */ private static class AnnotationCandidateClassFilter implements ClassFilter { private final Class annotationType; - public AnnotationCandidateClassFilter(Class annotationType) { + AnnotationCandidateClassFilter(Class annotationType) { this.annotationType = annotationType; } @@ -182,6 +183,29 @@ public class AnnotationMatchingPointcut implements Pointcut { public boolean matches(Class clazz) { return AnnotationUtils.isCandidateClass(clazz, this.annotationType); } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof AnnotationCandidateClassFilter)) { + return false; + } + AnnotationCandidateClassFilter that = (AnnotationCandidateClassFilter) obj; + return this.annotationType.equals(that.annotationType); + } + + @Override + public int hashCode() { + return this.annotationType.hashCode(); + } + + @Override + public String toString() { + return getClass().getName() + ": " + this.annotationType; + } + } } diff --git a/spring-aop/src/test/java/org/springframework/aop/support/AnnotationMatchingPointcutTests.java b/spring-aop/src/test/java/org/springframework/aop/support/AnnotationMatchingPointcutTests.java new file mode 100644 index 00000000000..5f59bf7190a --- /dev/null +++ b/spring-aop/src/test/java/org/springframework/aop/support/AnnotationMatchingPointcutTests.java @@ -0,0 +1,109 @@ +/* + * Copyright 2002-2019 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.support; + +import org.junit.jupiter.api.Test; + +import org.springframework.aop.MethodMatcher; +import org.springframework.aop.Pointcut; +import org.springframework.aop.support.annotation.AnnotationClassFilter; +import org.springframework.aop.support.annotation.AnnotationMatchingPointcut; +import org.springframework.aop.support.annotation.AnnotationMethodMatcher; +import org.springframework.beans.factory.annotation.Qualifier; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Unit tests for {@link AnnotationMatchingPointcut}. + * + * @author Sam Brannen + * @since 5.2 + */ +class AnnotationMatchingPointcutTests { + + @Test + void classLevelPointCuts() { + Pointcut pointcut1 = new AnnotationMatchingPointcut(Qualifier.class, true); + Pointcut pointcut2 = new AnnotationMatchingPointcut(Qualifier.class, true); + Pointcut pointcut3 = new AnnotationMatchingPointcut(Qualifier.class); + + assertThat(pointcut1.getClassFilter().getClass()).isEqualTo(AnnotationClassFilter.class); + assertThat(pointcut2.getClassFilter().getClass()).isEqualTo(AnnotationClassFilter.class); + assertThat(pointcut3.getClassFilter().getClass()).isEqualTo(AnnotationClassFilter.class); + assertThat(pointcut1.getClassFilter().toString()).contains(Qualifier.class.getName()); + + assertThat(pointcut1.getMethodMatcher()).isEqualTo(MethodMatcher.TRUE); + assertThat(pointcut2.getMethodMatcher()).isEqualTo(MethodMatcher.TRUE); + assertThat(pointcut3.getMethodMatcher()).isEqualTo(MethodMatcher.TRUE); + + assertThat(pointcut1).isEqualTo(pointcut2); + assertThat(pointcut1).isNotEqualTo(pointcut3); + assertThat(pointcut1.hashCode()).isEqualTo(pointcut2.hashCode()); + // #1 and #3 have equivalent hash codes even though equals() returns false. + assertThat(pointcut1.hashCode()).isEqualTo(pointcut3.hashCode()); + assertThat(pointcut1.toString()).isEqualTo(pointcut2.toString()); + } + + @Test + void methodLevelPointCuts() { + Pointcut pointcut1 = new AnnotationMatchingPointcut(null, Qualifier.class, true); + Pointcut pointcut2 = new AnnotationMatchingPointcut(null, Qualifier.class, true); + Pointcut pointcut3 = new AnnotationMatchingPointcut(null, Qualifier.class); + + assertThat(pointcut1.getClassFilter().getClass().getSimpleName()).isEqualTo("AnnotationCandidateClassFilter"); + assertThat(pointcut2.getClassFilter().getClass().getSimpleName()).isEqualTo("AnnotationCandidateClassFilter"); + assertThat(pointcut3.getClassFilter().getClass().getSimpleName()).isEqualTo("AnnotationCandidateClassFilter"); + assertThat(pointcut1.getClassFilter().toString()).contains(Qualifier.class.getName()); + + assertThat(pointcut1.getMethodMatcher().getClass()).isEqualTo(AnnotationMethodMatcher.class); + assertThat(pointcut2.getMethodMatcher().getClass()).isEqualTo(AnnotationMethodMatcher.class); + assertThat(pointcut3.getMethodMatcher().getClass()).isEqualTo(AnnotationMethodMatcher.class); + + assertThat(pointcut1).isEqualTo(pointcut2); + // TODO Uncomment the following once AnnotationMethodMatcher.equals(Object) has been fixed. + // assertThat(pointcut1).isNotEqualTo(pointcut3); + assertThat(pointcut1.hashCode()).isEqualTo(pointcut2.hashCode()); + // #1 and #3 have equivalent hash codes even though equals() returns false. + assertThat(pointcut1.hashCode()).isEqualTo(pointcut3.hashCode()); + assertThat(pointcut1.toString()).isEqualTo(pointcut2.toString()); + } + + @Test + void classLevelAndMethodLevelPointCuts() { + Pointcut pointcut1 = new AnnotationMatchingPointcut(Qualifier.class, Qualifier.class, true); + Pointcut pointcut2 = new AnnotationMatchingPointcut(Qualifier.class, Qualifier.class, true); + Pointcut pointcut3 = new AnnotationMatchingPointcut(Qualifier.class, Qualifier.class); + + assertThat(pointcut1.getClassFilter().getClass()).isEqualTo(AnnotationClassFilter.class); + assertThat(pointcut2.getClassFilter().getClass()).isEqualTo(AnnotationClassFilter.class); + assertThat(pointcut3.getClassFilter().getClass()).isEqualTo(AnnotationClassFilter.class); + assertThat(pointcut1.getClassFilter().toString()).contains(Qualifier.class.getName()); + + assertThat(pointcut1.getMethodMatcher().getClass()).isEqualTo(AnnotationMethodMatcher.class); + assertThat(pointcut2.getMethodMatcher().getClass()).isEqualTo(AnnotationMethodMatcher.class); + assertThat(pointcut3.getMethodMatcher().getClass()).isEqualTo(AnnotationMethodMatcher.class); + assertThat(pointcut1.getMethodMatcher().toString()).contains(Qualifier.class.getName()); + + assertThat(pointcut1).isEqualTo(pointcut2); + assertThat(pointcut1).isNotEqualTo(pointcut3); + assertThat(pointcut1.hashCode()).isEqualTo(pointcut2.hashCode()); + // #1 and #3 have equivalent hash codes even though equals() returns false. + assertThat(pointcut1.hashCode()).isEqualTo(pointcut3.hashCode()); + assertThat(pointcut1.toString()).isEqualTo(pointcut2.toString()); + } + +} diff --git a/spring-test/src/test/java/org/springframework/test/context/async/AsyncMethodsSpringTestContextIntegrationTests.java b/spring-test/src/test/java/org/springframework/test/context/async/AsyncMethodsSpringTestContextIntegrationTests.java new file mode 100644 index 00000000000..9083677a3b7 --- /dev/null +++ b/spring-test/src/test/java/org/springframework/test/context/async/AsyncMethodsSpringTestContextIntegrationTests.java @@ -0,0 +1,68 @@ +/* + * Copyright 2002-2019 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.test.context.async; + +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.RepeatedTest; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.scheduling.annotation.Async; +import org.springframework.scheduling.annotation.EnableAsync; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; + +/** + * Integration tests for applications using {@link Async @Async} methods with + * {@code @DirtiesContext}. + * + *

Execute this test class with {@code -Xmx8M} to verify that there are no + * issues with memory leaks as raised in + * gh-23571. + * + * @author Sam Brannen + * @since 5.2 + */ +@SpringJUnitConfig +@Disabled("Only meant to be executed manually") +class AsyncMethodsSpringTestContextIntegrationTests { + + @RepeatedTest(200) + @DirtiesContext + void test() { + // If we don't run out of memory, then this test is a success. + } + + + @Configuration + @EnableAsync + static class Config { + + @Bean + AsyncService asyncService() { + return new AsyncService(); + } + } + + static class AsyncService { + + @Async + void asyncMethod() { + } + } + +}