How many ways are there to define pointcuts in Spring AOP?

In Spring AOP, our most commonly used methods of defining pointcuts are mainly two:

  1. Use execution for non-intrusive interception.
  2. 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.

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:

  1. 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.
  2. 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.
  3. Annotation pointcut: AnnotationMatchingPointcut.
  4. Expression pointcut: ExpressionPointcut.
  5. Process cut point: ControlFlowPointcut.
  6. 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:

  1. Pass in the annotation name on the class, and determine whether interception is needed based on the annotations on the class.
  2. 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.
  3. Pass in the annotation type on the class and method, and determine whether interception is needed based on this annotation type.
  4. 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.hasAnnotationthe method to find it. Otherwise, just call clazz.isAnnotationPresentthe 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.isCandidateClassthe 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:

  1. Annotations starting with java.can be carried by all classes, and true will be returned in this case.
  2. 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.
  3. A given class cannot also be Ordereda 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:

  1. 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.
  2. 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.
  3. Annotation pointcut: AnnotationMatchingPointcut intercepts the target method or class based on the specified annotation.
  4. Expression pointcut: ExpressionPointcut This is the most commonly used pointcut definition method in our daily development.
  5. Process pointcut: ControlFlowPointcut This requires that the target method must be called from a certain location for the pointcut to take effect.
  6. Composite pointcut: ComposablePointcut is used to assemble multiple interceptors together. There are two assembly methods: intersection and union.
  7. TruePointcut This is a pointcut used internally by the framework to intercept everything.

Oh~