尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
MiniSpring框架学习笔记-Pointcut:如何批量匹配代理方法?
MiniSpring框架学习笔记-Pointcut如何批量匹配代理方法19. Pointcut如何批量匹配代理方法一、先看最终效果二、先分清 Advice、Pointcut 和 Advisor三、定义 Pointcut 和 MethodMatcher四、按方法名实现切点五、用 PointcutAdvisor 组合切点和通知六、ProxyFactoryBean 要能识别 Advisor七、动态代理根据切点决定是否增强八、验证匹配结果小结教程https://github.com/YaleGuo/minis极客时间手把手带你写一个 MiniSpring19. Pointcut如何批量匹配代理方法上一节已经把增强逻辑从动态代理中抽成了MethodInterceptor不过哪些方法需要增强仍然写死在代理类里if(doAction.equals(method.getName())interceptor!null){Class?targetClassthis.targetnull?null:this.target.getClass();MethodInvocationinvocationnewReflectiveMethodInvocation(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使用mappedNamedo*批量匹配所有以do开头的方法beanidrealactionclasscom.chenhai.test.Action1/beanidbeforeAdviceclasscom.chenhai.test.MyBeforeAdvice/beanidadvisorclasscom.chenhai.aop.NameMatchMethodPointcutAdvisorpropertytypecom.chenhai.aop.AdvicenameadvicerefbeforeAdvice/propertytypeStringnamemappedNamevaluedo*//beanbeanidactionclasscom.chenhai.aop.ProxyFactoryBeanpropertytypejava.lang.Objectnametargetrefrealaction/propertytypeStringnameinterceptorNamevalueadvisor//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.singletonInstancegetProxy(createAopProxy());createAopProxy()先根据配置组装目标对象和Advisor得到具体的JdkDynamicAopProxygetProxy()再调用 JDK 动态代理 API生成业务代码最终拿到的代理对象。哪个对象需要被代理、使用哪段增强逻辑、匹配哪些方法都写在 XML 中再由 IoC 容器通过 setter 自动注入。以上面的配置为例actionProxyFactoryBean ├─ target → realactionAction1被代理的真实对象 └─ interceptorName → advisor ↓ NameMatchMethodPointcutAdvisor ├─ advice → beforeAdviceMyBeforeAdvice具体增强逻辑 └─ mappedName → do*方法匹配规则所以ProxyFactoryBean先拿到targetrealaction和interceptorNameadvisor再从容器中取得NameMatchMethodPointcutAdvisor。这个Advisor已经由容器注入了beforeAdvice和do*前者决定“怎么增强”后者决定“哪些方法需要增强”。框架最后根据这些对象自动生成代理业务代码不需要手动创建或组装代理对象。二、先分清 Advice、Pointcut 和 Advisor切点可以理解成一个筛选条件有点像 SQL 中的where它只负责找出需要增强的方法不负责执行增强。本章几个核心对象的职责如下对象负责什么Advice/MethodInterceptor做什么增强Pointcut在哪里增强MethodMatcher判断当前方法是否命中PointcutAdvisor把增强逻辑和切点组合起来它们之间的关系是PointcutAdvisor ├─ MethodInterceptor怎么增强 └─ Pointcut └─ MethodMatcher当前方法是否匹配这样一来代理只负责组织调用不再关心具体匹配规则是方法名、注解还是其他条件。三、定义 Pointcut 和 MethodMatcher先定义切点接口。当前教学版只匹配方法所以Pointcut只需要提供MethodMatcherpackagecom.chenhai.aop;/** * 切点表示“在哪里增强”。 */publicinterfacePointcut{MethodMatchergetMethodMatcher();}MethodMatcher专门回答“这个方法要不要增强”packagecom.chenhai.aop;importjava.lang.reflect.Method;/** * 判断某个方法是否需要被 AOP 增强。 */publicinterfaceMethodMatcher{booleanmatches(Methodmethod,Class?targetClass);}这里同时传入了method和targetClass是为了让匹配器既能检查方法也能检查真实目标类。当前按名称匹配的实现只使用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.mappedNamemappedNamenull?:mappedName;}publicStringgetMappedName(){returnthis.mappedName;}Overridepublicbooleanmatches(Methodmethod,Class?targetClass){if(methodnull){returnfalse;}StringmethodNamemethod.getName();returnthis.mappedName.equals(methodName)||isMatch(methodName,this.mappedName);}protectedbooleanisMatch(StringmethodName,StringmappedName){returnPatternMatchUtils.simpleMatch(mappedName,methodName);}OverridepublicMethodMatchergetMethodMatcher(){// 当前对象自己就能匹配方法所以直接返回 this。returnthis;}}支持的匹配方式包括mappedName可以匹配doAction精确匹配doActiondo*doAction、doSomething*Action所有以Action结尾的方法*Action*所有名称中包含Action的方法do*Action以do开头、以Action结尾的方法这里的*是简单通配符不是正则表达式也不支持?等其他通配符。真正的通配符算法放在工具类中切点只保留 AOP 相关职责publicstaticbooleansimpleMatch(Stringpattern,Stringstr){if(patternnull||strnull){returnfalse;}intfirstIndexpattern.indexOf(*);if(firstIndex-1){returnpattern.equals(str);}if(firstIndex0){if(pattern.length()1){returntrue;}intnextIndexpattern.indexOf(*,1);if(nextIndex-1){returnstr.endsWith(pattern.substring(1));}Stringpartpattern.substring(1,nextIndex);if(part.isEmpty()){returnsimpleMatch(pattern.substring(nextIndex),str);}intpartIndexstr.indexOf(part);while(partIndex!-1){StringremainingPatternpattern.substring(nextIndex);StringremainingStringstr.substring(partIndexpart.length());if(simpleMatch(remainingPattern,remainingString)){returntrue;}partIndexstr.indexOf(part,partIndex1);}returnfalse;}returnstr.length()firstIndexpattern.substring(0,firstIndex).equals(str.substring(0,firstIndex))simpleMatch(pattern.substring(firstIndex),str.substring(firstIndex));}这段算法的核心思路是*前面的内容必须精确匹配遇到*后再递归匹配剩余部分。匹配算法本身不是本章重点知道NameMatchMethodPointcut把它当成工具使用即可。五、用 PointcutAdvisor 组合切点和通知现在还差一个对象把Advice和NameMatchMethodPointcut放在一起packagecom.chenhai.aop;/** * 使用方法名切点的 Advisor。 */publicclassNameMatchMethodPointcutAdvisorimplementsPointcutAdvisor{privateAdviceadvice;privateMethodInterceptormethodInterceptor;privateStringmappedName;privatefinalNameMatchMethodPointcutpointcutnewNameMatchMethodPointcut();publicNameMatchMethodPointcutAdvisor(){}publicNameMatchMethodPointcutAdvisor(Adviceadvice){setAdvice(advice);}publicvoidsetAdvice(Adviceadvice){this.adviceadvice;// 统一适配成代理能够执行的 MethodInterceptor。setMethodInterceptor(adaptAdvice(advice));}publicAdvicegetAdvice(){returnthis.advice;}OverridepublicMethodInterceptorgetMethodInterceptor(){returnthis.methodInterceptor;}OverridepublicvoidsetMethodInterceptor(MethodInterceptormethodInterceptor){this.methodInterceptormethodInterceptor;}OverridepublicPointcutgetPointcut(){returnthis.pointcut;}publicvoidsetMappedName(StringmappedName){this.mappedNamemappedName;// 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 注入会触发两个关键 settersetAdvice()把前置、后置通知适配成统一的MethodInterceptorsetMappedName()把do*交给内部的NameMatchMethodPointcut。最终advisor同时持有了“怎么增强”和“增强哪些方法”。六、ProxyFactoryBean 要能识别 Advisor上一节的interceptorName只能指向一个普通Advice。现在 XML 中配置的是propertytypeStringnameinterceptorNamevalueadvisor/虽然属性名还叫interceptorName但它现在既可以指向Advice也可以指向完整的Advisor。ProxyFactoryBean需要先识别取出来的对象privatesynchronizedvoidinitializeAdvisor(){if(this.advisor!null||this.interceptorNamenull||this.interceptorName.trim().isEmpty()){return;}if(this.beanFactorynull){thrownewIllegalStateException(BeanFactory is required to resolve interceptorName);}ObjectinterceptorOrAdvisor;try{interceptorOrAdvisorthis.beanFactory.getBean(this.interceptorName);}catch(BeansExceptione){thrownewIllegalStateException(Resolve interceptor failed: this.interceptorName,e);}this.advisoradaptAdvisor(interceptorOrAdvisor);}privateAdvisoradaptAdvisor(ObjectinterceptorOrAdvisor){if(interceptorOrAdvisorinstanceofAdvisor){// Advisor 已经包含通知PointcutAdvisor 还会包含切点。return(Advisor)interceptorOrAdvisor;}if(!(interceptorOrAdvisorinstanceofAdvice)){thrownewIllegalArgumentException(Unsupported interceptor/advisor type: interceptorOrAdvisor);}// 兼容上一章单独的 Advice 包成不带切点的 DefaultAdvisor。DefaultAdvisordefaultAdvisornewDefaultAdvisor();defaultAdvisor.setMethodInterceptor(adaptAdvice((Advice)interceptorOrAdvisor));returndefaultAdvisor;}这里保留了对上一章配置方式的兼容配置PointcutAdvisor按切点选择方法配置普通Advice包装成DefaultAdvisor对所有方法执行增强。七、动态代理根据切点决定是否增强最后回到JdkDynamicAopProxy.invoke()。原来写死的doAction被替换成了shouldApply(method)OverridepublicObjectinvoke(Objectproxy,Methodmethod,Object[]args)throwsThrowable{MethodInterceptorinterceptorgetMethodInterceptor();if(interceptor!nullshouldApply(method)){Class?targetClassthis.targetnull?null:this.target.getClass();MethodInvocationinvocationnewReflectiveMethodInvocation(proxy,this.target,method,args,targetClass);returninterceptor.invoke(invocation);}// 没有命中切点的方法仍然正常调用真实对象。returninvokeTarget(method,args);}privatebooleanshouldApply(Methodmethod){if(this.advisornull){returnfalse;}if(!(this.advisorinstanceofPointcutAdvisor)){// 不带切点的普通 Advisor 默认匹配所有方法。returntrue;}Class?targetClassthis.targetnull?null:this.target.getClass();Pointcutpointcut((PointcutAdvisor)this.advisor).getPointcut();returnpointcut!nullpointcut.getMethodMatcher()!nullpointcut.getMethodMatcher().matches(method,targetClass);}判断逻辑分为三种情况没有Advisor不执行增强是普通Advisor默认对所有方法执行增强是PointcutAdvisor只有MethodMatcher匹配成功才执行增强。无论有没有命中切点真实方法都要执行。切点只控制增强是否介入不能让未命中的方法直接返回null。八、验证匹配结果项目中的测试直接验证了do*的匹配结果NameMatchMethodPointcutpointcutnewNameMatchMethodPointcut();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匹配成功后执行增强以后再增加匹配规则时只需要实现新的Pointcut或MethodMatcher不必继续修改具体的动态代理逻辑。AOP的核心要点都围绕this.singletonInstance getProxy(createAopProxy())这行代码展开最终就是为了让框架自动生成代理对象。然后注意哪个类需要被代理具体怎么代理增强用哪个代理增强类都写在配置文件里通过IOC容器自动set注入依赖关系。先拿到需要被代理的对象targetinterceptorName–》com.chenhai.aop.NameMatchMethodPointcutAdvisor然后通过set注入Advice具体增强类、mappedName匹配规则
RELATED

相关推荐

AI 时代,零售商超如何用多模态数据“看见“每一排货架?

AI 时代,零售商超如何用多模态数据“看见“每一排货架?

中国连锁零售行业正站在一个关键转折点。根据中国连锁经营协会(CCFA)与毕马威联合发布的《2026年中国便利店发展报告》[1],2025年全国便利店终端网点数已达33.8万家,行业销售额触及4795亿元。另据CCFA统计数据,某头部连…

📅 2026/9/13 5:19:37
LeetCode 207. 课程表

LeetCode 207. 课程表

题目描述这个学期需要选修 numCourses 门课程,课程编号为 0 到 numCourses - 1。数组 prerequisites 表示课程之间的先修关系,其中 prerequisites[i] [ai, bi] 表示:如果要学习课程 ai,必须先学习课程 bi。例如:[0, 1…

📅 2026/9/13 6:50:49
浏览器又没网了?微信正常但网页打不开,我的修复踩坑记录

浏览器又没网了?微信正常但网页打不开,我的修复踩坑记录

🎉🎉🎉耶耶耶,今天终于解决了这个困扰我很久的问题!必须记录下来,帮到下一个被这个 bug 折磨的人。💻适用场景:右下角网络图标明明显示“已连接”,微信、QQ 都能正常聊天…

📅 2026/9/13 11:32:37
MORE NEWS

更多资讯

📰

MySQL主从复制配置全解析:原理、实操与排错

刚接手一个新项目时,最头疼的往往不是业务代码,而是数据库层面那些“看起来谁都懂、一上手就翻车”的活。MySQL主从配置就是这样——网上教程满地都是,但真按着一步步敲下来,卡在权限、版本、位点、SSL这类问题上的不在少数。这篇…

📰

ESLint配置文件完全指南:env、rules、extends到flat config

你有没有遇到过这种情况:同事交给你一个“能跑”的老项目,你改了一行代码,保存,编辑器瞬间被波浪线淹没。你反复确认这行代码没有语法错误,但 ESLint 就是在报错。然后你打开项目根目录那个.eslintrc.js,盯…

📰

Cursor终端中文乱码怎么办?从编码原理到PowerShell/WSL全场景解决方案

如果你在 Cursor 的终端里看到过–‡这种东西,大概率已经明白「乱码一时爽,排查火葬场」是什么体验。我最近连续处理了好几个项目的输出乱码,从 PowerShell 里 Python 的 print,到 WSL 里编译报错,再到 Git 文件名那一…

📰

MES基础建模全解析:五大业务对象、设计思路与落地实操

做了这么多年的MES实施和产品设计,我越来越确认一个判断:很多项目上线后出现的计划不准、追溯断链、报表对不上,根子大多不在执行层,而是出在基础建模这层地基上。MES系统区别于ERP最核心的一点,就是它必须把车间里的&…

📰

乱码标题背后藏着真需求?一套内容拆解与需求还原实战方法

敲下标题的那一刻,我盯着那串字符愣了几秒——“你好we‘f‘we‘f‘w‘fe”。它看起来像是不小心把键盘当成了打击乐器,又像是在输入法里胡乱打了个滚,就这样带着一种既认真又荒诞的气质出现在了我的工作清单里。说实话,这不是我第…

📰

JavaWeb花店管理系统实战:从Servlet+JSP到完整项目源码

简介:这套面向大学生毕设的JavaWeb花店管理系统,整合Struts2、Spring与Hibernate框架,提供带GUI界面的前后端完整源码与数据库脚本,适合作为课程设计、毕业设计或JavaWeb学习的实战参考。系统已实现前台商品展示、分类搜索、购物车…

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬