From 13e62d0b8e3911514a51864c7505663f2ad17465 Mon Sep 17 00:00:00 2001 From: Jinhui-Z <34084399+Jinhui-Z@users.noreply.github.com> Date: Thu, 25 Mar 2021 14:06:34 +0800 Subject: [PATCH] Introduce reverse on ClassFilter and MethodFilter See gh-26725 --- .../aop/support/ClassFilters.java | 47 ++++++++++++++ .../aop/support/MethodMatchers.java | 63 +++++++++++++++++++ .../aop/support/ClassFiltersTests.java | 9 +++ .../aop/support/MethodMatchersTests.java | 13 ++++ 4 files changed, 132 insertions(+) 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 7b2b4cc32de0..028c07f83c2d 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 @@ -85,6 +85,17 @@ public static ClassFilter intersection(ClassFilter[] classFilters) { return new IntersectionClassFilter(classFilters); } + /** + * reverse the given ClassFilter match. + * @param cf the ClassFilter + * @return a distinct ClassFilter that not matches classes + * of the given ClassFilter match + */ + public static ClassFilter reversion(ClassFilter cf) { + Assert.notNull(cf, "ClassFilter must not be null"); + return new ReversionClassFilter(cf); + } + /** * ClassFilter implementation for a union of the given ClassFilters. @@ -167,4 +178,40 @@ public String toString() { } + + /** + * ClassFilter implementation for an reversion of the given ClassFilter. + */ + @SuppressWarnings("serial") + private static class ReversionClassFilter implements ClassFilter, Serializable { + + private final ClassFilter filter; + + ReversionClassFilter(ClassFilter filter) { + this.filter = filter; + } + + @Override + public boolean matches(Class clazz) { + return !this.filter.matches(clazz); + } + + @Override + public boolean equals(@Nullable Object other) { + return (this == other || (other instanceof ReversionClassFilter && + this.filter.equals(((ReversionClassFilter)other).filter))); + } + + @Override + public int hashCode() { + return 37 * this.filter.hashCode(); + } + + @Override + public String toString() { + return getClass().getName() + ": " + this.filter; + } + + } + } diff --git a/spring-aop/src/main/java/org/springframework/aop/support/MethodMatchers.java b/spring-aop/src/main/java/org/springframework/aop/support/MethodMatchers.java index 22e8b44d2dbc..37d764662cb6 100644 --- a/spring-aop/src/main/java/org/springframework/aop/support/MethodMatchers.java +++ b/spring-aop/src/main/java/org/springframework/aop/support/MethodMatchers.java @@ -81,6 +81,16 @@ public static MethodMatcher intersection(MethodMatcher mm1, MethodMatcher mm2) { new IntersectionIntroductionAwareMethodMatcher(mm1, mm2) : new IntersectionMethodMatcher(mm1, mm2)); } + /** + * reverse the given MethodMatcher match. + * @param mm the MethodMatcher + * @return a distinct MethodMatcher that not matches methods + * of the given MethodMatcher match + */ + public static MethodMatcher reversion(MethodMatcher mm) { + return new ReversionMethodMatcher(mm); + } + /** * Apply the given MethodMatcher to the given Method, supporting an * {@link org.springframework.aop.IntroductionAwareMethodMatcher} @@ -338,4 +348,57 @@ public boolean matches(Method method, Class targetClass, boolean hasIntroduct } } + + /** + * MethodMatcher implementation for an reversion of the given MethodMatcher. + */ + @SuppressWarnings("serial") + private static class ReversionMethodMatcher implements MethodMatcher, Serializable { + + protected final MethodMatcher mm; + + public ReversionMethodMatcher(MethodMatcher mm) { + Assert.notNull(mm, "MethodMatcher must not be null"); + this.mm = mm; + } + + @Override + public boolean matches(Method method, Class targetClass) { + return !this.mm.matches(method, targetClass); + } + + @Override + public boolean isRuntime() { + return this.mm.isRuntime(); + } + + @Override + public boolean matches(Method method, Class targetClass, Object... args) { + return !this.mm.matches(method, targetClass, args); + } + + @Override + public boolean equals(@Nullable Object other) { + if (this == other) { + return true; + } + if (!(other instanceof ReversionMethodMatcher)) { + return false; + } + ReversionMethodMatcher that = (ReversionMethodMatcher) other; + return this.mm.equals(that.mm); + } + + @Override + public int hashCode() { + return 37 * this.mm.hashCode(); + } + + @Override + public String toString() { + return getClass().getName() + ": " + this.mm; + } + + } + } 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 db83a9424df1..3f0872eb3276 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 @@ -66,4 +66,13 @@ void intersection() { .matches("^.+IntersectionClassFilter: \\[.+RootClassFilter: .+Exception, .+RootClassFilter: .+NestedRuntimeException\\]$"); } + @Test + void reversion() { + assertThat(exceptionFilter.matches(RuntimeException.class)).isTrue(); + ClassFilter reversion = ClassFilters.reversion(exceptionFilter); + assertThat(reversion.matches(RuntimeException.class)).isFalse(); + assertThat(reversion.toString()) + .matches("^.+ReversionClassFilter: .+RootClassFilter: .+Exception$"); + } + } diff --git a/spring-aop/src/test/java/org/springframework/aop/support/MethodMatchersTests.java b/spring-aop/src/test/java/org/springframework/aop/support/MethodMatchersTests.java index 55a2d7cabb57..7c0084d63e78 100644 --- a/spring-aop/src/test/java/org/springframework/aop/support/MethodMatchersTests.java +++ b/spring-aop/src/test/java/org/springframework/aop/support/MethodMatchersTests.java @@ -111,6 +111,19 @@ public void testUnionEquals() { assertThat(second.equals(first)).isTrue(); } + @Test + public void testDynamicAndStaticMethodMatcherReversion() throws Exception { + MethodMatcher getterMatcher = new StartsWithMatcher("get"); + MethodMatcher reversion = MethodMatchers.reversion(getterMatcher); + assertThat(reversion.isRuntime()).as("Reversion is a static matcher").isFalse(); + assertThat(reversion.matches(ITESTBEAN_GETAGE, TestBean.class)).as("Didn't matched getAge method").isFalse(); + assertThat(reversion.matches(IOTHER_ABSQUATULATE, TestBean.class)).as("Matched absquatulate method").isTrue(); + reversion = MethodMatchers.reversion(new TestDynamicMethodMatcherWhichDoesNotMatch()); + assertThat(reversion.isRuntime()).as("Intersection is a dynamic matcher").isTrue(); + assertThat(reversion.matches(ITESTBEAN_SETAGE, TestBean.class)).as("Didn't matched setAge method").isFalse(); + assertThat(reversion.matches(ITESTBEAN_SETAGE, TestBean.class, 5)).as("Matched setAge method").isTrue(); + } + public static class StartsWithMatcher extends StaticMethodMatcher {