diff --git a/spring-aop/src/main/java/org/springframework/aop/ClassFilter.java b/spring-aop/src/main/java/org/springframework/aop/ClassFilter.java index 7409f77d91db..2cf7400f6bad 100644 --- a/spring-aop/src/main/java/org/springframework/aop/ClassFilter.java +++ b/spring-aop/src/main/java/org/springframework/aop/ClassFilter.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2016 the original author or authors. + * 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. @@ -23,6 +23,11 @@ *

Can be used as part of a {@link Pointcut} or for the entire * targeting of an {@link IntroductionAdvisor}. * + *

Concrete implementations of this interface should provide proper + * implementations of {@link Object#equals(Object)} and {@link Object#hashCode()} + * in order to allow the filter to be used in caching scenarios — for + * example, in proxies generated by CGLIB. + * * @author Rod Johnson * @see Pointcut * @see MethodMatcher diff --git a/spring-aop/src/main/java/org/springframework/aop/aspectj/TypePatternClassFilter.java b/spring-aop/src/main/java/org/springframework/aop/aspectj/TypePatternClassFilter.java index e319bec4b81e..cf1dcd929b15 100644 --- a/spring-aop/src/main/java/org/springframework/aop/aspectj/TypePatternClassFilter.java +++ b/spring-aop/src/main/java/org/springframework/aop/aspectj/TypePatternClassFilter.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2017 the original author or authors. + * 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. @@ -22,6 +22,7 @@ import org.springframework.aop.ClassFilter; import org.springframework.lang.Nullable; import org.springframework.util.Assert; +import org.springframework.util.ObjectUtils; import org.springframework.util.StringUtils; /** @@ -29,6 +30,7 @@ * * @author Rod Johnson * @author Juergen Hoeller + * @author Sam Brannen * @since 2.0 */ public class TypePatternClassFilter implements ClassFilter { @@ -113,4 +115,21 @@ private String replaceBooleanOperators(String pcExpr) { result = StringUtils.replace(result, " or ", " || "); return StringUtils.replace(result, " not ", " ! "); } + + @Override + public boolean equals(Object other) { + return (this == other || (other instanceof TypePatternClassFilter && + ObjectUtils.nullSafeEquals(this.typePattern, ((TypePatternClassFilter) other).typePattern))); + } + + @Override + public int hashCode() { + return ObjectUtils.nullSafeHashCode(this.typePattern); + } + + @Override + public String toString() { + return getClass().getName() + ": " + this.typePattern; + } + } diff --git a/spring-aop/src/main/java/org/springframework/aop/support/ClassFilters.java b/spring-aop/src/main/java/org/springframework/aop/support/ClassFilters.java index 6cca42fbbf75..746ee133ca82 100644 --- a/spring-aop/src/main/java/org/springframework/aop/support/ClassFilters.java +++ b/spring-aop/src/main/java/org/springframework/aop/support/ClassFilters.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2013 the original author or authors. + * 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. @@ -17,6 +17,7 @@ package org.springframework.aop.support; import java.io.Serializable; +import java.util.Arrays; import org.springframework.aop.ClassFilter; import org.springframework.util.Assert; @@ -28,6 +29,7 @@ * @author Rod Johnson * @author Rob Harrop * @author Juergen Hoeller + * @author Sam Brannen * @since 11.11.2003 * @see MethodMatchers * @see Pointcuts @@ -89,9 +91,9 @@ public static ClassFilter intersection(ClassFilter[] classFilters) { @SuppressWarnings("serial") private static class UnionClassFilter implements ClassFilter, Serializable { - private ClassFilter[] filters; + private final ClassFilter[] filters; - public UnionClassFilter(ClassFilter[] filters) { + UnionClassFilter(ClassFilter[] filters) { this.filters = filters; } @@ -115,6 +117,12 @@ public boolean equals(Object other) { public int hashCode() { return ObjectUtils.nullSafeHashCode(this.filters); } + + @Override + public String toString() { + return getClass().getName() + ": " + Arrays.toString(this.filters); + } + } @@ -124,9 +132,9 @@ public int hashCode() { @SuppressWarnings("serial") private static class IntersectionClassFilter implements ClassFilter, Serializable { - private ClassFilter[] filters; + private final ClassFilter[] filters; - public IntersectionClassFilter(ClassFilter[] filters) { + IntersectionClassFilter(ClassFilter[] filters) { this.filters = filters; } @@ -150,6 +158,12 @@ public boolean equals(Object other) { public int hashCode() { return ObjectUtils.nullSafeHashCode(this.filters); } + + @Override + public String toString() { + return getClass().getName() + ": " + Arrays.toString(this.filters); + } + } } diff --git a/spring-aop/src/main/java/org/springframework/aop/support/ControlFlowPointcut.java b/spring-aop/src/main/java/org/springframework/aop/support/ControlFlowPointcut.java index 89cb2023ca1a..5b24edb5bbf5 100644 --- a/spring-aop/src/main/java/org/springframework/aop/support/ControlFlowPointcut.java +++ b/spring-aop/src/main/java/org/springframework/aop/support/ControlFlowPointcut.java @@ -34,14 +34,15 @@ * @author Rod Johnson * @author Rob Harrop * @author Juergen Hoeller + * @author Sam Brannen */ @SuppressWarnings("serial") public class ControlFlowPointcut implements Pointcut, ClassFilter, MethodMatcher, Serializable { - private Class clazz; + private final Class clazz; @Nullable - private String methodName; + private final String methodName; private volatile int evaluations; @@ -142,4 +143,9 @@ public int hashCode() { return code; } + @Override + public String toString() { + return getClass().getName() + ": class = " + this.clazz.getName() + "; methodName = " + methodName; + } + } diff --git a/spring-aop/src/main/java/org/springframework/aop/support/DefaultIntroductionAdvisor.java b/spring-aop/src/main/java/org/springframework/aop/support/DefaultIntroductionAdvisor.java index 65da72f8d343..2dab972470ef 100644 --- a/spring-aop/src/main/java/org/springframework/aop/support/DefaultIntroductionAdvisor.java +++ b/spring-aop/src/main/java/org/springframework/aop/support/DefaultIntroductionAdvisor.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * 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. @@ -168,7 +168,7 @@ public int hashCode() { @Override public String toString() { - return ClassUtils.getShortName(getClass()) + ": advice [" + this.advice + "]; interfaces " + + return getClass().getName() + ": advice [" + this.advice + "]; interfaces " + ClassUtils.classNamesToString(this.interfaces); } diff --git a/spring-aop/src/main/java/org/springframework/aop/support/RootClassFilter.java b/spring-aop/src/main/java/org/springframework/aop/support/RootClassFilter.java index ad559261e50b..3809a303797b 100644 --- a/spring-aop/src/main/java/org/springframework/aop/support/RootClassFilter.java +++ b/spring-aop/src/main/java/org/springframework/aop/support/RootClassFilter.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * 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. @@ -19,19 +19,22 @@ import java.io.Serializable; import org.springframework.aop.ClassFilter; +import org.springframework.util.Assert; /** * Simple ClassFilter implementation that passes classes (and optionally subclasses). * * @author Rod Johnson + * @author Sam Brannen */ @SuppressWarnings("serial") public class RootClassFilter implements ClassFilter, Serializable { - private Class clazz; + private final Class clazz; public RootClassFilter(Class clazz) { + Assert.notNull(clazz, "Class must not be null"); this.clazz = clazz; } @@ -41,4 +44,20 @@ public boolean matches(Class candidate) { return this.clazz.isAssignableFrom(candidate); } + @Override + public boolean equals(Object other) { + return (this == other || (other instanceof RootClassFilter && + this.clazz.equals(((RootClassFilter) other).clazz))); + } + + @Override + public int hashCode() { + return this.clazz.hashCode(); + } + + @Override + public String toString() { + return getClass().getName() + ": " + this.clazz.getName(); + } + } 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 017ad739fb99..b8c7995e1299 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 @@ -1,5 +1,5 @@ /* - * Copyright 2002-2017 the original author or authors. + * 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. @@ -62,7 +62,7 @@ public AnnotationMatchingPointcut(Class classAnnotationTyp } /** - * 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 @@ -75,7 +75,7 @@ public AnnotationMatchingPointcut(@Nullable Class classAnn } /** - * 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 @@ -138,7 +138,7 @@ public int hashCode() { @Override public String toString() { - return "AnnotationMatchingPointcut: " + this.classFilter + ", " +this.methodMatcher; + return "AnnotationMatchingPointcut: " + this.classFilter + ", " + this.methodMatcher; } diff --git a/spring-aop/src/main/java/org/springframework/aop/support/annotation/AnnotationMethodMatcher.java b/spring-aop/src/main/java/org/springframework/aop/support/annotation/AnnotationMethodMatcher.java index 53b9030276c0..549af4c78be8 100644 --- a/spring-aop/src/main/java/org/springframework/aop/support/annotation/AnnotationMethodMatcher.java +++ b/spring-aop/src/main/java/org/springframework/aop/support/annotation/AnnotationMethodMatcher.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * 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. @@ -31,6 +31,7 @@ * interface, if any, and the corresponding method on the target class). * * @author Juergen Hoeller + * @author Sam Brannen * @since 2.0 * @see AnnotationMatchingPointcut */ @@ -94,7 +95,7 @@ public boolean equals(Object other) { return false; } AnnotationMethodMatcher otherMm = (AnnotationMethodMatcher) other; - return this.annotationType.equals(otherMm.annotationType); + return (this.annotationType.equals(otherMm.annotationType) && this.checkInherited == otherMm.checkInherited); } @Override diff --git a/spring-aop/src/test/java/org/springframework/aop/support/ClassFiltersTests.java b/spring-aop/src/test/java/org/springframework/aop/support/ClassFiltersTests.java index 63f42d8984b5..5bebed92b6ae 100644 --- a/spring-aop/src/test/java/org/springframework/aop/support/ClassFiltersTests.java +++ b/spring-aop/src/test/java/org/springframework/aop/support/ClassFiltersTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2013 the original author or authors. + * 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. @@ -26,36 +26,42 @@ import static org.junit.Assert.*; /** + * Unit tests for {@link ClassFilters}. + * * @author Rod Johnson * @author Chris Beams + * @author Sam Brannen */ public class ClassFiltersTests { - private ClassFilter exceptionFilter = new RootClassFilter(Exception.class); + private final ClassFilter exceptionFilter = new RootClassFilter(Exception.class); + + private final ClassFilter interfaceFilter = new RootClassFilter(ITestBean.class); - private ClassFilter itbFilter = new RootClassFilter(ITestBean.class); + private final ClassFilter hasRootCauseFilter = new RootClassFilter(NestedRuntimeException.class); - private ClassFilter hasRootCauseFilter = new RootClassFilter(NestedRuntimeException.class); @Test - public void testUnion() { + public void union() { assertTrue(exceptionFilter.matches(RuntimeException.class)); assertFalse(exceptionFilter.matches(TestBean.class)); - assertFalse(itbFilter.matches(Exception.class)); - assertTrue(itbFilter.matches(TestBean.class)); - ClassFilter union = ClassFilters.union(exceptionFilter, itbFilter); + assertFalse(interfaceFilter.matches(Exception.class)); + assertTrue(interfaceFilter.matches(TestBean.class)); + ClassFilter union = ClassFilters.union(exceptionFilter, interfaceFilter); assertTrue(union.matches(RuntimeException.class)); assertTrue(union.matches(TestBean.class)); + assertTrue(union.toString().matches("^.+UnionClassFilter: \\[.+RootClassFilter: java.lang.Exception, .+RootClassFilter: .+TestBean\\]$")); } @Test - public void testIntersection() { + public void intersection() { assertTrue(exceptionFilter.matches(RuntimeException.class)); assertTrue(hasRootCauseFilter.matches(NestedRuntimeException.class)); ClassFilter intersection = ClassFilters.intersection(exceptionFilter, hasRootCauseFilter); assertFalse(intersection.matches(RuntimeException.class)); assertFalse(intersection.matches(TestBean.class)); assertTrue(intersection.matches(NestedRuntimeException.class)); + assertTrue(intersection.toString().matches("^.+IntersectionClassFilter: \\[.+RootClassFilter: java.lang.Exception, .+RootClassFilter: .+NestedRuntimeException\\]$")); } } diff --git a/spring-aop/src/test/java/org/springframework/aop/support/ControlFlowPointcutTests.java b/spring-aop/src/test/java/org/springframework/aop/support/ControlFlowPointcutTests.java index cf0c9cc299d7..77fdd12113f5 100644 --- a/spring-aop/src/test/java/org/springframework/aop/support/ControlFlowPointcutTests.java +++ b/spring-aop/src/test/java/org/springframework/aop/support/ControlFlowPointcutTests.java @@ -100,6 +100,14 @@ public void testEqualsAndHashCode() throws Exception { assertFalse(new ControlFlowPointcut(One.class, "getAge").hashCode() == new ControlFlowPointcut(One.class).hashCode()); } + @Test + public void testToString() { + assertEquals(ControlFlowPointcut.class.getName() + ": class = " + One.class.getName() + "; methodName = null", + new ControlFlowPointcut(One.class).toString()); + assertEquals(ControlFlowPointcut.class.getName() + ": class = " + One.class.getName() + "; methodName = getAge", + new ControlFlowPointcut(One.class, "getAge").toString()); + } + public class One { int getAge(ITestBean proxied) { return proxied.getAge(); diff --git a/spring-aop/src/test/java/org/springframework/aop/support/annotation/AnnotationMatchingPointcutTests.java b/spring-aop/src/test/java/org/springframework/aop/support/annotation/AnnotationMatchingPointcutTests.java new file mode 100644 index 000000000000..15c027240ad7 --- /dev/null +++ b/spring-aop/src/test/java/org/springframework/aop/support/annotation/AnnotationMatchingPointcutTests.java @@ -0,0 +1,108 @@ +/* + * 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.annotation; + +import org.junit.Test; + +import org.springframework.aop.ClassFilter; +import org.springframework.aop.MethodMatcher; +import org.springframework.aop.Pointcut; +import org.springframework.beans.factory.annotation.Qualifier; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertTrue; + +/** + * Unit tests for {@link AnnotationMatchingPointcut}. + * + * @author Sam Brannen + * @since 5.1.10 + */ +public class AnnotationMatchingPointcutTests { + + @Test + public void classLevelPointCuts() { + Pointcut pointcut1 = new AnnotationMatchingPointcut(Qualifier.class, true); + Pointcut pointcut2 = new AnnotationMatchingPointcut(Qualifier.class, true); + Pointcut pointcut3 = new AnnotationMatchingPointcut(Qualifier.class); + + assertEquals(AnnotationClassFilter.class, pointcut1.getClassFilter().getClass()); + assertEquals(AnnotationClassFilter.class, pointcut2.getClassFilter().getClass()); + assertEquals(AnnotationClassFilter.class, pointcut3.getClassFilter().getClass()); + assertTrue(pointcut1.getClassFilter().toString().contains(Qualifier.class.getName())); + + assertEquals(MethodMatcher.TRUE, pointcut1.getMethodMatcher()); + assertEquals(MethodMatcher.TRUE, pointcut2.getMethodMatcher()); + assertEquals(MethodMatcher.TRUE, pointcut3.getMethodMatcher()); + + assertEquals(pointcut1, pointcut2); + assertNotEquals(pointcut1, pointcut3); + assertEquals(pointcut1.hashCode(), pointcut2.hashCode()); + // #1 and #3 have equivalent hash codes even though equals() returns false. + assertEquals(pointcut1.hashCode(), pointcut3.hashCode()); + assertEquals(pointcut1.toString(), pointcut2.toString()); + } + + @Test + public 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); + + assertEquals(ClassFilter.TRUE, pointcut1.getClassFilter()); + assertEquals(ClassFilter.TRUE, pointcut2.getClassFilter()); + assertEquals(ClassFilter.TRUE, pointcut3.getClassFilter()); + assertEquals("ClassFilter.TRUE", pointcut1.getClassFilter().toString()); + + assertEquals(AnnotationMethodMatcher.class, pointcut1.getMethodMatcher().getClass()); + assertEquals(AnnotationMethodMatcher.class, pointcut2.getMethodMatcher().getClass()); + assertEquals(AnnotationMethodMatcher.class, pointcut3.getMethodMatcher().getClass()); + + assertEquals(pointcut1, pointcut2); + assertNotEquals(pointcut1, pointcut3); + assertEquals(pointcut1.hashCode(), pointcut2.hashCode()); + // #1 and #3 have equivalent hash codes even though equals() returns false. + assertEquals(pointcut1.hashCode(), pointcut3.hashCode()); + assertEquals(pointcut1.toString(), pointcut2.toString()); + } + + @Test + public 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); + + assertEquals(AnnotationClassFilter.class, pointcut1.getClassFilter().getClass()); + assertEquals(AnnotationClassFilter.class, pointcut2.getClassFilter().getClass()); + assertEquals(AnnotationClassFilter.class, pointcut3.getClassFilter().getClass()); + assertTrue(pointcut1.getClassFilter().toString().contains(Qualifier.class.getName())); + + assertEquals(AnnotationMethodMatcher.class, pointcut1.getMethodMatcher().getClass()); + assertEquals(AnnotationMethodMatcher.class, pointcut2.getMethodMatcher().getClass()); + assertEquals(AnnotationMethodMatcher.class, pointcut3.getMethodMatcher().getClass()); + assertTrue(pointcut1.getMethodMatcher().toString().contains(Qualifier.class.getName())); + + assertEquals(pointcut1, pointcut2); + assertNotEquals(pointcut1, pointcut3); + assertEquals(pointcut1.hashCode(), pointcut2.hashCode()); + // #1 and #3 have equivalent hash codes even though equals() returns false. + assertEquals(pointcut1.hashCode(), pointcut3.hashCode()); + assertEquals(pointcut1.toString(), pointcut2.toString()); + } + +}