From 252f52ab07d524d0a35cfa884d5bbf53a6abce61 Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Fri, 20 Jul 2018 00:17:31 +0200 Subject: [PATCH] Tighten (IntroductionAware)MethodMatcher contract Provides a non-null guarantee for MethodMatcher's targetClass argument and strict separation between IntroductionAwareMethodMatcher and regular MethodMatcher, enabling DefaultAdvisorChainFactory to defer its IntroductionAdvisor determination until encountering an actual IntroductionAwareMethodMatcher (even behind union/intersection). Issue: SPR-17068 --- .../aop/IntroductionAwareMethodMatcher.java | 9 +- .../springframework/aop/MethodMatcher.java | 23 ++-- .../aop/TrueMethodMatcher.java | 6 +- .../aop/aspectj/AbstractAspectJAdvice.java | 2 +- .../aspectj/AspectJExpressionPointcut.java | 13 +- ...ntiationModelAwarePointcutAdvisorImpl.java | 4 +- .../aop/framework/AdvisedSupport.java | 3 +- .../framework/DefaultAdvisorChainFactory.java | 4 +- .../framework/ReflectiveMethodInvocation.java | 5 +- .../support/AbstractRegexpMethodPointcut.java | 10 +- .../aop/support/ControlFlowPointcut.java | 4 +- .../aop/support/DynamicMethodMatcher.java | 3 +- .../aop/support/MethodMatchers.java | 126 +++++++++++++----- .../aop/support/NameMatchMethodPointcut.java | 3 +- .../aop/support/Pointcuts.java | 5 +- .../aop/support/StaticMethodMatcher.java | 3 +- .../annotation/AnnotationMethodMatcher.java | 5 +- .../JCacheOperationSourcePointcut.java | 2 +- .../CacheOperationSourcePointcut.java | 4 +- ...AsyncAnnotationBeanPostProcessorTests.java | 29 +++- .../TransactionAttributeSourcePointcut.java | 6 +- .../interceptor/TransactionInterceptor.java | 4 +- 22 files changed, 170 insertions(+), 103 deletions(-) diff --git a/spring-aop/src/main/java/org/springframework/aop/IntroductionAwareMethodMatcher.java b/spring-aop/src/main/java/org/springframework/aop/IntroductionAwareMethodMatcher.java index eceac03ea2..a5ea6e88ce 100644 --- a/spring-aop/src/main/java/org/springframework/aop/IntroductionAwareMethodMatcher.java +++ b/spring-aop/src/main/java/org/springframework/aop/IntroductionAwareMethodMatcher.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2013 the original author or authors. + * Copyright 2002-2018 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. @@ -18,8 +18,6 @@ package org.springframework.aop; import java.lang.reflect.Method; -import org.springframework.lang.Nullable; - /** * A specialized type of {@link MethodMatcher} that takes into account introductions * when matching methods. If there are no introductions on the target class, @@ -35,12 +33,11 @@ public interface IntroductionAwareMethodMatcher extends MethodMatcher { * instead of the 2-arg {@link #matches(java.lang.reflect.Method, Class)} method * if the caller supports the extended IntroductionAwareMethodMatcher interface. * @param method the candidate method - * @param targetClass the target class (may be {@code null}, in which case - * the candidate class must be taken to be the method's declaring class) + * @param targetClass the target class * @param hasIntroductions {@code true} if the object on whose behalf we are * asking is the subject on one or more introductions; {@code false} otherwise * @return whether or not this method matches statically */ - boolean matches(Method method, @Nullable Class targetClass, boolean hasIntroductions); + boolean matches(Method method, Class targetClass, boolean hasIntroductions); } diff --git a/spring-aop/src/main/java/org/springframework/aop/MethodMatcher.java b/spring-aop/src/main/java/org/springframework/aop/MethodMatcher.java index 7b863b0712..9a31e33fda 100644 --- a/spring-aop/src/main/java/org/springframework/aop/MethodMatcher.java +++ b/spring-aop/src/main/java/org/springframework/aop/MethodMatcher.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2015 the original author or authors. + * Copyright 2002-2018 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. @@ -18,8 +18,6 @@ package org.springframework.aop; import java.lang.reflect.Method; -import org.springframework.lang.Nullable; - /** * Part of a {@link Pointcut}: Checks whether the target method is eligible for advice. * @@ -50,16 +48,16 @@ import org.springframework.lang.Nullable; public interface MethodMatcher { /** - * Perform static checking whether the given method matches. If this - * returns {@code false} or if the {@link #isRuntime()} method - * returns {@code false}, no runtime check (i.e. no. - * {@link #matches(java.lang.reflect.Method, Class, Object[])} call) will be made. + * Perform static checking whether the given method matches. + *

If this returns {@code false} or if the {@link #isRuntime()} + * method returns {@code false}, no runtime check (i.e. no + * {@link #matches(java.lang.reflect.Method, Class, Object[])} call) + * will be made. * @param method the candidate method - * @param targetClass the target class (may be {@code null}, in which case - * the candidate class must be taken to be the method's declaring class) + * @param targetClass the target class * @return whether or not this method matches statically */ - boolean matches(Method method, @Nullable Class targetClass); + boolean matches(Method method, Class targetClass); /** * Is this MethodMatcher dynamic, that is, must a final call be made on the @@ -82,13 +80,12 @@ public interface MethodMatcher { * immediately before potential running of the advice, after any * advice earlier in the advice chain has run. * @param method the candidate method - * @param targetClass the target class (may be {@code null}, in which case - * the candidate class must be taken to be the method's declaring class) + * @param targetClass the target class * @param args arguments to the method * @return whether there's a runtime match * @see MethodMatcher#matches(Method, Class) */ - boolean matches(Method method, @Nullable Class targetClass, Object... args); + boolean matches(Method method, Class targetClass, Object... args); /** diff --git a/spring-aop/src/main/java/org/springframework/aop/TrueMethodMatcher.java b/spring-aop/src/main/java/org/springframework/aop/TrueMethodMatcher.java index 91640b6d6d..966ef79512 100644 --- a/spring-aop/src/main/java/org/springframework/aop/TrueMethodMatcher.java +++ b/spring-aop/src/main/java/org/springframework/aop/TrueMethodMatcher.java @@ -19,8 +19,6 @@ package org.springframework.aop; import java.io.Serializable; import java.lang.reflect.Method; -import org.springframework.lang.Nullable; - /** * Canonical MethodMatcher instance that matches all methods. * @@ -45,12 +43,12 @@ final class TrueMethodMatcher implements MethodMatcher, Serializable { } @Override - public boolean matches(Method method, @Nullable Class targetClass) { + public boolean matches(Method method, Class targetClass) { return true; } @Override - public boolean matches(Method method, @Nullable Class targetClass, Object... args) { + public boolean matches(Method method, Class targetClass, Object... args) { // Should never be invoked as isRuntime returns false. throw new UnsupportedOperationException(); } diff --git a/spring-aop/src/main/java/org/springframework/aop/aspectj/AbstractAspectJAdvice.java b/spring-aop/src/main/java/org/springframework/aop/aspectj/AbstractAspectJAdvice.java index 8951abcd58..57b73ed659 100644 --- a/spring-aop/src/main/java/org/springframework/aop/aspectj/AbstractAspectJAdvice.java +++ b/spring-aop/src/main/java/org/springframework/aop/aspectj/AbstractAspectJAdvice.java @@ -715,7 +715,7 @@ public abstract class AbstractAspectJAdvice implements Advice, AspectJPrecedence } @Override - public boolean matches(Method method, @Nullable Class targetClass) { + public boolean matches(Method method, Class targetClass) { return !this.adviceMethod.equals(method); } diff --git a/spring-aop/src/main/java/org/springframework/aop/aspectj/AspectJExpressionPointcut.java b/spring-aop/src/main/java/org/springframework/aop/aspectj/AspectJExpressionPointcut.java index 68c1e02dd6..3ee4f44ee0 100644 --- a/spring-aop/src/main/java/org/springframework/aop/aspectj/AspectJExpressionPointcut.java +++ b/spring-aop/src/main/java/org/springframework/aop/aspectj/AspectJExpressionPointcut.java @@ -290,7 +290,7 @@ public class AspectJExpressionPointcut extends AbstractExpressionPointcut } @Override - public boolean matches(Method method, @Nullable Class targetClass, boolean hasIntroductions) { + public boolean matches(Method method, Class targetClass, boolean hasIntroductions) { obtainPointcutExpression(); ShadowMatch shadowMatch = getTargetShadowMatch(method, targetClass); @@ -313,13 +313,12 @@ public class AspectJExpressionPointcut extends AbstractExpressionPointcut // we say this is not a match as in Spring there will never be a different // runtime subtype. RuntimeTestWalker walker = getRuntimeTestWalker(shadowMatch); - return (!walker.testsSubtypeSensitiveVars() || - (targetClass != null && walker.testTargetInstanceOfResidue(targetClass))); + return (!walker.testsSubtypeSensitiveVars() || walker.testTargetInstanceOfResidue(targetClass)); } } @Override - public boolean matches(Method method, @Nullable Class targetClass) { + public boolean matches(Method method, Class targetClass) { return matches(method, targetClass, false); } @@ -329,7 +328,7 @@ public class AspectJExpressionPointcut extends AbstractExpressionPointcut } @Override - public boolean matches(Method method, @Nullable Class targetClass, Object... args) { + public boolean matches(Method method, Class targetClass, Object... args) { obtainPointcutExpression(); ShadowMatch shadowMatch = getTargetShadowMatch(method, targetClass); @@ -426,9 +425,9 @@ public class AspectJExpressionPointcut extends AbstractExpressionPointcut invocation.setUserAttribute(resolveExpression(), jpm); } - private ShadowMatch getTargetShadowMatch(Method method, @Nullable Class targetClass) { + private ShadowMatch getTargetShadowMatch(Method method, Class targetClass) { Method targetMethod = AopUtils.getMostSpecificMethod(method, targetClass); - if (targetClass != null && targetMethod.getDeclaringClass().isInterface()) { + if (targetMethod.getDeclaringClass().isInterface()) { // Try to build the most specific interface possible for inherited methods to be // considered for sub-interface matches as well, in particular for proxy classes. // Note: AspectJ is only going to take Method.getDeclaringClass() into account. diff --git a/spring-aop/src/main/java/org/springframework/aop/aspectj/annotation/InstantiationModelAwarePointcutAdvisorImpl.java b/spring-aop/src/main/java/org/springframework/aop/aspectj/annotation/InstantiationModelAwarePointcutAdvisorImpl.java index ff5403be9d..0f2d462278 100644 --- a/spring-aop/src/main/java/org/springframework/aop/aspectj/annotation/InstantiationModelAwarePointcutAdvisorImpl.java +++ b/spring-aop/src/main/java/org/springframework/aop/aspectj/annotation/InstantiationModelAwarePointcutAdvisorImpl.java @@ -284,14 +284,14 @@ final class InstantiationModelAwarePointcutAdvisorImpl } @Override - public boolean matches(Method method, @Nullable Class targetClass) { + public boolean matches(Method method, Class targetClass) { // We're either instantiated and matching on declared pointcut, or uninstantiated matching on either pointcut return (isAspectMaterialized() && this.declaredPointcut.matches(method, targetClass)) || this.preInstantiationPointcut.getMethodMatcher().matches(method, targetClass); } @Override - public boolean matches(Method method, @Nullable Class targetClass, Object... args) { + public boolean matches(Method method, Class targetClass, Object... args) { // This can match only on declared pointcut. return (isAspectMaterialized() && this.declaredPointcut.matches(method, targetClass)); } diff --git a/spring-aop/src/main/java/org/springframework/aop/framework/AdvisedSupport.java b/spring-aop/src/main/java/org/springframework/aop/framework/AdvisedSupport.java index d2fa549c17..54133402e6 100644 --- a/spring-aop/src/main/java/org/springframework/aop/framework/AdvisedSupport.java +++ b/spring-aop/src/main/java/org/springframework/aop/framework/AdvisedSupport.java @@ -152,11 +152,12 @@ public class AdvisedSupport extends ProxyConfig implements Advised { * @see #setTargetSource * @see #setTarget */ - public void setTargetClass(Class targetClass) { + public void setTargetClass(@Nullable Class targetClass) { this.targetSource = EmptyTargetSource.forClass(targetClass); } @Override + @Nullable public Class getTargetClass() { return this.targetSource.getTargetClass(); } diff --git a/spring-aop/src/main/java/org/springframework/aop/framework/DefaultAdvisorChainFactory.java b/spring-aop/src/main/java/org/springframework/aop/framework/DefaultAdvisorChainFactory.java index 586051bc78..31438c78f8 100644 --- a/spring-aop/src/main/java/org/springframework/aop/framework/DefaultAdvisorChainFactory.java +++ b/spring-aop/src/main/java/org/springframework/aop/framework/DefaultAdvisorChainFactory.java @@ -70,10 +70,10 @@ public class DefaultAdvisorChainFactory implements AdvisorChainFactory, Serializ if (hasIntroductions == null) { hasIntroductions = hasMatchingIntroductions(advisors, actualClass); } - match = ((IntroductionAwareMethodMatcher) mm).matches(method, targetClass, hasIntroductions); + match = ((IntroductionAwareMethodMatcher) mm).matches(method, actualClass, hasIntroductions); } else { - match = mm.matches(method, targetClass); + match = mm.matches(method, actualClass); } if (match) { MethodInterceptor[] interceptors = registry.getInterceptors(advisor); diff --git a/spring-aop/src/main/java/org/springframework/aop/framework/ReflectiveMethodInvocation.java b/spring-aop/src/main/java/org/springframework/aop/framework/ReflectiveMethodInvocation.java index c8a2ece273..3a2134fb4f 100644 --- a/spring-aop/src/main/java/org/springframework/aop/framework/ReflectiveMethodInvocation.java +++ b/spring-aop/src/main/java/org/springframework/aop/framework/ReflectiveMethodInvocation.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2017 the original author or authors. + * Copyright 2002-2018 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. @@ -170,7 +170,8 @@ public class ReflectiveMethodInvocation implements ProxyMethodInvocation, Clonea // been evaluated and found to match. InterceptorAndDynamicMethodMatcher dm = (InterceptorAndDynamicMethodMatcher) interceptorOrInterceptionAdvice; - if (dm.methodMatcher.matches(this.method, this.targetClass, this.arguments)) { + Class targetClass = (this.targetClass != null ? this.targetClass : this.method.getDeclaringClass()); + if (dm.methodMatcher.matches(this.method, targetClass, this.arguments)) { return dm.interceptor.invoke(this); } else { diff --git a/spring-aop/src/main/java/org/springframework/aop/support/AbstractRegexpMethodPointcut.java b/spring-aop/src/main/java/org/springframework/aop/support/AbstractRegexpMethodPointcut.java index f6c7152796..d96f65f6e0 100644 --- a/spring-aop/src/main/java/org/springframework/aop/support/AbstractRegexpMethodPointcut.java +++ b/spring-aop/src/main/java/org/springframework/aop/support/AbstractRegexpMethodPointcut.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2016 the original author or authors. + * Copyright 2002-2018 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. @@ -20,7 +20,6 @@ import java.io.Serializable; import java.lang.reflect.Method; import java.util.Arrays; -import org.springframework.lang.Nullable; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; import org.springframework.util.ObjectUtils; @@ -130,9 +129,10 @@ public abstract class AbstractRegexpMethodPointcut extends StaticMethodMatcherPo * plus the name of the method. */ @Override - public boolean matches(Method method, @Nullable Class targetClass) { - return ((targetClass != null && matchesPattern(ClassUtils.getQualifiedMethodName(method, targetClass))) || - matchesPattern(ClassUtils.getQualifiedMethodName(method))); + public boolean matches(Method method, Class targetClass) { + return (matchesPattern(ClassUtils.getQualifiedMethodName(method, targetClass)) || + (targetClass != method.getDeclaringClass() && + matchesPattern(ClassUtils.getQualifiedMethodName(method, method.getDeclaringClass())))); } /** 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 67fed08bd9..832eaafcfe 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 @@ -81,7 +81,7 @@ public class ControlFlowPointcut implements Pointcut, ClassFilter, MethodMatcher * some candidate classes. */ @Override - public boolean matches(Method method, @Nullable Class targetClass) { + public boolean matches(Method method, Class targetClass) { return true; } @@ -91,7 +91,7 @@ public class ControlFlowPointcut implements Pointcut, ClassFilter, MethodMatcher } @Override - public boolean matches(Method method, @Nullable Class targetClass, Object... args) { + public boolean matches(Method method, Class targetClass, Object... args) { this.evaluations++; for (StackTraceElement element : new Throwable().getStackTrace()) { diff --git a/spring-aop/src/main/java/org/springframework/aop/support/DynamicMethodMatcher.java b/spring-aop/src/main/java/org/springframework/aop/support/DynamicMethodMatcher.java index 8ae7e82110..1f7dc97484 100644 --- a/spring-aop/src/main/java/org/springframework/aop/support/DynamicMethodMatcher.java +++ b/spring-aop/src/main/java/org/springframework/aop/support/DynamicMethodMatcher.java @@ -19,7 +19,6 @@ package org.springframework.aop.support; import java.lang.reflect.Method; import org.springframework.aop.MethodMatcher; -import org.springframework.lang.Nullable; /** * Convenient abstract superclass for dynamic method matchers, @@ -39,7 +38,7 @@ public abstract class DynamicMethodMatcher implements MethodMatcher { * always returns true. */ @Override - public boolean matches(Method method, @Nullable Class targetClass) { + public boolean matches(Method method, Class targetClass) { return true; } 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 72bf5e4c99..2d3d0b678d 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 @@ -22,7 +22,6 @@ import java.lang.reflect.Method; import org.springframework.aop.ClassFilter; import org.springframework.aop.IntroductionAwareMethodMatcher; import org.springframework.aop.MethodMatcher; -import org.springframework.lang.Nullable; import org.springframework.util.Assert; /** @@ -49,7 +48,8 @@ public abstract class MethodMatchers { * of the given MethodMatchers matches */ public static MethodMatcher union(MethodMatcher mm1, MethodMatcher mm2) { - return new UnionMethodMatcher(mm1, mm2); + return (mm1 instanceof IntroductionAwareMethodMatcher || mm2 instanceof IntroductionAwareMethodMatcher ? + new UnionIntroductionAwareMethodMatcher(mm1, mm2) : new UnionMethodMatcher(mm1, mm2)); } /** @@ -62,7 +62,9 @@ public abstract class MethodMatchers { * of the given MethodMatchers matches */ static MethodMatcher union(MethodMatcher mm1, ClassFilter cf1, MethodMatcher mm2, ClassFilter cf2) { - return new ClassFilterAwareUnionMethodMatcher(mm1, cf1, mm2, cf2); + return (mm1 instanceof IntroductionAwareMethodMatcher || mm2 instanceof IntroductionAwareMethodMatcher ? + new ClassFilterAwareUnionIntroductionAwareMethodMatcher(mm1, cf1, mm2, cf2) : + new ClassFilterAwareUnionMethodMatcher(mm1, cf1, mm2, cf2)); } /** @@ -73,7 +75,8 @@ public abstract class MethodMatchers { * of the given MethodMatchers match */ public static MethodMatcher intersection(MethodMatcher mm1, MethodMatcher mm2) { - return new IntersectionMethodMatcher(mm1, mm2); + return (mm1 instanceof IntroductionAwareMethodMatcher || mm2 instanceof IntroductionAwareMethodMatcher ? + new IntersectionIntroductionAwareMethodMatcher(mm1, mm2) : new IntersectionMethodMatcher(mm1, mm2)); } /** @@ -82,13 +85,12 @@ public abstract class MethodMatchers { * (if applicable). * @param mm the MethodMatcher to apply (may be an IntroductionAwareMethodMatcher) * @param method the candidate method - * @param targetClass the target class (may be {@code null}, in which case - * the candidate class must be taken to be the method's declaring class) + * @param targetClass the target class * @param hasIntroductions {@code true} if the object on whose behalf we are * asking is the subject on one or more introductions; {@code false} otherwise * @return whether or not this method matches statically */ - public static boolean matches(MethodMatcher mm, Method method, @Nullable Class targetClass, boolean hasIntroductions) { + public static boolean matches(MethodMatcher mm, Method method, Class targetClass, boolean hasIntroductions) { Assert.notNull(mm, "MethodMatcher must not be null"); return (mm instanceof IntroductionAwareMethodMatcher ? ((IntroductionAwareMethodMatcher) mm).matches(method, targetClass, hasIntroductions) : @@ -100,11 +102,11 @@ public abstract class MethodMatchers { * MethodMatcher implementation for a union of two given MethodMatchers. */ @SuppressWarnings("serial") - private static class UnionMethodMatcher implements IntroductionAwareMethodMatcher, Serializable { + private static class UnionMethodMatcher implements MethodMatcher, Serializable { - private final MethodMatcher mm1; + protected final MethodMatcher mm1; - private final MethodMatcher mm2; + protected final MethodMatcher mm2; public UnionMethodMatcher(MethodMatcher mm1, MethodMatcher mm2) { Assert.notNull(mm1, "First MethodMatcher must not be null"); @@ -114,22 +116,16 @@ public abstract class MethodMatchers { } @Override - public boolean matches(Method method, @Nullable Class targetClass, boolean hasIntroductions) { - return (matchesClass1(targetClass) && MethodMatchers.matches(this.mm1, method, targetClass, hasIntroductions)) || - (matchesClass2(targetClass) && MethodMatchers.matches(this.mm2, method, targetClass, hasIntroductions)); - } - - @Override - public boolean matches(Method method, @Nullable Class targetClass) { + public boolean matches(Method method, Class targetClass) { return (matchesClass1(targetClass) && this.mm1.matches(method, targetClass)) || (matchesClass2(targetClass) && this.mm2.matches(method, targetClass)); } - protected boolean matchesClass1(@Nullable Class targetClass) { + protected boolean matchesClass1(Class targetClass) { return true; } - protected boolean matchesClass2(@Nullable Class targetClass) { + protected boolean matchesClass2(Class targetClass) { return true; } @@ -139,7 +135,7 @@ public abstract class MethodMatchers { } @Override - public boolean matches(Method method, @Nullable Class targetClass, Object... args) { + public boolean matches(Method method, Class targetClass, Object... args) { return this.mm1.matches(method, targetClass, args) || this.mm2.matches(method, targetClass, args); } @@ -162,6 +158,27 @@ public abstract class MethodMatchers { } + /** + * MethodMatcher implementation for a union of two given MethodMatchers + * of which at least one is an IntroductionAwareMethodMatcher. + * @since 5.1 + */ + @SuppressWarnings("serial") + private static class UnionIntroductionAwareMethodMatcher extends UnionMethodMatcher + implements IntroductionAwareMethodMatcher { + + public UnionIntroductionAwareMethodMatcher(MethodMatcher mm1, MethodMatcher mm2) { + super(mm1, mm2); + } + + @Override + public boolean matches(Method method, Class targetClass, boolean hasIntroductions) { + return (matchesClass1(targetClass) && MethodMatchers.matches(this.mm1, method, targetClass, hasIntroductions)) || + (matchesClass2(targetClass) && MethodMatchers.matches(this.mm2, method, targetClass, hasIntroductions)); + } + } + + /** * MethodMatcher implementation for a union of two given MethodMatchers, * supporting an associated ClassFilter per MethodMatcher. @@ -180,13 +197,13 @@ public abstract class MethodMatchers { } @Override - protected boolean matchesClass1(@Nullable Class targetClass) { - return (targetClass != null && this.cf1.matches(targetClass)); + protected boolean matchesClass1(Class targetClass) { + return this.cf1.matches(targetClass); } @Override - protected boolean matchesClass2(@Nullable Class targetClass) { - return (targetClass != null && this.cf2.matches(targetClass)); + protected boolean matchesClass2(Class targetClass) { + return this.cf2.matches(targetClass); } @Override @@ -215,15 +232,39 @@ public abstract class MethodMatchers { } + /** + * MethodMatcher implementation for a union of two given MethodMatchers + * of which at least one is an IntroductionAwareMethodMatcher, + * supporting an associated ClassFilter per MethodMatcher. + * @since 5.1 + */ + @SuppressWarnings("serial") + private static class ClassFilterAwareUnionIntroductionAwareMethodMatcher extends ClassFilterAwareUnionMethodMatcher + implements IntroductionAwareMethodMatcher { + + public ClassFilterAwareUnionIntroductionAwareMethodMatcher( + MethodMatcher mm1, ClassFilter cf1, MethodMatcher mm2, ClassFilter cf2) { + + super(mm1, cf1, mm2, cf2); + } + + @Override + public boolean matches(Method method, Class targetClass, boolean hasIntroductions) { + return (matchesClass1(targetClass) && MethodMatchers.matches(this.mm1, method, targetClass, hasIntroductions)) || + (matchesClass2(targetClass) && MethodMatchers.matches(this.mm2, method, targetClass, hasIntroductions)); + } + } + + /** * MethodMatcher implementation for an intersection of two given MethodMatchers. */ @SuppressWarnings("serial") - private static class IntersectionMethodMatcher implements IntroductionAwareMethodMatcher, Serializable { + private static class IntersectionMethodMatcher implements MethodMatcher, Serializable { - private final MethodMatcher mm1; + protected final MethodMatcher mm1; - private final MethodMatcher mm2; + protected final MethodMatcher mm2; public IntersectionMethodMatcher(MethodMatcher mm1, MethodMatcher mm2) { Assert.notNull(mm1, "First MethodMatcher must not be null"); @@ -233,13 +274,7 @@ public abstract class MethodMatchers { } @Override - public boolean matches(Method method, @Nullable Class targetClass, boolean hasIntroductions) { - return (MethodMatchers.matches(this.mm1, method, targetClass, hasIntroductions) && - MethodMatchers.matches(this.mm2, method, targetClass, hasIntroductions)); - } - - @Override - public boolean matches(Method method, @Nullable Class targetClass) { + public boolean matches(Method method, Class targetClass) { return (this.mm1.matches(method, targetClass) && this.mm2.matches(method, targetClass)); } @@ -249,7 +284,7 @@ public abstract class MethodMatchers { } @Override - public boolean matches(Method method, @Nullable Class targetClass, Object... args) { + public boolean matches(Method method, Class targetClass, Object... args) { // Because a dynamic intersection may be composed of a static and dynamic part, // we must avoid calling the 3-arg matches method on a dynamic matcher, as // it will probably be an unsupported operation. @@ -278,4 +313,25 @@ public abstract class MethodMatchers { } } + + /** + * MethodMatcher implementation for an intersection of two given MethodMatchers + * of which at least one is an IntroductionAwareMethodMatcher. + * @since 5.1 + */ + @SuppressWarnings("serial") + private static class IntersectionIntroductionAwareMethodMatcher extends IntersectionMethodMatcher + implements IntroductionAwareMethodMatcher { + + public IntersectionIntroductionAwareMethodMatcher(MethodMatcher mm1, MethodMatcher mm2) { + super(mm1, mm2); + } + + @Override + public boolean matches(Method method, Class targetClass, boolean hasIntroductions) { + return (MethodMatchers.matches(this.mm1, method, targetClass, hasIntroductions) && + MethodMatchers.matches(this.mm2, method, targetClass, hasIntroductions)); + } + } + } diff --git a/spring-aop/src/main/java/org/springframework/aop/support/NameMatchMethodPointcut.java b/spring-aop/src/main/java/org/springframework/aop/support/NameMatchMethodPointcut.java index 4c450954fa..19009b4579 100644 --- a/spring-aop/src/main/java/org/springframework/aop/support/NameMatchMethodPointcut.java +++ b/spring-aop/src/main/java/org/springframework/aop/support/NameMatchMethodPointcut.java @@ -22,7 +22,6 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; -import org.springframework.lang.Nullable; import org.springframework.util.PatternMatchUtils; /** @@ -75,7 +74,7 @@ public class NameMatchMethodPointcut extends StaticMethodMatcherPointcut impleme @Override - public boolean matches(Method method, @Nullable Class targetClass) { + public boolean matches(Method method, Class targetClass) { for (String mappedName : this.mappedNames) { if (mappedName.equals(method.getName()) || isMatch(method.getName(), mappedName)) { return true; diff --git a/spring-aop/src/main/java/org/springframework/aop/support/Pointcuts.java b/spring-aop/src/main/java/org/springframework/aop/support/Pointcuts.java index 8af45e2e14..d362b6fe88 100644 --- a/spring-aop/src/main/java/org/springframework/aop/support/Pointcuts.java +++ b/spring-aop/src/main/java/org/springframework/aop/support/Pointcuts.java @@ -21,7 +21,6 @@ import java.lang.reflect.Method; import org.springframework.aop.MethodMatcher; import org.springframework.aop.Pointcut; -import org.springframework.lang.Nullable; import org.springframework.util.Assert; /** @@ -98,7 +97,7 @@ public abstract class Pointcuts { public static final SetterPointcut INSTANCE = new SetterPointcut(); @Override - public boolean matches(Method method, @Nullable Class targetClass) { + public boolean matches(Method method, Class targetClass) { return (method.getName().startsWith("set") && method.getParameterCount() == 1 && method.getReturnType() == Void.TYPE); @@ -119,7 +118,7 @@ public abstract class Pointcuts { public static final GetterPointcut INSTANCE = new GetterPointcut(); @Override - public boolean matches(Method method, @Nullable Class targetClass) { + public boolean matches(Method method, Class targetClass) { return (method.getName().startsWith("get") && method.getParameterCount() == 0); } diff --git a/spring-aop/src/main/java/org/springframework/aop/support/StaticMethodMatcher.java b/spring-aop/src/main/java/org/springframework/aop/support/StaticMethodMatcher.java index 150ac87c98..923daaf94d 100644 --- a/spring-aop/src/main/java/org/springframework/aop/support/StaticMethodMatcher.java +++ b/spring-aop/src/main/java/org/springframework/aop/support/StaticMethodMatcher.java @@ -19,7 +19,6 @@ package org.springframework.aop.support; import java.lang.reflect.Method; import org.springframework.aop.MethodMatcher; -import org.springframework.lang.Nullable; /** * Convenient abstract superclass for static method matchers, which don't care @@ -35,7 +34,7 @@ public abstract class StaticMethodMatcher implements MethodMatcher { } @Override - public final boolean matches(Method method, @Nullable Class targetClass, Object... args) { + public final boolean matches(Method method, Class targetClass, Object... args) { // should never be invoked because isRuntime() returns false throw new UnsupportedOperationException("Illegal MethodMatcher usage"); } 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 63caee265e..9f529447a3 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 @@ -23,7 +23,6 @@ import java.lang.reflect.Proxy; import org.springframework.aop.support.AopUtils; import org.springframework.aop.support.StaticMethodMatcher; import org.springframework.core.annotation.AnnotationUtils; -import org.springframework.lang.Nullable; import org.springframework.util.Assert; /** @@ -68,12 +67,12 @@ public class AnnotationMethodMatcher extends StaticMethodMatcher { @Override - public boolean matches(Method method, @Nullable Class targetClass) { + public boolean matches(Method method, Class targetClass) { if (matchesMethod(method)) { return true; } // Proxy classes never have annotations on their redeclared methods. - if (targetClass != null && Proxy.isProxyClass(targetClass)) { + if (Proxy.isProxyClass(targetClass)) { return false; } // The method may be on an interface, so let's check on the target class as well. diff --git a/spring-context-support/src/main/java/org/springframework/cache/jcache/interceptor/JCacheOperationSourcePointcut.java b/spring-context-support/src/main/java/org/springframework/cache/jcache/interceptor/JCacheOperationSourcePointcut.java index f638c1f987..3e4e127880 100644 --- a/spring-context-support/src/main/java/org/springframework/cache/jcache/interceptor/JCacheOperationSourcePointcut.java +++ b/spring-context-support/src/main/java/org/springframework/cache/jcache/interceptor/JCacheOperationSourcePointcut.java @@ -34,7 +34,7 @@ import org.springframework.util.ObjectUtils; public abstract class JCacheOperationSourcePointcut extends StaticMethodMatcherPointcut implements Serializable { @Override - public boolean matches(Method method, @Nullable Class targetClass) { + public boolean matches(Method method, Class targetClass) { JCacheOperationSource cas = getCacheOperationSource(); return (cas != null && cas.getCacheOperation(method, targetClass) != null); } diff --git a/spring-context/src/main/java/org/springframework/cache/interceptor/CacheOperationSourcePointcut.java b/spring-context/src/main/java/org/springframework/cache/interceptor/CacheOperationSourcePointcut.java index 87b8aaecc0..9cb731efad 100644 --- a/spring-context/src/main/java/org/springframework/cache/interceptor/CacheOperationSourcePointcut.java +++ b/spring-context/src/main/java/org/springframework/cache/interceptor/CacheOperationSourcePointcut.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2018 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. @@ -35,7 +35,7 @@ import org.springframework.util.ObjectUtils; abstract class CacheOperationSourcePointcut extends StaticMethodMatcherPointcut implements Serializable { @Override - public boolean matches(Method method, @Nullable Class targetClass) { + public boolean matches(Method method, Class targetClass) { CacheOperationSource cas = getCacheOperationSource(); return (cas != null && !CollectionUtils.isEmpty(cas.getCacheOperations(method, targetClass))); } diff --git a/spring-context/src/test/java/org/springframework/scheduling/annotation/AsyncAnnotationBeanPostProcessorTests.java b/spring-context/src/test/java/org/springframework/scheduling/annotation/AsyncAnnotationBeanPostProcessorTests.java index e9ff9524e4..af5393cdcd 100644 --- a/spring-context/src/test/java/org/springframework/scheduling/annotation/AsyncAnnotationBeanPostProcessorTests.java +++ b/spring-context/src/test/java/org/springframework/scheduling/annotation/AsyncAnnotationBeanPostProcessorTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2015 the original author or authors. + * Copyright 2002-2018 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,8 +23,10 @@ import java.util.concurrent.Executor; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; +import org.aopalliance.intercept.MethodInterceptor; import org.junit.Test; +import org.springframework.aop.framework.ProxyFactory; import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler; import org.springframework.aop.support.AopUtils; import org.springframework.beans.factory.config.BeanDefinition; @@ -62,6 +64,26 @@ public class AsyncAnnotationBeanPostProcessorTests { public void invokedAsynchronously() { ConfigurableApplicationContext context = initContext( new RootBeanDefinition(AsyncAnnotationBeanPostProcessor.class)); + + ITestBean testBean = context.getBean("target", ITestBean.class); + testBean.test(); + Thread mainThread = Thread.currentThread(); + testBean.await(3000); + Thread asyncThread = testBean.getThread(); + assertNotSame(mainThread, asyncThread); + context.close(); + } + + @Test + public void invokedAsynchronouslyOnProxyTarget() { + StaticApplicationContext context = new StaticApplicationContext(); + context.registerBeanDefinition("postProcessor", new RootBeanDefinition(AsyncAnnotationBeanPostProcessor.class)); + TestBean tb = new TestBean(); + ProxyFactory pf = new ProxyFactory(ITestBean.class, + (MethodInterceptor) invocation -> invocation.getMethod().invoke(tb, invocation.getArguments())); + context.registerBean("target", ITestBean.class, () -> (ITestBean) pf.getProxy()); + context.refresh(); + ITestBean testBean = context.getBean("target", ITestBean.class); testBean.test(); Thread mainThread = Thread.currentThread(); @@ -79,6 +101,7 @@ public class AsyncAnnotationBeanPostProcessorTests { executor.afterPropertiesSet(); processorDefinition.getPropertyValues().add("executor", executor); ConfigurableApplicationContext context = initContext(processorDefinition); + ITestBean testBean = context.getBean("target", ITestBean.class); testBean.test(); testBean.await(3000); @@ -246,8 +269,7 @@ public class AsyncAnnotationBeanPostProcessorTests { private ConfigurableApplicationContext initContext(BeanDefinition asyncAnnotationBeanPostProcessorDefinition) { StaticApplicationContext context = new StaticApplicationContext(); - BeanDefinition targetDefinition = - new RootBeanDefinition(AsyncAnnotationBeanPostProcessorTests.TestBean.class); + BeanDefinition targetDefinition = new RootBeanDefinition(TestBean.class); context.registerBeanDefinition("postProcessor", asyncAnnotationBeanPostProcessorDefinition); context.registerBeanDefinition("target", targetDefinition); context.refresh(); @@ -259,6 +281,7 @@ public class AsyncAnnotationBeanPostProcessorTests { Thread getThread(); + @Async void test(); Future failWithFuture(); diff --git a/spring-tx/src/main/java/org/springframework/transaction/interceptor/TransactionAttributeSourcePointcut.java b/spring-tx/src/main/java/org/springframework/transaction/interceptor/TransactionAttributeSourcePointcut.java index 6392cd04fc..d2a89da22e 100644 --- a/spring-tx/src/main/java/org/springframework/transaction/interceptor/TransactionAttributeSourcePointcut.java +++ b/spring-tx/src/main/java/org/springframework/transaction/interceptor/TransactionAttributeSourcePointcut.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2017 the original author or authors. + * Copyright 2002-2018 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. @@ -34,8 +34,8 @@ import org.springframework.util.ObjectUtils; abstract class TransactionAttributeSourcePointcut extends StaticMethodMatcherPointcut implements Serializable { @Override - public boolean matches(Method method, @Nullable Class targetClass) { - if (targetClass != null && TransactionalProxy.class.isAssignableFrom(targetClass)) { + public boolean matches(Method method, Class targetClass) { + if (TransactionalProxy.class.isAssignableFrom(targetClass)) { return false; } TransactionAttributeSource tas = getTransactionAttributeSource(); diff --git a/spring-tx/src/main/java/org/springframework/transaction/interceptor/TransactionInterceptor.java b/spring-tx/src/main/java/org/springframework/transaction/interceptor/TransactionInterceptor.java index 9b74694d30..b74a80e039 100644 --- a/spring-tx/src/main/java/org/springframework/transaction/interceptor/TransactionInterceptor.java +++ b/spring-tx/src/main/java/org/springframework/transaction/interceptor/TransactionInterceptor.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2017 the original author or authors. + * Copyright 2002-2018 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. @@ -88,7 +88,7 @@ public class TransactionInterceptor extends TransactionAspectSupport implements @Override @Nullable - public Object invoke(final MethodInvocation invocation) throws Throwable { + public Object invoke(MethodInvocation invocation) throws Throwable { // Work out the target class: may be {@code null}. // The TransactionAttributeSource should be passed the target class // as well as the method, which may be from an interface.