In Spring AOP, our most commonly used methods of defining pointcuts are mainly two:
- Use execution for non-intrusive interception.
- Use annotations to intercept.
These should be the two most commonly used methods of defining cut points in daily work. But are there any others besides these two? Today Brother Song is here to talk to you about this topic.
Contents
1. Pointcut classification
Let’s look at the definition of Pointcut:
public interface Pointcut { ClassFilter getClassFilter () ; MethodMatcher getMethodMatcher () ; Pointcut TRUE = TruePointcut.INSTANCE; }
It can be seen from the method names that getClassFilter performs class filtering and getMethodMatcher performs method filtering. By filtering Class and filtering Method, we can lock an interception object.
Let’s take a look at the inheritance diagram of the Pointcut class:
As you can see, there are not many implementation classes. Most of them can be guessed by their names. We can roughly divide these implementation classes into six categories:
- Static method pointcut: StaticMethodMatcherPointcut represents the abstract base class of static method pointcut, which matches all classes by default, and then matches different methods through different rules.
- Dynamic method pointcut: DynamicMethodMatcherPointcut represents the abstract base class of dynamic method pointcut. By default, it matches all classes, and then matches different methods through different rules. This is somewhat similar to StaticMethodMatcherPointcut. The difference is that StaticMethodMatcherPointcut only signatures methods. Matching is performed and only matched once, while DynamicMethodMatcherPointcut will check the value of the method’s input parameters during runtime. Since the parameters passed in may be different each time, it must be judged before calling, which leads to poor performance of DynamicMethodMatcherPointcut.
- Annotation pointcut: AnnotationMatchingPointcut.
- Expression pointcut: ExpressionPointcut.
- Process cut point: ControlFlowPointcut.
- Composite pointcut: ComposablePointcut.
In addition to the above six, there is also a single TruePointcut, which can be seen from the name to intercept everything.
So there are seven types of cut points. Let’s analyze them one by one.
2.TruePointcut
As the name suggests, this implementation class intercepts everything. Let’s take a look at how this class does it:
final class TruePointcut implements Pointcut , Serializable { //... @Override public ClassFilter getClassFilter () { return ClassFilter.TRUE; } @Override public MethodMatcher getMethodMatcher () { return MethodMatcher.TRUE; } //... }
First of all, friends, please note that this class is not public, which means that we cannot directly use this pointcut in our own development. Then you can see that in the getClassFilter and getMethodMatcher methods, the corresponding TRUE is returned here, and the implementation of these two TRUE is very simple, that is, where comparison needs to be made, without any comparison, just return true directly. This results in everything being intercepted eventually.
3. StaticMethodMatcherPointcut
StaticMethodMatcherPointcut only matches the method name signature (including method name and input parameter type and order), and static matching is only judged once.
public abstract class StaticMethodMatcherPointcut extends StaticMethodMatcher implements Pointcut { private ClassFilter classFilter = ClassFilter.TRUE; /** * Set the { @link ClassFilter} to use for this pointcut. * Default is { @link ClassFilter#TRUE}. */ public void setClassFilter (ClassFilter classFilter) { this .classFilter = classFilter; } @Override public ClassFilter getClassFilter () { return this .classFilter; } @Override public final MethodMatcher getMethodMatcher () { return this ; } }
As you can see, the matching of the class here returns true by default, and the matching of the method returns the current object, which means it depends on the specific implementation.
StaticMethodMatcherPointcut has several written implementation classes, let’s take a look.
3.1 SetterPointcut
As you can see from the name, this can be used to intercept all set methods:
private static class SetterPointcut extends StaticMethodMatcherPointcut implements Serializable { public static final SetterPointcut INSTANCE = new SetterPointcut (); @Override public boolean matches (Method method, Class<?> targetClass) { return (method.getName().startsWith( "set" ) && method.getParameterCount() == 1 && method.getReturnType() == Void.TYPE); } private Object readResolve () { return INSTANCE; } @Override public String toString () { return "Pointcuts.SETTERS" ; } }
As you can see, method matching is to determine whether the current method is a set method. The method name is required to start with set. The method has only one parameter and the method return value is null, which accurately locates a set method.
An example of use is as follows:
ProxyFactory proxyFactory = new ProxyFactory (); proxyFactory.setTarget( new CalculatorImpl ()); proxyFactory.addInterface(ICalculator.class); proxyFactory.addAdvisor( new PointcutAdvisor () { @Override public Pointcut getPointcut () { return Pointcuts.SETTERS; } @Override public Advice getAdvice () { return new MethodInterceptor () { @Override public Object invoke (MethodInvocation invocation) throws Throwable { Method method = invocation.getMethod(); String name = method.getName(); System.out.println(name + "The method has started to execute..." ); Object proceed = invocation.proceed(); System.out.println(name + "Method execution ended..." ); return proceed; } }; } @Override public boolean isPerInstance () { return true ; } }); ICalculator calculator = (ICalculator) proxyFactory.getProxy(); calculator.setA( 5 );
Since SetterPointcut is private and cannot be new directly, the instance can be obtained through the tool class Pointcuts.
3.2 GetterPointcut
GetterPointcut is similar to SetterPointcut, as follows:
private static class GetterPointcut extends StaticMethodMatcherPointcut implements Serializable { public static final GetterPointcut INSTANCE = new GetterPointcut (); @Override public boolean matches (Method method, Class<?> targetClass) { return (method.getName().startsWith( "get" ) && method.getParameterCount() == 0 ); } private Object readResolve () { return INSTANCE; } @Override public String toString () { return "Pointcuts.GETTERS" ; } }
I think this shouldn’t require too much explanation. It’s similar to the previous SetterPointcut. Just compare and understand it.
3.3 NameMatchMethodPointcut
This is done based on the method name.
public class NameMatchMethodPointcut extends StaticMethodMatcherPointcut implements Serializable { private List<String> mappedNames = new ArrayList <>(); public void setMappedName (String mappedName) { setMappedNames(mappedName); } public void setMappedNames (String... mappedNames) { this .mappedNames = new ArrayList <>(Arrays.asList(mappedNames)); } public NameMatchMethodPointcut addMethodName (String name) { this .mappedNames.add(name); return this ; } @Override public boolean matches (Method method, Class<?> targetClass) { for (String mappedName : this .mappedNames) { if (mappedName.equals(method.getName()) || isMatch(method.getName(), mappedName) ) { return true ; } } return false ; } protected boolean isMatch (String methodName, String mappedName) { return PatternMatchUtils.simpleMatch(mappedName, methodName); } }
As you can see, this is to pass in a list of method names from the outside, and then match in the matches method. When matching, the equals method is directly called to match. If the equals method does not match, the isMatch method is called to match. This Finally, the PatternMatchUtils.simpleMatch method is called, which is a tool class provided in Spring that supports wildcard matching.
Give a simple example:
ProxyFactory proxyFactory = new ProxyFactory (); proxyFactory.setTarget( new CalculatorImpl ()); proxyFactory.addInterface(ICalculator.class); proxyFactory.addAdvisor( new PointcutAdvisor () { @Override public Pointcut getPointcut () { NameMatchMethodPointcut pointcut = new NameMatchMethodPointcut (); pointcut.setMappedNames( "add" , "set*" ); return pointcut; } @Override public Advice getAdvice () { return new MethodInterceptor () { @Override public Object invoke (MethodInvocation invocation) throws Throwable { Method method = invocation.getMethod(); String name = method.getName(); System.out.println(name + "The method has started to execute..." ); Object proceed = invocation.proceed(); System.out.println(name + "Method execution ended..." ); return proceed; } }; } @Override public boolean isPerInstance () { return true ; } }); ICalculator calculator = (ICalculator) proxyFactory.getProxy(); calculator.add( 3 , 4 ); calculator.minus( 3 , 4 ); calculator.setA( 5 );
What I set here is to intercept the method whose method name is add or the method name starts with set.
3.4 JdkRegexpMethodPointcut
This supports matching method names through regular expressions, and matching methods will be intercepted.
public class JdkRegexpMethodPointcut extends AbstractRegexpMethodPointcut { private Pattern[] compiledPatterns = new Pattern [ 0 ]; private Pattern[] compiledExclusionPatterns = new Pattern [ 0 ]; @Override protected void initPatternRepresentation (String[] patterns) throws PatternSyntaxException { this .compiledPatterns = compilePatterns( patterns); } @Override protected void initExcludedPatternRepresentation (String[] excludedPatterns) throws PatternSyntaxException { this .compiledExclusionPatterns = compilePatterns(excludedPatterns); } @Override protected boolean matches (String pattern, int patternIndex) { Matcher matcher = this .compiledPatterns[patternIndex].matcher(pattern); return matcher.matches(); } @Override protected boolean matchesExclusion (String candidate, int patternIndex) { Matcher matcher = this .compiledExclusionPatterns[patternIndex].matcher(candidate); return matcher.matches(); } private Pattern[] compilePatterns(String[] source) throws PatternSyntaxException { Pattern[] destination = new Pattern [source.length]; for ( int i = 0 ; i < source.length; i++) { destination[i] = Pattern.compile(source[i]); } return destination; } }
As you can see, here is actually passing in a regular expression, and then using the regular expression to match whether the method name meets the conditions. Multiple regular expressions can be passed in, and the system will traverse the parent class of JdkRegexpMethodPointcut to match them one by one. Let me give you an example:
ProxyFactory proxyFactory = new ProxyFactory (); proxyFactory.setTarget( new CalculatorImpl ()); proxyFactory.addInterface(ICalculator.class); proxyFactory.addAdvisor( new PointcutAdvisor () { @Override public Pointcut getPointcut () { JdkRegexpMethodPointcut pc = new JdkRegexpMethodPointcut (); pc.setPatterns( "org.javaboy.bean.aop3.ICalculator.set.*" ); pc.setExcludedPattern( "org.javaboy.bean.aop3.ICalculator.setA" ); return pc; } @Override public Advice getAdvice () { return new MethodInterceptor () { @Override public Object invoke (MethodInvocation invocation) throws Throwable { Method method = invocation.getMethod(); String name = method.getName(); System.out.println(name + "The method has started to execute..." ); Object proceed = invocation.proceed(); System.out.println(name + "Method execution ended..." ); return proceed; } }; } @Override public boolean isPerInstance () { return true ; } }); ICalculator calculator = (ICalculator) proxyFactory.getProxy(); calculator.add( 3 , 4 ); calculator.minus( 3 , 4 ); calculator.setA( 5 );
The above example also intercepts the setXXX method, but it should be noted that the full path of the method is used when matching method names.
In addition, it should be noted that when configuring the matching rules, you can also set the ExcludedPattern. In fact, when matching, forward matching is first performed, that is, first to see if the method name meets the rules. If it does, the method name is compared with the ExcludedPattern. If the comparison is not satisfied, then this method will be determined to be intercepted.
StaticMethodMatcherPointcut mainly provides us with these rules.
4. DynamicMethodMatcherPointcut
This is the cut-off point for dynamic method matching. By default, it matches all classes. However, when it comes to method matching, it will match every time. Let’s take a look:
public abstract class DynamicMethodMatcherPointcut extends DynamicMethodMatcher implements Pointcut { @Override public ClassFilter getClassFilter () { return ClassFilter.TRUE; } @Override public final MethodMatcher getMethodMatcher () { return this ; } }
As you can see, getClassFilter directly returns TRUE, that is, the class is directly matched, and getMethodMatcher returns the current object. That is because the current class implements the DynamicMethodMatcher interface, which is a method matcher:
public abstract class DynamicMethodMatcher implements MethodMatcher { @Override public final boolean isRuntime () { return true ; } @Override public boolean matches (Method method, Class<?> targetClass) { return true ; } }
Friends, you can see that the isRuntime method here returns true. This method is true, which means that the matches method with three parameters will be called, so the matches method with two parameters here can directly return true without any control.
Of course, you can also make some pre-judgments in the matches method of the two parameters.
Let’s look at a simple example:
public class MyDynamicMethodMatcherPointcut extends DynamicMethodMatcherPointcut { @Override public boolean matches (Method method, Class<?> targetClass) { return method.getName().startsWith( "set" ); } @Override public boolean matches (Method method, Class<?> targetClass, Object... args) { return method.getName().startsWith( "set" ) && args.length == 1 && Integer.class.isAssignableFrom(args [ 0 ].getClass()); } }
In the actual execution process, the matches method with two parameters returns true, and the matches method with three parameters will be executed. If the matches method with two parameters returns false, the matches method with three parameters will not be executed. Therefore, the two-parameter matches method can also directly return true, and only perform matching operations in the three-parameter matches method.
Then use this pointcut:
ProxyFactory proxyFactory = new ProxyFactory (); proxyFactory.setTarget( new CalculatorImpl ()); proxyFactory.addInterface(ICalculator.class); proxyFactory.addAdvisor( new PointcutAdvisor () { @Override public Pointcut getPointcut () { return new MyDynamicMethodMatcherPointcut (); } @Override public Advice getAdvice () { return new MethodInterceptor () { @Override public Object invoke (MethodInvocation invocation) throws Throwable { Method method = invocation.getMethod(); String name = method.getName(); System.out.println(name + "The method has started to execute..." ); Object proceed = invocation.proceed(); System.out.println(name + "Method execution ended..." ); return proceed; } }; } @Override public boolean isPerInstance () { return true ; } }); ICalculator calculator = (ICalculator) proxyFactory.getProxy(); calculator.add( 3 , 4 ); calculator.minus( 3 , 4 ); calculator.setA( 5 );
5. AnnotationMatchingPointcut
This is to determine whether there is an annotation on the class or method. If it exists, intercept it, otherwise it will not intercept it.
Let’s first look at the definition of this class:
public class AnnotationMatchingPointcut implements Pointcut { private final ClassFilter classFilter; private final MethodMatcher methodMatcher; public AnnotationMatchingPointcut (Class<? extends Annotation> classAnnotationType) { this (classAnnotationType, false ); } public AnnotationMatchingPointcut (Class<? extends Annotation> classAnnotationType, boolean checkInherited) { this .classFilter = new AnnotationClassFilter (classAnnotationType, checkInherited); this .methodMatcher = MethodMatcher.TRUE; } public AnnotationMatchingPointcut ( @Nullable Class<? extends Annotation> classAnnotationType, @Nullable Class<? extends Annotation> methodAnnotationType) { this (classAnnotationType, methodAnnotationType, false ); } public AnnotationMatchingPointcut ( @Nullable Class<? extends Annotation> classAnnotationType, @Nullable Class<? extends Annotation> methodAnnotationType, boolean checkInherited) { if (classAnnotationType != null ) { this .classFilter = new AnnotationClassFilter (classAnnotationType, checkInherited); } else { this .classFilter = new AnnotationCandidateClassFilter (methodAnnotationType); } if (methodAnnotationType != null ) { this .methodMatcher = new AnnotationMethodMatcher (methodAnnotationType, checkInherited); } else { this .methodMatcher = MethodMatcher.TRUE; } } @Override public ClassFilter getClassFilter () { return this .classFilter; } @Override public MethodMatcher getMethodMatcher () { return this .methodMatcher; } public static AnnotationMatchingPointcut forClassAnnotation (Class<? extends Annotation> annotationType) { Assert.notNull(annotationType, "Annotation type must not be null" ); return new AnnotationMatchingPointcut (annotationType); } public static AnnotationMatchingPointcut forMethodAnnotation (Class<? extends Annotation> annotationType) { Assert.notNull(annotationType, "Annotation type must not be null" ); return new AnnotationMatchingPointcut ( null , annotationType); } }
First of all, friends noticed that this class has a total of four construction methods, from top to bottom:
- Pass in the annotation name on the class, and determine whether interception is needed based on the annotations on the class.
- On the basis of 1, add a checkInherited, which indicates whether it is necessary to check whether there are relevant annotations on the parent class.
- Pass in the annotation type on the class and method, and determine whether interception is needed based on this annotation type.
- On the basis of 3, add a checkInherited, which indicates whether it is necessary to check whether there are relevant annotations on the parent class or method.
Among them, the fourth construction method handles many types of situations. If the user passes in classAnnotationType, a ClassFilter of type AnnotationClassFilter is built, otherwise a ClassFilter of type AnnotationCandidateClassFilter is built; if the user passes in methodAnnotationType, a MethodMatcher of type AnnotationMethodMatcher is built. Otherwise, the method matcher will directly return matching methods.
So next let’s take a look at these different matchers.
5.1 AnnotationClassFilter
public class AnnotationClassFilter implements ClassFilter { //... @Override public boolean matches (Class<?> clazz) { return ( this .checkInherited ? AnnotatedElementUtils.hasAnnotation(clazz, this .annotationType) : clazz.isAnnotationPresent( this .annotationType)); } //... }
Some code is omitted here. The key point is the matching method. If you need to check whether the parent class contains the annotation, call AnnotatedElementUtils.hasAnnotation
the method to find it. Otherwise, just call clazz.isAnnotationPresent
the method to determine whether the current class contains the specified annotation.
5.2 AnnotationCandidateClassFilter
private static class AnnotationCandidateClassFilter implements ClassFilter { private final Class<? extends Annotation > annotationType; AnnotationCandidateClassFilter(Class<? extends Annotation > annotationType) { this .annotationType = annotationType; } @Override public boolean matches (Class<?> clazz) { return AnnotationUtils.isCandidateClass(clazz, this .annotationType); } }
Here, AnnotationUtils.isCandidateClass
the method is called for judgment. This method is used to judge whether the specified class is a candidate class that can carry the specified annotation. The rule for returning true is:
- Annotations starting with
java.
can be carried by all classes, and true will be returned in this case. - The target class cannot
java.
start with , which means that the classes in the JDK will not work.java.
Classes that do not start with can return true. - A given class cannot also be
Ordered
a class.
If the above conditions are met, this class is in compliance with the regulations.
AnnotationCandidateClassFilter is mainly for the situation where the user does not pass in annotations on the class. In this case, it is usually matched based on the annotations on the method, so here it mainly excludes some system classes.
5.3 AnnotationMethodMatcher
public class AnnotationMethodMatcher extends StaticMethodMatcher { @Override public boolean matches (Method method, Class<?> targetClass) { if (matchesMethod(method)) { return true ; } // Proxy classes never have annotations on their redeclared methods. if (Proxy.isProxyClass(targetClass)) { return false ; } // The method may be on an interface, so let's check on the target class as well. Method specificMethod = AopUtils.getMostSpecificMethod(method, targetClass); return (specificMethod != method && matchesMethod(specificMethod)); } private boolean matchesMethod (Method method) { return ( this .checkInherited ? AnnotatedElementUtils.hasAnnotation(method, this .annotationType) : method.isAnnotationPresent( this .annotationType)); } }
Method matching is to first check whether there are annotations on the method. If checkInherited is turned on, then check whether there are relevant annotations on the method corresponding to the parent class. If so, it means that the method is matched and true is returned.
Otherwise, first check whether the current class is a proxy object. The corresponding method in the proxy object must not be annotated and return false directly.
If the previous two steps have not returned, finally consider that this method may be on an interface and check whether its implementation class contains this annotation.
This is AnnotationMatchingPointcut. Brother Song also gives a simple example.
5.4 Practice
First I customize an annotation as follows:
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface MyAction { }
Then add it to a method:
public class CalculatorImpl implements ICalculator { @Override public void add ( int a, int b) { System.out.println(a + "+" + b + "=" + (a + b)); } @MyAction @Override public int minus ( int a, int b) { return a - b; } @Override public void setA ( int a) { System.out.println( "a = " + a); } }
Finally, let’s practice it:
ProxyFactory proxyFactory = new ProxyFactory (); proxyFactory.setTarget( new CalculatorImpl ()); proxyFactory.addInterface(ICalculator.class); proxyFactory.addAdvisor( new PointcutAdvisor () { @Override public Pointcut getPointcut () { return new AnnotationMatchingPointcut ( null , MyAction.class); } @Override public Advice getAdvice () { return new MethodInterceptor () { @Override public Object invoke (MethodInvocation invocation) throws Throwable { Method method = invocation.getMethod(); String name = method.getName(); System.out.println(name + "The method has started to execute..." ); Object proceed = invocation.proceed(); System.out.println(name + "Method execution ended..." ); return proceed; } }; } @Override public boolean isPerInstance () { return true ; } }); ICalculator calculator = (ICalculator) proxyFactory.getProxy(); calculator.add( 3 , 4 ); calculator.minus( 3 , 4 ); calculator.setA( 5 );
Only the minus method is intercepted.
6.ExpressionPointcut
This is actually the most commonly used pointcut definition method in our daily development. Maybe 99% of the pointcut definitions in the project use ExpressionPointcut. I won’t go into the specific usage here, because it is quite rich and can be written in a separate article. If you are not familiar with the basic usage of ExpressionPointcut, you can reply to ssm in the background of the official account [Jiangnan Yidianyu]. Yousong You can refer to the introductory video tutorials I recorded before.
Here I will simply sort out its implementation ideas with my friends. The implementation of ExpressionPointcut is all in the AspectJExpressionPointcut class. This class supports the use of pointcut language to give a very precise description of the method to be intercepted, so precise that it needs to be intercepted. For the return value of the method, the implementation of the AspectJExpressionPointcut class is relatively long and complex. I will post some of the key codes here to take a look:
public class AspectJExpressionPointcut extends AbstractExpressionPointcut implements ClassFilter , IntroductionAwareMethodMatcher, BeanFactoryAware { private static final Set<PointcutPrimitive> SUPPORTED_PRIMITIVES = Set.of( PointcutPrimitive.EXECUTION, PointcutPrimitive.ARGS, PointcutPrimitive.REFERENCE, PointcutPrimitive.THIS, PointcutPrimitive.TARGET, PointcutPrimitive.WITHIN, PointcutPrimitive.AT_ANNOTATION, PointcutPrimitive.AT_WITHIN, PointcutPrimitive.AT_ARGS, PointcutPrimitive.AT_TARGET); @Override public ClassFilter getClassFilter () { obtainPointcutExpression(); return this ; } @Override public MethodMatcher getMethodMatcher () { obtainPointcutExpression(); return this ; } /** * Check whether this pointcut is ready to match, * lazily building the underlying AspectJ pointcut expression. */ private PointcutExpression obtainPointcutExpression () { if (getExpression() == null ) { throw new IllegalStateException ( "Must set property 'expression' before attempting to match" ); } if ( this .pointcutExpression == null ) { this .pointcutClassLoader = determinePointcutClassLoader(); this .pointcutExpression = buildPointcutExpression( this .pointcutClassLoader); } return this .pointcutExpression; } }
In fact, the key is to obtain the ClassFilter and MethodMatcher, and then call their matches method. The current class happens to implement these two, so just return this directly. Before the getClassFilter or getMethodMatcher method is executed, the obtainPointcutExpression method will be called first to parse the expression string we passed in and parse it into a PointcutExpression object. The subsequent matches method can match based on this.
7.ControlFlowPointcut
ControlFlowPointcut mainly means that the target method is executed from the specified method of a specified class, and the pointcut will take effect, otherwise it will not take effect.
A simple example is as follows:
public class AopDemo04 { public static void main (String[] args) { ProxyFactory proxyFactory = new ProxyFactory (); proxyFactory.setTarget( new CalculatorImpl ()); proxyFactory.addInterface(ICalculator.class); proxyFactory.addAdvisor( new PointcutAdvisor () { @Override public Pointcut getPointcut () { return new ControlFlowPointcut (AopDemo04.class, "evl" ); } @Override public Advice getAdvice () { return new MethodInterceptor () { @Override public Object invoke (MethodInvocation invocation) throws Throwable { Method method = invocation.getMethod(); String name = method.getName(); System.out.println(name + "The method has started to execute..." ); Object proceed = invocation.proceed(); System.out.println(name + "Method execution ended..." ); return proceed; } }; } @Override public boolean isPerInstance () { return true ; } }); ICalculator calculator = (ICalculator) proxyFactory.getProxy(); calculator.add( 3 , 4 ); System.out.println( "//////////////////" ); evl(calculator); } public static void evl (ICalculator iCalculator) { iCalculator.add( 3 , 4 ); } }
The pointcut here means that the add method must be called from the evl method of the AopDemo04 class for this pointcut to take effect. If the add method is called directly after getting the ICalculator object, the pointcut will not take effect.
The principle of ControlFlowPointcut is also very simple, just compare the class name and method name, as follows:
public class ControlFlowPointcut implements Pointcut , ClassFilter, MethodMatcher, Serializable { @Override public boolean matches (Class<?> clazz) { return true ; } @Override public boolean matches (Method method, Class<?> targetClass) { return true ; } @Override public boolean isRuntime () { return true ; } @Override public boolean matches (Method method, Class<?> targetClass, Object... args) { this .evaluations.incrementAndGet(); for (StackTraceElement element : new Throwable ().getStackTrace()) { if (element.getClassName().equals( this .clazz.getName()) && ( this .methodName == null || element.getMethodName().equals( this .methodName))) { return true ; } } return false ; } @Override public ClassFilter getClassFilter () { return this ; } @Override public MethodMatcher getMethodMatcher () { return this ; } }
As you can see, the isRuntime method returns true, indicating that this is a dynamic method matcher. The key matches method is to compare whether the given class name and method name are satisfied based on the information in the call stack.
8. ComposablePointcut
As you can see from the name, this can combine multiple cut points together. The combination relationships include intersection and union, which correspond to the intersection method and union method in ComposablePointcut respectively.
The following cases:
ProxyFactory proxyFactory = new ProxyFactory (); proxyFactory.setTarget( new CalculatorImpl ()); proxyFactory.addInterface(ICalculator.class); proxyFactory.addAdvisor( new PointcutAdvisor () { @Override public Pointcut getPointcut () { NameMatchMethodPointcut nameMatchMethodPointcut = new NameMatchMethodPointcut (); nameMatchMethodPointcut.setMappedNames( "add" ); ComposablePointcut pc = new ComposablePointcut ((Pointcut) nameMatchMethodPointcut); pc.union( new AnnotationMatchingPointcut ( null , MyAction.class)); return pc; } @Override public Advice getAdvice () { return new MethodInterceptor () { @Override public Object invoke (MethodInvocation invocation) throws Throwable { Method method = invocation.getMethod(); String name = method.getName(); System.out.println(name + "The method has started to execute..." ); Object proceed = invocation.proceed(); System.out.println(name + "Method execution ended..." ); return proceed; } }; } @Override public boolean isPerInstance () { return true ; } }); ICalculator calculator = (ICalculator) proxyFactory.getProxy(); calculator.add( 3 , 4 ); calculator.minus( 3 , 4 ); calculator.setA( 5 );
In the above case, the two pointcuts NameMatchMethodPointcut and AnnotationMatchingPointcut are combined to intercept not only the method named add, but also the method containing the @MyAction annotation.
If the union method is changed to intersection, it means that the interception method is named add and marked by the @MyAction annotation. as follows:
@Override public Pointcut getPointcut () { NameMatchMethodPointcut nameMatchMethodPointcut = new NameMatchMethodPointcut (); nameMatchMethodPointcut.setMappedNames( "add" ); ComposablePointcut pc = new ComposablePointcut ((Pointcut) nameMatchMethodPointcut); pc.intersection( new AnnotationMatchingPointcut ( null , MyAction.class)); return pc; }
In fact, the principle of this combination of pointcuts is very simple. First collect the ClassFilter and MethodMatcher we provide into a collection. If it is a union, it will traverse the collection directly. As long as one of them is satisfied, it will return true; if it is an intersection, it will also be traversed directly. , if one of them returns false, just return false directly.
Taking ClassFilter as an example, let’s take a brief look at the source code:
public ComposablePointcut union (ClassFilter other) { this .classFilter = ClassFilters.union( this .classFilter, other); return this ; } public abstract class ClassFilters { public static ClassFilter union (ClassFilter cf1, ClassFilter cf2) { return new UnionClassFilter ( new ClassFilter [] {cf1, cf2}); } private static class UnionClassFilter implements ClassFilter , Serializable { private final ClassFilter[] filters; UnionClassFilter(ClassFilter[] filters) { this .filters = filters; } @Override public boolean matches (Class<?> clazz) { for (ClassFilter filter : this .filters) { if (filter.matches(clazz)) { return true ; } } return false ; } } }
It can be seen that the multiple ClassFilters passed in are assembled together and traversed one by one in the matches method. As long as one of them returns true, it is true.
9. Summary
Okay, these are the 7 Pointcuts that Brother Song introduced to his friends today. I hope that through this, my friends will have a complete understanding of the types of pointcuts in Spring AOP. Let’s review the key points:
- Static method pointcut: StaticMethodMatcherPointcut represents the abstract base class of static method pointcut, which matches all classes by default, and then matches different methods through different rules.
- Dynamic method pointcut: DynamicMethodMatcherPointcut represents the abstract base class of dynamic method pointcut. By default, it matches all classes, and then matches different methods through different rules. This is somewhat similar to StaticMethodMatcherPointcut. The difference is that StaticMethodMatcherPointcut only signatures methods. Matching is performed and only matched once, while DynamicMethodMatcherPointcut will check the value of the method’s input parameters during runtime. Since the parameters passed in may be different each time, it must be judged before calling, which leads to poor performance of DynamicMethodMatcherPointcut.
- Annotation pointcut: AnnotationMatchingPointcut intercepts the target method or class based on the specified annotation.
- Expression pointcut: ExpressionPointcut This is the most commonly used pointcut definition method in our daily development.
- Process pointcut: ControlFlowPointcut This requires that the target method must be called from a certain location for the pointcut to take effect.
- Composite pointcut: ComposablePointcut is used to assemble multiple interceptors together. There are two assembly methods: intersection and union.
- TruePointcut This is a pointcut used internally by the framework to intercept everything.
Oh~