In Spring AOP, there are two main ways to define pointcut that we use most often:

  1. Non-intrusive interception using execution.
  2. Use annotations for interception.

This should be the daily work of the two most used in the definition of Pointcut. But in addition to these two there are some other, this article to explore this topic.

1. Pointcut Classification

Take a look at the definition of Pointcut:

1
2
3
4
5
public interface Pointcut {
 ClassFilter getClassFilter();
 MethodMatcher getMethodMatcher();
 Pointcut TRUE = TruePointcut.INSTANCE;
}

As you can see from the method names, getClassFilter does class filtering and getMethodMatcher does method filtering. By filtering Class and filtering Method, we can identify an object that is being intercepted.

Let’s take a look at the inheritance diagram of the Pointcut class:

Inheritance diagram of Pointcut class

As you can see, in fact, the implementation of the class is not too much, most of them can be guessed by the name of what is done, the implementation of these classes we can be roughly divided into six categories:

  1. Static Method Pointcut: StaticMethodMatcherPointcut represents an abstract base class for static method pointcut, which matches all classes by default, and then matches different methods by different rules.
  2. Dynamic method pointcut: DynamicMethodMatcherPointcut represents an abstract base class for dynamic method pointcut, which by default matches all classes and then matches different methods with different rules. This is somewhat similar to StaticMethodMatcherPointcut, except that StaticMethodMatcherPointcut only matches method signatures and does so only once, whereas DynamicMethodMatcherPointcut checks the value of the method’s input parameter at runtime. Since the input parameters may be different each time, it is necessary to judge them every time it is called, which results in the performance of DynamicMethodMatcherPointcut being worse.
  3. Annotation pointcut: AnnotationMatchingPointcut.
  4. Expression pointcut: ExpressionPointcut.
  5. Flow pointcut: ControlFlowPointcut.
  6. Composite pointcut: ComposablePointcut.

In addition to the above six, there is also a TruePointcut, which as you can see from its name intercepts everything.

So a full count, there are seven types of pointcut, then we will analyse them one by one.

2. TruePointcut

This implementation class by name is Intercept All, Intercept Everything, so let’s look at how this class does it:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
final class TruePointcut implements Pointcut, Serializable {
    //...
 @Override
 public ClassFilter getClassFilter() {
  return ClassFilter.TRUE;
 }

 @Override
 public MethodMatcher getMethodMatcher() {
  return MethodMatcher.TRUE;
 }
    //...
}

Note that this class is not public, so that means we can’t use this pointcut directly in our own development. getClassFilter and getMethodMatcher methods return TRUE here, and the implementation of these two TRUEs is very simple, that is, where there is a need to compare, we don’t do any comparison, we just return true. The implementation of these two TRUEs is very simple. This results in everything being intercepted in the end.

3. StaticMethodMatcherPointcut

StaticMethodMatcherPointcut matches only method name signatures (including method name and input types and order), static matches are judged only once.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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, here class matching is the default return true, method matching is to return the current object, that is, depending on the specific implementation.

StaticMethodMatcherPointcut has several predefined 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:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
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 determining whether the current method is a set method, requiring that the method name start with set, that the method has only one parameter, and that the method return value is null, pinpointing a set method.

An example of use is given below:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
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 + " method has begun to execute...");
                Object proceed = invocation.proceed();
                System.out.println(name + " Method execution is over....");
                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, instances can be obtained through the tool class Pointcuts.

3.2 GetterPointcut

GetterPointcut is similar to SetterPointcut, as follows:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
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 don’t think we need to explain this too much, it’s similar to the previous SetterPointcut.

3.3 NameMatchMethodPointcut

This one matches a method by its name.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
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 a list of method names from the outside in, and then in the matches method to match can be matched, matching directly call equals method to match, if equals method does not match, then call isMatch method to match, this ultimately call to PatternMatchUtils. simpleMatch method, which is a tool class provided in Spring to support wildcard matching.

A simple example:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
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 + " method has begun to execute...");
                Object proceed = invocation.proceed();
                System.out.println(name + " Method execution is over....");
                return proceed;
            }
        };
    }
    @Override
    public boolean isPerInstance() {
        return true;
    }
});
ICalculator calculator = (ICalculator) proxyFactory.getProxy();
calculator.add(3,4);
calculator.minus(3, 4);
calculator.setA(5);

Here I’ve set up an intercept for methods whose name is add or whose name starts with set.

3.4 JdkRegexpMethodPointcut

This supports matching the method name with a regularity, and any method that matches will be blocked.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
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, you are actually passing in a regular expression and then matching the method name to see if it meets the condition. You can pass more than one regular expression, and the system will iterate through the parent class of JdkRegexpMethodPointcut to match them one by one. Let me give you an example:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
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 + " method has begun to execute...");
                Object proceed = invocation.proceed();
                System.out.println(name + " Method execution is over....");
                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 the method name.

Also note that when configuring the matching rules, you can also set the ExcludedPattern, which is actually the first forward match when matching, that is, first see if the method name meets the rules, if it does, then the method name is matched against the ExcludedPattern, if it doesn’t, then the method will be determined to be intercepted.

StaticMethodMatcherPointcut mainly provides us with these rules.

4. DynamicMethodMatcherPointcut

This is a dynamic method matching pointcut, by default it also matches all classes, but in the case of method matching, it matches every time it is executed, so let’s take a look at it.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
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 return TRUE, that is, the class is directly matched, getMethodMatcher return is the current object, that is because the current class implements the DynamicMethodMatcher interface, which is a method matcher.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
public abstract class DynamicMethodMatcher implements MethodMatcher {

 @Override
 public final boolean isRuntime() {
  return true;
 }
 @Override
 public boolean matches(Method method, Class<?> targetClass) {
  return true;
 }

}

The isRuntime method returns true, which means that the three-argument matches method will be called, so the two-argument matches method can just return true without any control.

Of course, you can also do some pre-judgment in the two-argument matches method.

Let’s look at a simple example.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
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 two-argument matches method returns true before the three-argument matches method is executed, and if the two-argument matches method returns false, then the three-argument matches will not be executed. So you can also fix the two-argument matches method to return true, and just do the matching in the three-argument matches method.

Then use this pointcut:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
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 + " method has begun to execute...");
                Object proceed = invocation.proceed();
                System.out.println(name + " The execution of the method is over....");
                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 so, it will be intercepted, otherwise not intercepted.

First look at the definition of the class:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
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);
 }

}

The first thing you’ll notice is that the class has a total of four constructor methods, from top to bottom:

  1. pass in the name of the annotation on the class to determine if it needs to be intercepted based on the annotation on the class.
  2. on top of 1, add a checkInherited, which indicates whether the parent class needs to be checked for the existence of the annotation.
  3. Pass in the annotation type of the class and method, and determine if it needs to be intercepted based on that annotation type.
  4. On top of 3, add a checkInherited, which indicates whether we need to check if the annotation exists on the parent class or method.

Among them, there are more types of cases handled in the fourth constructor, if the user passes in classAnnotationType, it builds ClassFilter of type AnnotationClassFilter, otherwise it builds AnnotationCandidateClassFilter of type ClassFilter; if the user passes methodAnnotationType, then build MethodMatcher of type AnnotationMethodMatcher, otherwise the method matcher will directly return match all methods.

So let’s look at these different matchers next.

5.1 AnnotationClassFilter

1
2
3
4
5
6
7
8
9
public class AnnotationClassFilter implements ClassFilter {
    //...
 @Override
 public boolean matches(Class<?> clazz) {
  return (this.checkInherited ? AnnotatedElementUtils.hasAnnotation(clazz, this.annotationType) :
    clazz.isAnnotationPresent(this.annotationType));
 }
    //...
}

Here some code is omitted, the key place is this match method, if you need to check whether the parent class contains the annotation, then call the AnnotatedElementUtils.hasAnnotation method to find out, otherwise directly call the clazz.isAnnotationPresent method to determine if the current class isAnnotationPresent` method to determine whether the current class contains the specified annotation.

5.2 AnnotationCandidateClassFilter

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
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);
 }
}

The AnnotationUtils.isCandidateClass method is called to determine whether the specified class is a candidate class for the specified annotation, and the rules for returning true are as follows:

  1. Annotations that begin with java. will return true.
  2. the target class cannot begin with java., which means that any class in the JDK will return false. a class that does not begin with java. will return true.
  3. The given class cannot be an Ordered class either.

If the above conditions are met, the class is compliant.

AnnotationCandidateClassFilter is mainly for the case where the user does not pass in the class annotations, which are generally matched based on the annotations on the methods, so this is mainly to exclude some system classes.

5.3 AnnotationMethodMatcher

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
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 if there is annotation on the method, if checkInherited is enabled, then check if there is annotation on the corresponding method of the parent class, if there is, then the method is matched, return true.

Otherwise, check if the current class is a proxy object first, the corresponding method in the proxy object must not have annotations, and return false directly.

If the first two steps do not return, finally consider that the method may be on an interface, to check whether its implementation class contains the annotation.

This is AnnotationMatchingPointcut, a simple example is as follows.

5.4 Practices

First I customise an annotation as follows:

1
2
3
4
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface MyAction {
}

Then annotate it to a method:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
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, practice.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
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 + " method has begun to execute...");
                Object proceed = invocation.proceed();
                System.out.println(name + " Method execution is over...");
                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 one of the most used pointcut definitions in our daily development, probably 99% of the cutpoint definitions in the project are using ExpressionPointcut. I won’t talk about the specific usage of this one here, because it’s quite rich, and I’m going to write a separate blog about it later.

I’m here to simply sort out the idea of its implementation , ExpressionPointcut implementation are in the AspectJExpressionPointcut class , the class supports the use of pointcut language to do a very precise description of the method to be intercepted (accurate to the return value of the method to be intercepted) , AspectJExpressionPointcut class implementation of the longer and more complex , the following is some of the key code .

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
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;
 }
}

The key is to get ClassFilter and MethodMatcher, and then call their matches methods, which the current class implements, so you can just return this. Before the getClassFilter or getMethodMatcher methods are executed, they call the getPointcutExpression method, which parses the expression string we passed in, and parses the string into a PointcutExpression object. The matches method will then use this object as a base for matching.

7. ControlFlowPointcut

ControlFlowPointcut mainly means that the pointcut takes effect when the target method is executed from a specified method of a specified class, otherwise it does not take effect.

A simple example is as follows:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
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 + " method has begun to execute...");
                        Object proceed = invocation.proceed();
                        System.out.println(name + " Method execution is over....");
                        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 the pointcut to take effect, if the add method is called directly after getting the ICalculator object, then the pointcut will not take effect.

The principle of ControlFlowPointcut is also very simple, which is to compare the class name and method name, as follows:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
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 compares whether a given class name and method name are satisfied based on the information in the call stack.

8. ComposablePointcut

Look at the name, you know, this can be combined with multiple pointcut, the combination of two kinds of intersection and union, respectively, corresponding to the ComposablePointcut intersection method and union method.

Examples are as follows.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
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 + " method has begun to execute... ");
                Object proceed = invocation.proceed();
                System.out.println(name + " Method execution is over... ");
                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 example, the NameMatchMethodPointcut and AnnotationMatchingPointcut pointcut are combined to intercept both the method named add and the method with the @MyAction annotation.

If you change the union method to intersection, you are intercepting the method named add that is annotated with the @MyAction annotation. This would look like the following:

1
2
3
4
5
6
7
8
@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 pointcut is very simple, first we provide ClassFilter and MethodMatcher collected into a collection, if it is a union, directly traverse the collection, as long as there is a meet, it will return true; if it is an intersection, it is also directly traverse. If one of them returns false, then it returns false directly.

ClassFilter as an example, let’s take a look at the source code:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
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;
  }

 }
}

As you can see, the incoming ClassFilters are assembled together and traversed one by one in the matches method, and as long as one of them returns true, it’s true.

https://mp.weixin.qq.com/s/O-EGbaMCwHhToNvLvoCPfg