Pointcut 功能
- Pointcut 接口是中心接口,用于将 advice 定向到特定的类和方法。
- pointcut 可以重用,而不依赖于 advice 类型。可以使用相同的 pointcut 来针对不同的 advice。
- 将 Pointcut 接口分成两个部分允许重复使用类(ClassFilter)和方法(MethodMatcher)匹配部件以及细粒度的组合操作(例如与另一个 MethodMatcher/ClassFilter 执行交集或者并集)。
类图
从类图可知 Pointcut 分为 6 大类:
- ComposablePointcut:把多个过滤条件(ClassFilter 或 MethodMatcher 亦是 Pointcut)通过交、并组合起来变成一个 Pointcut
- StaticMethodMatcherPointcut:StaticMethodMatcher + Pointcut 组合在一起,不需要关心运行时参数
- DynamicMethodMatcherPointcut:DynamicMethodMatcher + Pointcut 组合在一起,关心运行时参数
- AnnotationMatchingPointcut:注解类型 Pointcut
- ControlFlowPointcut:用于简单的 cflow(CallTree 调用链) 风格 Pointcut
- ExpressionPointcut:表达式类型 Pointcut
相关类/接口介绍
ClassFilter
- 限制 Pointcut 匹配或对一组给定目标类的 Introduction 的过滤器。作为 Pointcut 一部分使用也可以作为一个 IntroductionAdvisor 的整个目标,该接口的具体实现通常应该提供 Object.equals(Object) 和Object. hashcode() 的适当实现,以便允许过滤器在缓存场景中使用 —— 例如,在CGLIB生成的代理中。
- 对类进行过滤所以只提供了:
boolean matches(Class<?> clazz)
方法
MethodMatcher
- 用于检查目标方法是否有资格获得 advice。
- MathodMatcher 可以静态地或在运行时(动态地)计算。静态匹配涉及方法和(可能)方法属性。动态匹配还使特定调用的参数可用,以及将先前的 advice 应用到 joinpoint 的任何效果。
- 如果实现
isRuntime()
方法返回false
,则可以静态执行求值,并且该方法的所有调用的结果都是相同的,无论它们的参数是什么。这意味着如果isRuntime()
方法返回false
,则 3 参数matches(Method method, Class<?> targetClass, Object... args)
方法将永远不会被调用。 - 如果一个实现从它的 2 参数
matches(Method method, Class<?> targetClass)
返回true
和它的isRuntime()
方法返回true
, 3 参数matches(Method method, Class<?> targetClass, Object... args)
方法将在相关 advice 的每次潜在执行之前被立即调用,以决定该 advice 是否应该运行。之前的所有 adivce (比如拦截器链中的早期拦截器)都将运行,因此它们在参数或 ThreadLocal 状态中产生的任何状态更改都将在计算时可用。 - 该接口的具体实现通常应该提供 Object.equals(Object) 和 Object. hashcode() 的适当实现,以便允许匹配器在缓存场景中使用——例如,在 CGLIB 生成的代理中。
StaticMethodMatcherPointcut
StaticMethodMatcher + Pointcut 二合一。 StaticMethodMatcher:静态方法匹配器的方便抽象超类,它不关心运行时的参数。由于不关心运行时参数所以
isRuntime()
方法返回 false,3个参数的matches
方法直接抛出UnsupportedOperationException
异常。ClassFilter:默认值是 ClassFilter.TRUE(匹配所有类),可以通过 setClassFilter 方法设置 ClassFilter 或者覆写 getClassFilter 方法
示例
如何对 EchoService 接口的 echo 方法进行拦截?
// 继承 StaticMethodMatcherPointcut 覆写 matches 方法
public class SimpleStaticPointcut extends StaticMethodMatcherPointcut {
@Override
public boolean matches(@NonNull Method method,@NonNull Class<?> targetClass) {
return "echo".equals(method.getName());
}
@Override
@NonNull
public ClassFilter getClassFilter() {
return EchoService.class::isAssignableFrom;
}
}
public interface EchoService {
String echo(String message);
}
public class DefaultEchoServiceImpl implements EchoService {
@Override
public String echo(String message) {
return message;
}
}
public interface EchoService2 {
String echo(String message);
}
public class DefaultEcho2ServiceImpl implements EchoService2 {
@Override
public String echo(String message) {
return message;
}
}
public class StaticPointcutDemo {
public static void main(String[] args) {
EchoService echoService = new DefaultEchoServiceImpl();
EchoService2 echoService2 = new DefaultEcho2ServiceImpl();
Pointcut pc = new SimpleStaticPointcut();
Advisor advisor = new DefaultPointcutAdvisor(pc, (MethodBeforeAdvice) (method, arg, target) -> System.out.println("MethodBeforeAdvice execute: " + method));
ProxyFactory proxyFactory = new ProxyFactory();
proxyFactory.addAdvisor(advisor);
proxyFactory.setTarget(echoService);
EchoService proxyOne = (EchoService) proxyFactory.getProxy();
ProxyFactory proxyFactory2 = new ProxyFactory();
proxyFactory2.setTarget(echoService2);
proxyFactory2.addAdvisor(advisor);
EchoService2 proxyTwo = (EchoService2) proxyFactory2.getProxy();
// 会被拦截并执行 MethodBeforeAdvice
proxyOne.echo("文海");
proxyTwo.echo("文海");
}
}
DynamicMethodMatcherPointcut
DynamicMethodMatcher + Pointcut 2合一。 DynamicMethodMatcher:动态方法匹配器的方便抽象超类,它关心运行时的参数。由于关心运行时参数所以
isRuntime()
方法返回 true,将要覆写3个参数的matches
方法。ClassFilter:默认值是 ClassFilter.TRUE(匹配所有类),可以通过覆写 getClassFilter 方法设置 ClassFilter
示例
如何实现对 EchoService 接口的 echo 方法参数为
文海大叔
时才进行拦截 ?
public class SimpleDynamicPointcut extends DynamicMethodMatcherPointcut {
@Override
public boolean matches(@NonNull Method method, @NonNull Class<?> targetClass, @NonNull Object... args) {
System.out.println("Dynamic check method: " + method.getName());
System.out.println("Method args: " + Arrays.toString(args));
String message = (String) args[0];
boolean match = "文海大叔".equals(message);
System.out.println("是否匹配:" + match);
return match;
}
@Override
public boolean matches(Method method, Class<?> targetClass) {
System.out.println("Static check method: " + method.getName());
return "echo".equals(method.getName());
}
@Override
@NonNull
public ClassFilter getClassFilter() {
return EchoService.class::isAssignableFrom;
}
}
public interface EchoService {
String echo(String message);
}
public class DefaultEchoServiceImpl implements EchoService {
@Override
public String echo(String message) {
return message;
}
}
public interface EchoService2 {
String echo(String message);
}
public class DefaultEcho2ServiceImpl implements EchoService2 {
@Override
public String echo(String message) {
return message;
}
}
public class DynamicPointcutDemo {
public static void main(String[] args) {
EchoService echoService = new DefaultEchoServiceImpl();
EchoService2 echoService2 = new DefaultEcho2ServiceImpl();
Advisor advisor = new DefaultPointcutAdvisor(new SimpleDynamicPointcut(),
(MethodBeforeAdvice) (method, args1, target) -> System.out.printf("被拦截的方法为:%s,方法参数为:%s\n", method, Arrays.toString(args1))
);
ProxyFactory proxyFactory = new ProxyFactory();
proxyFactory.setTarget(echoService);
proxyFactory.addAdvisor(advisor);
EchoService echoServiceProxy = (EchoService) proxyFactory.getProxy();
ProxyFactory proxyFactory2 = new ProxyFactory();
proxyFactory2.setTarget(echoService2);
proxyFactory2.addAdvisor(advisor);
EchoService2 echoService2Proxy = (EchoService2) proxyFactory2.getProxy();
// 先类型匹配了,再进行静态匹配,后面在进行动态匹配参数,参数不符合,不执行后面方法;
echoServiceProxy.echo("文海");
// 先类型匹配了,不匹配。不再进行后面匹配
echoService2Proxy.echo("文海");
// 先类型匹配了,再进行静态匹配,后面在进行动态匹配参数,参数符合,执行 advice 方法;
echoServiceProxy.echo("文海大叔");
// 即使是参数匹配,但是也是先类型匹配了,不匹配。不再进行后面匹配
echoService2Proxy.echo("文海大叔");
}
}
ComposablePointcut
把多个过滤条件(ClassFilter 或 MethodMatcher 亦是 Pointcut)通过交、并组合起来变成一个 Pointcut
示例
如果实现对 EchoService 或 EchoService2 类中 echo 方法进行拦截?
public class ComposablePointcutDemo {
public static void main(String[] args) {
EchoService echoService = new DefaultEchoServiceImpl();
EchoService2 echoService2 = new DefaultEcho2ServiceImpl();
// 对 EchoService 实例过滤
ComposablePointcut pointcut = new ComposablePointcut((EchoService.class::isAssignableFrom));
// 或对 EchoService2 实例过滤
pointcut.union((EchoService2.class::isAssignableFrom));
// 并对 echo 方法过滤
pointcut.intersection(new MethodMatcher() {
@Override
public boolean matches(Method method, Class<?> targetClass) {
return "echo".equals(method.getName());
}
@Override
public boolean isRuntime() {
return false;
}
@Override
public boolean matches(Method method, Class<?> targetClass, Object... args) {
return false;
}
});
Advisor advisor = new DefaultPointcutAdvisor(pointcut,
(MethodBeforeAdvice) (method, args1, target) -> System.out.printf("被拦截的方法为:%s,方法参数为:%s\n", method, Arrays.toString(args1))
);
ProxyFactory proxyFactory = new ProxyFactory();
proxyFactory.setTarget(echoService);
proxyFactory.addAdvisor(advisor);
EchoService echoServiceProxy = (EchoService) proxyFactory.getProxy();
ProxyFactory proxyFactory2 = new ProxyFactory();
proxyFactory2.setTarget(echoService2);
proxyFactory2.addAdvisor(advisor);
EchoService2 echoService2Proxy = (EchoService2) proxyFactory2.getProxy();
echoServiceProxy.echo("文海");
echoService2Proxy.echo("文海");
echoServiceProxy.echo("文海大叔");
echoService2Proxy.echo("文海大叔");
echoServiceProxy.echo2("文海");
}
}
ExpressionPointcut
支持表达式类型 Pointcut,最具有代表性的就是 AspectJExpressionPointcut。对表达式类型 Pointcut 表达式值是一个 AspectJ 字符串表达式。这可以引用其他 pointcut 并使用组合和其他操作。
示例
对 EchoService echo 方法进行拦截
public interface EchoService {
String echo(String message);
default String echo2(String message){
return message;
}
}
public class DefaultEchoServiceImpl implements EchoService {
@Override
public String echo(String message) {
return message;
}
}
public class AspectJExpressionPointcutDemo {
public static void main(String[] args) {
EchoService echoService = new DefaultEchoServiceImpl();
AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
pointcut.setExpression("execution(* echo(..))");
Advisor advisor = new DefaultPointcutAdvisor(pointcut,
(MethodBeforeAdvice) (method, args1, target) -> System.out.printf("被拦截的方法为:%s,方法参数为:%s\n", method, Arrays.toString(args1))
);
ProxyFactory proxyFactory = new ProxyFactory();
proxyFactory.setTarget(echoService);
proxyFactory.addAdvisor(advisor);
EchoService proxy = (EchoService) proxyFactory.getProxy();
proxy.echo("文海");
// 不会拦截
proxy.echo2("文海");
}
}
AnnotationMatchingPointcut
寻找类(forClassAnnotation)或方法(forMethodAnnotation)上的特定 Java 5 注释的简单 Pointcut。通过 AnnotationClassFilter 和 AnnotionMethodMatcher 来实现。
示例
找到标注自定义 @Advice 方法进行拦截
public interface EchoService {
String echo(String message);
default String echo2(String message){
return message;
}
}
public class DefaultEchoServiceImpl implements EchoService {
@AnnotationPointcut.Advice
@Override
public String echo(String message) {
return message;
}
}
public class AnnotationPointcut {
public static void main(String[] args) {
EchoService echoService = new DefaultEchoServiceImpl();
AnnotationMatchingPointcut pointcut = AnnotationMatchingPointcut.forMethodAnnotation(Advice.class);
Advisor advisor = new DefaultPointcutAdvisor(pointcut,
(MethodBeforeAdvice) (method, args1, target) -> System.out.printf("被拦截的方法为:%s,方法参数为:%s\n", method, Arrays.toString(args1))
);
ProxyFactory proxyFactory = new ProxyFactory();
proxyFactory.setTarget(echoService);
proxyFactory.addAdvisor(advisor);
EchoService proxy = (EchoService) proxyFactory.getProxy();
// 拦截并发生前置 Advice
proxy.echo("文海");
proxy.echo2("文海");
}
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface Advice {
}
}
ControlFlowPointcut
用于简单的 cflow(CallTree 调用链) 风格 Pointcut。如果要拦截某个类调用某个类的某个方法或者类里面的任何方法是非常有用的。
示例
拦截 Client 类调用 EchoService 的 echo 方法。
public class Client {
public void call(EchoService echoService) {
echoService.echo("文海");
}
}
public class ControlFlowDemo {
public static void main(String[] args) {
EchoService echoService = new DefaultEchoServiceImpl();
Pointcut pc = new ControlFlowPointcut(Client.class, "call");
Advisor advisor = new DefaultPointcutAdvisor(pc,
(MethodBeforeAdvice) (method, args1, target) -> System.out.printf("被拦截的方法为:%s,方法参数为:%s\n", method, Arrays.toString(args1))
);
ProxyFactory pf = new ProxyFactory();
pf.setTarget(echoService);
pf.addAdvisor(advisor);
EchoService proxy = (EchoService) pf.getProxy();
new Client().call(proxy);
}
}
matches 方法解密
@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;
}
总结(一张图搞定)