学好路更宽,钱多少加班。 ——小马哥

版本修订

  • 2021.5.19:去除目录
  • 2021.5.21:引用 Spring 官方 Pointcut 概念,修改 Pointcut 功能表述

简介

大家好,我是小马哥成千上万粉丝中的一员!2019年8月有幸在叩丁狼教育举办的猿圈活动中知道有这么一位大咖,从此结下了不解之缘!此系列在多次学习极客时间《小马哥讲Spring AOP 编程思想》基础上形成的个人一些总结。希望能帮助各位小伙伴, 祝小伙伴早日学有所成。

Pointcut 功能

  1. Pointcut 接口是中心接口,用于将 advice 定向到特定的类和方法。
  2. pointcut 可以重用,而不依赖于 advice 类型。可以使用相同的 pointcut 来针对不同的 advice。
  3. 将 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

  1. 限制 Pointcut 匹配或对一组给定目标类的 Introduction 的过滤器。作为 Pointcut 一部分使用也可以作为一个 IntroductionAdvisor 的整个目标,该接口的具体实现通常应该提供 Object.equals(Object) 和Object. hashcode() 的适当实现,以便允许过滤器在缓存场景中使用 —— 例如,在CGLIB生成的代理中。
  2. 对类进行过滤所以只提供了:boolean matches(Class<?> clazz) 方法

MethodMatcher

  1. 用于检查目标方法是否有资格获得 advice。
  2. MathodMatcher 可以静态地或在运行时(动态地)计算。静态匹配涉及方法和(可能)方法属性。动态匹配还使特定调用的参数可用,以及将先前的 advice 应用到 joinpoint 的任何效果。
  3. 如果实现 isRuntime() 方法返回 false,则可以静态执行求值,并且该方法的所有调用的结果都是相同的,无论它们的参数是什么。这意味着如果 isRuntime()方法返回 false,则 3 参数 matches(Method method, Class<?> targetClass, Object... args) 方法将永远不会被调用。
  4. 如果一个实现从它的 2 参数 matches(Method method, Class<?> targetClass)返回 true和它的 isRuntime() 方法返回 true, 3 参数 matches(Method method, Class<?> targetClass, Object... args) 方法将在相关 advice 的每次潜在执行之前被立即调用,以决定该 advice 是否应该运行。之前的所有 adivce (比如拦截器链中的早期拦截器)都将运行,因此它们在参数或 ThreadLocal 状态中产生的任何状态更改都将在计算时可用。
  5. 该接口的具体实现通常应该提供 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 {@Overridepublic boolean matches(@NonNull Method method,@NonNull Class<?> targetClass) {return "echo".equals(method.getName());}@Override@NonNullpublic ClassFilter getClassFilter() {return EchoService.class::isAssignableFrom;}
}public interface EchoService {String echo(String message);
}
public class DefaultEchoServiceImpl implements EchoService {@Overridepublic String echo(String message) {return message;}
}public interface EchoService2 {String echo(String message);
}
public class DefaultEcho2ServiceImpl implements EchoService2 {@Overridepublic 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();// 会被拦截并执行 MethodBeforeAdviceproxyOne.echo("文海");proxyTwo.echo("文海");}
}

关于 adivce 详情请参阅 跟着小马哥学系列之 Spring AOP( Advice 组件详解)

DynamicMethodMatcherPointcut

DynamicMethodMatcher + Pointcut 2合一。 DynamicMethodMatcher:动态方法匹配器的方便抽象超类,它关心运行时的参数。由于关心运行时参数所以 isRuntime() 方法返回 true,将要覆写3个参数的 matches 方法。ClassFilter:默认值是 ClassFilter.TRUE(匹配所有类),可以通过覆写 getClassFilter 方法设置 ClassFilter

示例

如何实现对 EchoService 接口的 echo 方法参数为 文海大叔时才进行拦截 ?


public class SimpleDynamicPointcut extends DynamicMethodMatcherPointcut {@Overridepublic 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;}@Overridepublic boolean matches(Method method, Class<?> targetClass) {System.out.println("Static check method: " + method.getName());return "echo".equals(method.getName());}@Override@NonNullpublic ClassFilter getClassFilter() {return EchoService.class::isAssignableFrom;}
}public interface EchoService {String echo(String message);
}
public class DefaultEchoServiceImpl implements EchoService {@Overridepublic String echo(String message) {return message;}
}public interface EchoService2 {String echo(String message);
}
public class DefaultEcho2ServiceImpl implements EchoService2 {@Overridepublic 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() {@Overridepublic boolean matches(Method method, Class<?> targetClass) {return "echo".equals(method.getName());}@Overridepublic boolean isRuntime() {return false;}@Overridepublic 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 {@Overridepublic 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@Overridepublic 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();// 拦截并发生前置 Adviceproxy.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;
}

总结(一张图搞定)

跟着小马哥学系列之 Spring AOP(Pointcut 组件详解)相关推荐

  1. 跟着小马哥学系列之 Spring AOP(Advisor 详解)

    学好路更宽,钱多少加班. --小马哥 简介 大家好,我是小马哥成千上万粉丝中的一员!2019年8月有幸在叩丁狼教育举办的猿圈活动中知道有这么一位大咖,从此结下了不解之缘!此系列在多次学习极客时间< ...

  2. 跟着小马哥学系列之 Spring AOP(AbstractAutoProxyCreator 详解)

    学成路更宽,吊打面试官. --小马哥 版本修订 2021.5.19:去除目录 简介 大家好,我是小马哥成千上万粉丝中的一员!2019年8月有幸在叩丁狼教育举办的猿圈活动中知道有这么一位大咖,从此结下了 ...

  3. 跟着小马哥学系列之 Spring AOP(基于 XML 定义 Advice 源码解析)

    学好路更宽,钱多少加班. --小马哥 简介 大家好,我是小马哥成千上万粉丝中的一员!2019年8月有幸在叩丁狼教育举办的猿圈活动中知道有这么一位大咖,从此结下了不解之缘!此系列在多次学习极客时间< ...

  4. 跟着小马哥学系列之 Spring IoC(源码篇:Bean 生命周期)

    跟着小马哥学系列之 Spring IoC(源码篇:Bean 生命周期) 简介 Bean 元信息来源 Bean 元信息解析成 BeanDefinition 并注册 BeanDefinition 转变成 ...

  5. 跟着小马哥学系列之 Spring IoC(源码篇:@Import)

    跟着小马哥学系列之 Spring IoC(源码篇:@Import) 简介 @ Import 简介 元信息 元注解 属性 @Import 注解 value 属性取值范围 ImportSelector I ...

  6. 跟着小马哥学系列之 Spring IoC(进阶篇:Environment)

    学成路更宽,吊打面试官. --小马哥 简介 大家好,我是小马哥成千上万粉丝中的一员!2019年8月有幸在叩丁狼教育举办的猿圈活动中知道有这么一位大咖,从此结下了不解之缘!此系列在多次学习极客时间< ...

  7. 跟着小马哥学系列之 Spring IoC(进阶篇:类型转换)

    学成路更宽,吊打面试官. --小马哥 简介 大家好,我是小马哥成千上万粉丝中的一员!2019年8月有幸在叩丁狼教育举办的猿圈活动中知道有这么一位大咖,从此结下了不解之缘!此系列在多次学习极客时间< ...

  8. Spring AOP 与代理详解

    SpringBoot 系列教程 - 源码地址:https://github.com/laolunsi/spring-boot-examples 大家知道我现在还是一个 CRUD 崽,平时用 AOP 也 ...

  9. Spring AOP 功能使用详解

    前言 AOP 既熟悉又陌生,了解过 Spring 人的都知道 AOP 的概念,即面向切面编程,可以用来管理一些和主业务无关的周边业务,如日志记录,事务管理等:陌生是因为在工作中基本没有使用过,AOP ...

最新文章

  1. java程序课程总结_java课程总结
  2. activemq 持久订阅_ActiveMQ群集,持久订阅者和虚拟主题可助您一臂之力
  3. python 机器学习资料
  4. 编程寓言:两位新手正讨论常用快捷键,路边乞丐直接说出答案!
  5. MYSQL 数据库怎样快速的复制表以及表中的数据
  6. linux批量筛选序列变异位点,使用bedtools获取指定坐标上下游的序列
  7. Tomcat下JSP环境的配置
  8. 使用腾讯云短信SDK发送验证码
  9. 7-10 365次方 (10 分)
  10. (写给小白)企业代码提交和发布流程
  11. 没有服务器认证消息,关于《跑跑卡丁车》没有服务器认证消息的问题,怎么解决?...
  12. 3dsMax---期末设计[CC‘s 游乐园’]
  13. Xilinx的FPGA手册中关于如何Booting RFSoCsZynq
  14. Druid Monitor监控
  15. 开机后黑屏看不到桌面_开机后黑屏看不到桌面怎么解决
  16. iWatch应用开发-oc篇
  17. android 蘑菇街组件化,蘑菇街 App 的组件化之路
  18. java中elements_Java Element.elements方法代碼示例
  19. 一起来看流星雨剧情简介/剧情介绍/剧情分集介绍第二十二集
  20. TN,FN,TP,FP等用过的评价指标计算公式

热门文章

  1. 自制新型文字密码,聊天记录的画风都不一样了,再也不怕被女朋友偷窥手机了!
  2. 文件服务器属于固定资产吗,云服务器属于固定资产吗
  3. 华为交换机路由器consle忘记密码该怎么办?
  4. 红米k60和k50至尊版参数对比 Redmi k60和k50至尊版哪个好
  5. MT6737/MT6737T/MT6737M处理器参数差异分析资料
  6. pubmedy安装不聊了_安装这个,Pubmed就彻底封神了!
  7. ChatGPT连接企微
  8. 9块9的U盘,你敢买吗?
  9. JAVA最佳学习方法
  10. 小的以及大的Typhon IDE