当前位置: 首页 > news >正文

MiniSpring框架学习笔记-Pointcut:如何批量匹配代理方法?

MiniSpring框架学习笔记-Pointcut:如何批量匹配代理方法?

  • 19. Pointcut:如何批量匹配代理方法?
    • 一、先看最终效果
    • 二、先分清 Advice、Pointcut 和 Advisor
    • 三、定义 Pointcut 和 MethodMatcher
    • 四、按方法名实现切点
    • 五、用 PointcutAdvisor 组合切点和通知
    • 六、ProxyFactoryBean 要能识别 Advisor
    • 七、动态代理根据切点决定是否增强
    • 八、验证匹配结果
    • 小结

教程:https://github.com/YaleGuo/minis
极客时间:手把手带你写一个 MiniSpring

19. Pointcut:如何批量匹配代理方法?

上一节已经把增强逻辑从动态代理中抽成了MethodInterceptor,不过,哪些方法需要增强仍然写死在代理类里:

if("doAction".equals(method.getName())&&interceptor!=null){Class<?>targetClass=this.target==null?null:this.target.getClass();MethodInvocationinvocation=newReflectiveMethodInvocation(proxy,this.target,method,args,targetClass);returninterceptor.invoke(invocation);}

这样只能增强doAction()。如果又增加了doSomething()doSave()等方法,就要继续修改代理类。

这显然不是一个可扩展的做法。本节要引入Pointcut(切点),把“哪些方法需要增强”也变成独立配置。

先注意一个容易混淆的地方:JDK 生成的代理对象仍然代理接口中的所有方法,Pointcut决定的是“哪个方法调用需要执行增强”,不是“只为哪些方法生成代理”。

一、先看最终效果

项目中的IAction现在有两个业务方法:

packagecom.chenhai.test;publicinterfaceIAction{voiddoAction();voiddoSomething();}

调用方还是照常从容器中获取代理对象:

IActionaction=(IAction)context.getBean("action");action.doAction();action.doSomething();

beans.xml使用mappedName="do*"批量匹配所有以do开头的方法:

<beanid="realaction"class="com.chenhai.test.Action1"/><beanid="beforeAdvice"class="com.chenhai.test.MyBeforeAdvice"/><beanid="advisor"class="com.chenhai.aop.NameMatchMethodPointcutAdvisor"><propertytype="com.chenhai.aop.Advice"name="advice"ref="beforeAdvice"/><propertytype="String"name="mappedName"value="do*"/></bean><beanid="action"class="com.chenhai.aop.ProxyFactoryBean"><propertytype="java.lang.Object"name="target"ref="realaction"/><propertytype="String"name="interceptorName"value="advisor"/></bean>

因此,doAction()doSomething()都会先执行前置通知:

----------my interceptor before method call---------- real action doAction........ ----------my interceptor before method call---------- real action doSomething........

整个判断过程可以先记成下面这条主线:

代理对象收到方法调用 ↓ 从 Advisor 取得 MethodInterceptor ↓ 从 Pointcut 取得 MethodMatcher ↓ 判断当前方法是否匹配 do* ↓ 命中:执行增强,再调用真实方法 未命中:直接调用真实方法

核心理解

在当前 MiniSpring 教学版中,AOP 代理的创建最终落在这一行代码上:

this.singletonInstance=getProxy(createAopProxy());

createAopProxy()先根据配置组装目标对象和Advisor,得到具体的JdkDynamicAopProxygetProxy()再调用 JDK 动态代理 API,生成业务代码最终拿到的代理对象。

哪个对象需要被代理、使用哪段增强逻辑、匹配哪些方法,都写在 XML 中,再由 IoC 容器通过 setter 自动注入。以上面的配置为例:

action(ProxyFactoryBean) ├─ target → realaction(Action1,被代理的真实对象) └─ interceptorName → advisor ↓ NameMatchMethodPointcutAdvisor ├─ advice → beforeAdvice(MyBeforeAdvice,具体增强逻辑) └─ mappedName → do*(方法匹配规则)

所以,ProxyFactoryBean先拿到target=realactioninterceptorName=advisor,再从容器中取得NameMatchMethodPointcutAdvisor。这个Advisor已经由容器注入了beforeAdvicedo*:前者决定“怎么增强”,后者决定“哪些方法需要增强”。框架最后根据这些对象自动生成代理,业务代码不需要手动创建或组装代理对象。

二、先分清 Advice、Pointcut 和 Advisor

切点可以理解成一个筛选条件,有点像 SQL 中的where:它只负责找出需要增强的方法,不负责执行增强。

本章几个核心对象的职责如下:

对象负责什么
Advice/MethodInterceptor做什么增强
Pointcut在哪里增强
MethodMatcher判断当前方法是否命中
PointcutAdvisor把增强逻辑和切点组合起来

它们之间的关系是:

PointcutAdvisor ├─ MethodInterceptor:怎么增强 └─ Pointcut └─ MethodMatcher:当前方法是否匹配

这样一来,代理只负责组织调用,不再关心具体匹配规则是方法名、注解还是其他条件。

三、定义 Pointcut 和 MethodMatcher

先定义切点接口。当前教学版只匹配方法,所以Pointcut只需要提供MethodMatcher

packagecom.chenhai.aop;/** * 切点,表示“在哪里增强”。 */publicinterfacePointcut{MethodMatchergetMethodMatcher();}

MethodMatcher专门回答“这个方法要不要增强”:

packagecom.chenhai.aop;importjava.lang.reflect.Method;/** * 判断某个方法是否需要被 AOP 增强。 */publicinterfaceMethodMatcher{booleanmatches(Methodmethod,Class<?>targetClass);}

这里同时传入了methodtargetClass,是为了让匹配器既能检查方法,也能检查真实目标类。当前按名称匹配的实现只使用methodtargetClass暂时没有参与判断。

完整的 Spring 切点还可以同时筛选目标类和方法。本章只保留方法匹配,是为了先把最小调用链跑通。

最后扩展Advisor,让它同时提供增强和切点:

packagecom.chenhai.aop;/** * 带切点的 Advisor。 */publicinterfacePointcutAdvisorextendsAdvisor{PointcutgetPointcut();}

普通Advisor只回答“怎么增强”,PointcutAdvisor还可以回答“在哪里增强”。

四、按方法名实现切点

接下来实现NameMatchMethodPointcut。它既是Pointcut,也直接承担MethodMatcher的匹配工作:

packagecom.chenhai.aop;importcom.chenhai.util.PatternMatchUtils;importjava.lang.reflect.Method;/** * 按方法名匹配的切点。 * * mappedName 可以是精确名称,也可以包含简单通配符 *。 */publicclassNameMatchMethodPointcutimplementsMethodMatcher,Pointcut{privateStringmappedName="";publicvoidsetMappedName(StringmappedName){// 避免后续匹配时出现空指针。this.mappedName=mappedName==null?"":mappedName;}publicStringgetMappedName(){returnthis.mappedName;}@Overridepublicbooleanmatches(Methodmethod,Class<?>targetClass){if(method==null){returnfalse;}StringmethodName=method.getName();returnthis.mappedName.equals(methodName)||isMatch(methodName,this.mappedName);}protectedbooleanisMatch(StringmethodName,StringmappedName){returnPatternMatchUtils.simpleMatch(mappedName,methodName);}@OverridepublicMethodMatchergetMethodMatcher(){// 当前对象自己就能匹配方法,所以直接返回 this。returnthis;}}

支持的匹配方式包括:

mappedName可以匹配
doAction精确匹配doAction
do*doActiondoSomething
*Action所有以Action结尾的方法
*Action*所有名称中包含Action的方法
do*Actiondo开头、以Action结尾的方法

这里的*是简单通配符,不是正则表达式,也不支持?等其他通配符。

真正的通配符算法放在工具类中,切点只保留 AOP 相关职责:

publicstaticbooleansimpleMatch(Stringpattern,Stringstr){if(pattern==null||str==null){returnfalse;}intfirstIndex=pattern.indexOf('*');if(firstIndex==-1){returnpattern.equals(str);}if(firstIndex==0){if(pattern.length()==1){returntrue;}intnextIndex=pattern.indexOf('*',1);if(nextIndex==-1){returnstr.endsWith(pattern.substring(1));}Stringpart=pattern.substring(1,nextIndex);if(part.isEmpty()){returnsimpleMatch(pattern.substring(nextIndex),str);}intpartIndex=str.indexOf(part);while(partIndex!=-1){StringremainingPattern=pattern.substring(nextIndex);StringremainingString=str.substring(partIndex+part.length());if(simpleMatch(remainingPattern,remainingString)){returntrue;}partIndex=str.indexOf(part,partIndex+1);}returnfalse;}returnstr.length()>=firstIndex&&pattern.substring(0,firstIndex).equals(str.substring(0,firstIndex))&&simpleMatch(pattern.substring(firstIndex),str.substring(firstIndex));}

这段算法的核心思路是:*前面的内容必须精确匹配,遇到*后再递归匹配剩余部分。匹配算法本身不是本章重点,知道NameMatchMethodPointcut把它当成工具使用即可。

五、用 PointcutAdvisor 组合切点和通知

现在还差一个对象,把AdviceNameMatchMethodPointcut放在一起:

packagecom.chenhai.aop;/** * 使用方法名切点的 Advisor。 */publicclassNameMatchMethodPointcutAdvisorimplementsPointcutAdvisor{privateAdviceadvice;privateMethodInterceptormethodInterceptor;privateStringmappedName;privatefinalNameMatchMethodPointcutpointcut=newNameMatchMethodPointcut();publicNameMatchMethodPointcutAdvisor(){}publicNameMatchMethodPointcutAdvisor(Adviceadvice){setAdvice(advice);}publicvoidsetAdvice(Adviceadvice){this.advice=advice;// 统一适配成代理能够执行的 MethodInterceptor。setMethodInterceptor(adaptAdvice(advice));}publicAdvicegetAdvice(){returnthis.advice;}@OverridepublicMethodInterceptorgetMethodInterceptor(){returnthis.methodInterceptor;}@OverridepublicvoidsetMethodInterceptor(MethodInterceptormethodInterceptor){this.methodInterceptor=methodInterceptor;}@OverridepublicPointcutgetPointcut(){returnthis.pointcut;}publicvoidsetMappedName(StringmappedName){this.mappedName=mappedName;// XML 注入 mappedName 时,同时更新内部切点。this.pointcut.setMappedName(mappedName);}publicStringgetMappedName(){returnthis.mappedName;}privateMethodInterceptoradaptAdvice(Adviceadvice){if(adviceinstanceofMethodBeforeAdvice){returnnewMethodBeforeAdviceInterceptor((MethodBeforeAdvice)advice);}if(adviceinstanceofAfterReturningAdvice){returnnewAfterReturningAdviceInterceptor((AfterReturningAdvice)advice);}if(adviceinstanceofMethodInterceptor){return(MethodInterceptor)advice;}thrownewIllegalArgumentException("Unsupported advice type: "+advice);}}

XML 注入会触发两个关键 setter:

  1. setAdvice()把前置、后置通知适配成统一的MethodInterceptor
  2. setMappedName()do*交给内部的NameMatchMethodPointcut

最终,advisor同时持有了“怎么增强”和“增强哪些方法”。

六、ProxyFactoryBean 要能识别 Advisor

上一节的interceptorName只能指向一个普通Advice。现在 XML 中配置的是:

<propertytype="String"name="interceptorName"value="advisor"/>

虽然属性名还叫interceptorName,但它现在既可以指向Advice,也可以指向完整的AdvisorProxyFactoryBean需要先识别取出来的对象:

privatesynchronizedvoidinitializeAdvisor(){if(this.advisor!=null||this.interceptorName==null||this.interceptorName.trim().isEmpty()){return;}if(this.beanFactory==null){thrownewIllegalStateException("BeanFactory is required to resolve interceptorName");}ObjectinterceptorOrAdvisor;try{interceptorOrAdvisor=this.beanFactory.getBean(this.interceptorName);}catch(BeansExceptione){thrownewIllegalStateException("Resolve interceptor failed: "+this.interceptorName,e);}this.advisor=adaptAdvisor(interceptorOrAdvisor);}privateAdvisoradaptAdvisor(ObjectinterceptorOrAdvisor){if(interceptorOrAdvisorinstanceofAdvisor){// Advisor 已经包含通知;PointcutAdvisor 还会包含切点。return(Advisor)interceptorOrAdvisor;}if(!(interceptorOrAdvisorinstanceofAdvice)){thrownewIllegalArgumentException("Unsupported interceptor/advisor type: "+interceptorOrAdvisor);}// 兼容上一章:单独的 Advice 包成不带切点的 DefaultAdvisor。DefaultAdvisordefaultAdvisor=newDefaultAdvisor();defaultAdvisor.setMethodInterceptor(adaptAdvice((Advice)interceptorOrAdvisor));returndefaultAdvisor;}

这里保留了对上一章配置方式的兼容:

  • 配置PointcutAdvisor:按切点选择方法;
  • 配置普通Advice:包装成DefaultAdvisor,对所有方法执行增强。

七、动态代理根据切点决定是否增强

最后回到JdkDynamicAopProxy.invoke()。原来写死的"doAction"被替换成了shouldApply(method)

@OverridepublicObjectinvoke(Objectproxy,Methodmethod,Object[]args)throwsThrowable{MethodInterceptorinterceptor=getMethodInterceptor();if(interceptor!=null&&shouldApply(method)){Class<?>targetClass=this.target==null?null:this.target.getClass();MethodInvocationinvocation=newReflectiveMethodInvocation(proxy,this.target,method,args,targetClass);returninterceptor.invoke(invocation);}// 没有命中切点的方法仍然正常调用真实对象。returninvokeTarget(method,args);}privatebooleanshouldApply(Methodmethod){if(this.advisor==null){returnfalse;}if(!(this.advisorinstanceofPointcutAdvisor)){// 不带切点的普通 Advisor 默认匹配所有方法。returntrue;}Class<?>targetClass=this.target==null?null:this.target.getClass();Pointcutpointcut=((PointcutAdvisor)this.advisor).getPointcut();returnpointcut!=null&&pointcut.getMethodMatcher()!=null&&pointcut.getMethodMatcher().matches(method,targetClass);}

判断逻辑分为三种情况:

  1. 没有Advisor,不执行增强;
  2. 是普通Advisor,默认对所有方法执行增强;
  3. PointcutAdvisor,只有MethodMatcher匹配成功才执行增强。

无论有没有命中切点,真实方法都要执行。切点只控制增强是否介入,不能让未命中的方法直接返回null

八、验证匹配结果

项目中的测试直接验证了do*的匹配结果:

NameMatchMethodPointcutpointcut=newNameMatchMethodPointcut();pointcut.setMappedName("do*");assertTrue(pointcut.matches(IAction.class.getMethod("doAction"),Action1.class));assertTrue(pointcut.matches(IAction.class.getMethod("doSomething"),Action1.class));assertFalse(pointcut.matches(Object.class.getMethod("toString"),Action1.class));

它还会通过容器取得代理对象,验证两个do方法都先执行前置通知,再执行真实方法。这样既检查了匹配器,也检查了 XML、Advisor和动态代理组成的完整调用链。

当前版本仍然是一个教学实现:

  • 一个NameMatchMethodPointcut只保存一个mappedName
  • 只按方法名匹配,还不支持类、注解和参数类型等条件;
  • 一个代理仍然只配置一个Advisor,还没有多拦截器链。

小结

这一节把硬编码的方法名判断抽成了独立切点:

Advice:做什么增强 Pointcut:在哪里增强 Advisor:把二者组合起来 JdkDynamicAopProxy:匹配成功后执行增强

以后再增加匹配规则时,只需要实现新的PointcutMethodMatcher,不必继续修改具体的动态代理逻辑。

AOP的核心要点都围绕this.singletonInstance = getProxy(createAopProxy())这行代码展开,最终就是为了让框架自动生成代理对象。然后注意哪个类需要被代理,具体怎么代理增强(用哪个代理增强类)都写在配置文件里,通过IOC容器自动set注入依赖关系。先拿到需要被代理的对象,target,interceptorName–》com.chenhai.aop.NameMatchMethodPointcutAdvisor,然后通过set注入Advice(具体增强类)、mappedName(匹配规则)

http://www.jsqmd.com/news/1284279/

相关文章:

  • SmartStor如何备份和恢复操作系统文件
  • 区块链服务商排名调整,场景深度绑定成关键变量
  • AI 写歌 App 推荐:2026中文商用写歌工具横向对比
  • (2026最新)苏州本地人必选的靠谱漏水检测维修推荐:正规防水补漏防水-卫生间/厨房/屋顶/阳台/外墙渗漏水精准测漏,本地人的信赖之选 - 安佳防水
  • Python作业实战:函数封装与文件操作技巧
  • 【泄底】Spring Has Come - 梓崎优
  • DIY焊接健身一体架:硬件工程师的硬核桌面改造方案
  • 一文吃透 GB/T 4208-2017 IP 防护标准|代码释义、测试流程、国标与 IEC 差异、工程避坑指南
  • SLO2016与PIC18F97J60在工业物联网中的组合应用
  • C++组合关系:从类包含到现代软件设计实践
  • Postman实战:从零构建GraphQL API测试全流程指南
  • InnoDB 行级锁与意向锁
  • LTE Cat 1bis与PIC18微控制器在美洲物联网中的应用
  • 3D打印与激光切割融合制造:从设计到实战的混合制造指南
  • (2026最新)自贡本地人必选的靠谱漏水检测维修推荐:正规防水补漏防水-卫生间/厨房/屋顶/阳台/外墙渗漏水精准测漏,本地人的信赖之选 - 安佳防水
  • (2026最新)芜湖本地漏水检测维修公司靠谱推荐:正规防水补漏上门维修-墙面/屋顶/外墙/暗管漏水检测精准定位 - 即刻修防水
  • Python基础语法入门:从环境配置到核心要素
  • LeetCode 207. 课程表
  • 常熟打井找哪家?2026年最新攻略:苏州市瑞溪泉水利工程有限公司凭实力说话 - 瑞溪泉水利
  • Zotero插件市场:一站式插件管理与安装体验的终极指南
  • 凡科杰建云的小程序、商城、门店和建站数据互通吗?
  • 基于行空板与RC车打造可编程智能小车:从硬件连接到Web遥控
  • AIGC检测技术:守护学术原创性的AI解决方案
  • 2026年多功能万年历应用推荐:黄历道历佛历、亲友提醒和个人历怎么选?附天乙日历App完整测评
  • 2026年苏州专业处理个人欠款纠纷律师大盘点 - 品牌排行榜
  • 消费级外骨骼三条技术路线:适老、户外与轻量化如何选择?
  • K8s资源调度与HPA自动扩缩容!AI流量波峰波谷自动适配,实现Agent服务智能弹性伸缩、降本增效
  • 行空板M10集成百度与讯飞双语音引擎:嵌入式AI语音交互实践
  • 2026年,西宁地区如何选择可靠的竹床漏粪板合作品牌? - 装修教育财税推荐2026
  • Unity字体优化:基于TextMeshPro的自动化字符集扫描与精简方案