Skip to main content

Pointcut in Spring AOP

Please refer to spring-framework under org.springframework.mylearntest package for code related to this article (from official source spring-test module).

If Pointcut type is TruePointcut, it defaults to match all objects in system, and all supported Joinpoints on objects.

Pointcut
package org.springframework.aop;

public interface Pointcut {

ClassFilter getClassFilter();

MethodMatcher getMethodMatcher();

Pointcut TRUE = TruePointcut.INSTANCE;

}
TruePointcut
package org.springframework.aop;

import java.io.Serializable;

@SuppressWarnings("serial")
final class TruePointcut implements Pointcut, Serializable {

public static final TruePointcut INSTANCE = new TruePointcut();

private TruePointcut() {
}

@Override
public ClassFilter getClassFilter() {
return ClassFilter.TRUE;
}

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

private Object readResolve() {
return INSTANCE;
}

@Override
public String toString() {
return "Pointcut.TRUE";
}

}

ClassFilter and MethodMatcher are used to match objects and corresponding methods where weaving operations will be performed respectively. Reason for separating type matching and method matching definition is that matching definitions of different levels can be reused, and combination operations can be performed on different levels or same level, or force a subclass to only Override corresponding method definition etc.

ClassFilter
package org.springframework.aop;

@FunctionalInterface
public interface ClassFilter {

boolean matches(Class<?> clazz);

ClassFilter TRUE = TrueClassFilter.INSTANCE;

}
MethodMatcher
package org.springframework.aop;

import java.lang.reflect.Method;

public interface MethodMatcher {

boolean matches(Method method, Class<?> targetClass);

boolean isRuntime();

boolean matches(Method method, Class<?> targetClass, Object... args);

MethodMatcher TRUE = TrueMethodMatcher.INSTANCE;

}

When isRuntime returns false, it indicates that method arguments of concrete Joinpoint will not be considered, MethodMatcher of this type is called staticMethodMatcher. Because there is no need to check arguments every time, matching results for same type of methods can be cached inside framework to improve performance.

When isRuntime returns true, it indicates that MethodMatcher will check method call arguments for matching every time, MethodMatcher of this type is called DynamicMethodMatcher. Because method arguments need to be checked every time, matching results cannot be cached, so matching efficiency is poorer compared to StaticMethodMatcher. And in most cases, staticMethodMatcher can already satisfy needs. It is best to avoid using DynamicMethodMatcher type.

If boolean matches(Method method, Class<?> targetClass); returns true, matches with three arguments will be executed to further check matching conditions; if boolean matches(Method method, Class<?> targetClass); returns false, then regardless of whether this MethodMatcher is staticMethodMatcher or DynamicMethodMatcher, this result is already final result, method with three arguments will definitely not be executed.

Common pointcut

NameMatchMethodPointcut

Simples Pointcut implementation, belongs to subclass of StaticMethodMatcherPointcut, can match based on a set of method names specified by itself against method name at Joinpoint.

NameMatchMethodPointcut pointcut = new NameMatchMethodPointcut();
pointcut.setMappedName("matches");
// Or pass multiple method names
pointcut.setMappedNames(new String[]{"matches", "isRuntime"});
// Simple fuzzy matching
pointcut.setMappedNames(new String[]{"match*", "matches", "mat*es" });

This method cannot match overloaded method names, because it only matches method names, does not consider parameter related information, and also does not provide a way to specify parameter matching information.

JdkRegexpMethodPointcut and Perl5RegexpMethodPointcut

StaticMethodMatcherPointcut's subclass has a branch specially providing regular expression based implementation, commanded by abstract class AbstractRegexpMethodPointcut, declaring pattern and patterns properties, can specify one or more regular expression matching patterns. Under it there are two concrete implementations SdkRegexpMethodPointcut (likely meaning JdkRegexpMethodPointcut) and Perl5RegexpMethodPointcut. JdkRegexpMethodPointcut is JDK standard regular expression introduced after JDK 1.4.

JdkRegexpMethodPointcut pointcut = new JdkRegexpMethodPointcut();
pointcut.setPattern(".*match.*");
pointcut.setPatterns(new String[]{".*match.", ".*matches"});

Note that regular expression matching pattern must be specified in form matching entire method signature (Method signature), and cannot just give matching method name part like NameMatchMethodPointcut.

Perl5RegexpMethodPointcut implementation uses Jakarta ORO to provide regular expression support.

  • Can specify one or more regular expressions via pattern or patterns object properties
  • Specified regular expression matching pattern should cover matching entire method signature, rather than just specifying method name part.

AnnotationMatchingPointcut

ClassLevelAnnotation
package org.springframework.mylearntest.aop.annotationmatchingpointcut;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface ClassLevelAnnotation {
}
MethodLevelAnnotation
package org.springframework.mylearntest.aop.annotationmatchingpointcut;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface MethodLevelAnnotation {
}
GenericTargetObject
package org.springframework.mylearntest.aop.annotationmatchingpointcut;

@ClassLevelAnnotation
public class GenericTargetObject {

@MethodLevelAnnotation
public void getMethod1() {
System.out.println("getMethod1");
}

public void getMethod2() {
System.out.println("getMethod2");
}
}
How to specify Pointcut
AnnotationMatchingPointcut pointcut = new AnnotationMatchingPointcut(ClassLevelAnnotation.class);
// Or via static method
AnnotationMatchingPointcut pointcut1 = AnnotationMatchingPointcut.forClassAnnotation(MethodLevelAnnotation.class);
// Simultaneous limit
AnnotationMatchingPointcut pointcut2 = AnnotationMatchingPointcut.forClassAnnotation(ClassLevelAnnotation.class);

ComposablePointcut

Spring AOP provides Pointcut implementation for Pointcut logic operations. It can perform "Union" and "Intersection" operations between Pointcuts.

Test4ComposablePointcut
package org.springframework.mylearntest.aop.pointcut.composablePointcut;

import org.junit.Assert;
import org.springframework.aop.ClassFilter;
import org.springframework.aop.MethodMatcher;
import org.springframework.aop.Pointcut;
import org.springframework.aop.support.ComposablePointcut;
import org.springframework.aop.support.Pointcuts;

public class Test4ComposablePointcut {

public static void main(String[] args) {
ComposablePointcut pointcut1 = new ComposablePointcut(new ClassFilter() {
@Override
public boolean matches(Class<?> clazz) {
return false;
}
}, MethodMatcher.TRUE);

ComposablePointcut pointcut2 = new ComposablePointcut(new ClassFilter() {
@Override
public boolean matches(Class<?> clazz) {
return false;
}
}, MethodMatcher.TRUE);

// union intersection
ComposablePointcut union = pointcut1.union(pointcut2);
ComposablePointcut intersection = pointcut1.intersection(union);

Assert.assertEquals(pointcut1,intersection);

// combine classFilter with methodMatcher
pointcut2.union(new ClassFilter() {
@Override
public boolean matches(Class<?> clazz) {
return false;
}
}).intersection(MethodMatcher.TRUE);

// just compute between pointcut, use org.springframework.aop.support.Pointcuts
Pointcut pointcut3 = new Pointcut() {
@Override
public ClassFilter getClassFilter() {
return null;
}

@Override
public MethodMatcher getMethodMatcher() {
return null;
}
};

Pointcut pointcut4 = new Pointcut() {
@Override
public ClassFilter getClassFilter() {
return null;
}

@Override
public MethodMatcher getMethodMatcher() {
return null;
}
};

Pointcut union1 = Pointcuts.union(pointcut3, pointcut4);
Pointcut intersection1 = Pointcuts.intersection(pointcut3, pointcut4);

}
}

ControlFlowPointcut

ControlFlowPointcut matches the program's calling flow, it does not match a single feature at the Joinpoint where a method execution is located, but intercepts method only when executed by a specific class. Because ControlFlowPointcut type Pointcut needs to check program's calling stack during runtime, and needs to check every time method is called, so performance is relatively poor.

Agreement
The code part of this work is licensed under Apache License 2.0 . You may freely modify and redistribute the code, and use it for commercial purposes, provided that you comply with the license. However, you are required to:
  • Attribution: Retain the original author's signature and code source information in the original and derivative code.
  • Preserve License: Retain the Apache 2.0 license file in the original and derivative code.
The documentation part of this work is licensed under Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License . You may freely share, including copying and distributing this work in any medium or format, and freely adapt, remix, transform, and build upon the material. However, you are required to:
  • Attribution: Give appropriate credit, provide a link to the license, and indicate if changes were made.
  • NonCommercial: You may not use the material for commercial purposes. For commercial use, please contact the author.
  • ShareAlike: If you remix, transform, or build upon the material, you must distribute your contributions under the same license as the original.