尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

《SpringBoot 3:入门与应用实战》第 7 章 AOP 思想与实现 阅读笔记 14

《SpringBoot 3:入门与应用实战》第 7 章 AOP 思想与实现 阅读笔记 14 《SpringBoot 3入门与应用实战》第 7 章 AOP 思想与实现 阅读笔记 147.2 AOP 的基础—动态代理通过 7.1 节的内容从最原始的推演过程中读者可以深刻理解 AOP 思想的底层实现基础就是动态代理。JDK 1.3 原生提供了动态代理机制而Cglib 也有一种动态代理。本节会简单回顾动态代理的内容。7.2.1 JDK 动态代理的使用JDK 动态代理要求被代理的对象所属类必须实现一个及以上的接口创建代理对象时使用 Proxy.newProxyInstance 方法该方法中有3个参数。ClassLoader loader被代理的对象所属类的类加载器。Class?[​] interfaces被代理的对象所属类实现的接口。InvocationHandler h代理的具体代码实现。在这三个参数中前面两个都容易理解最后一个 InvocationHandler 是一个接口它的核心方法 invoke 中也有 3 个参数。Object proxy代理对象的引用代理后的​。Method method代理对象执行的方法。Object[​] args代理对象执行方法的参数列表。具体的代理逻辑在 InvocationHandler 的 invoke 方法中编写可根据方法名称等因素过滤需要增强的代码逻辑。7.2.2 Cglib 动态代理的使用虽然基于 JDK 动态代理的场景不需要额外依赖但是接口实现的限制使得 JDK 动态代理的使用场景受到一定限制好在 Cglib 可以直接使用字节码增强技术同样实现动态代理。要想使用 Cglib必须先引入 Cglib 的 jar 包。使用 Cglib 时有两个小前提被代理的类不能是 final 的Cglib动态代理会创建子类final 类型的 Class 无法继承​被代理的类必须有无参构造方法底层反射创建对象时不能获取构造方法参数​。dependencygroupIdcglib/groupIdartifactIdcglib/artifactIdversion3.1/version/dependencyCglib 动态代理的内容相对较少它只需传入两个元素。Class type被代理的对象所属类的类型。Callback callback增强的代码实现。由于一般情况下是对类中的方法增强因此在传入 Callback 时通常选择这个接口的子接口 MethodInterceptor。另外 MethodInterceptor 中intercept 方法的参数列表与 InvocationHandler 中 invoke 方法的参数列表类似唯独多了一个 MethodProxy它是对参数列表中的 Method 又做了一层封装利用它可以直接执行被代理对象的方法。代码展示了一个简单的 Cglib 动态代理使用方式使用 Enhancer 的 create 方法可以创建代理对象。publicstaticPartnergetPartner(intmoney){Partnerpartnerpartners.remove(0);return(Partner)Enhancer.create(partner.getClass(),newMethodInterceptor(){privateintbudgetmoney;privatebooleanstatusfalse;OverridepublicObjectintercept(Objectproxy,Methodmethod,Object[]args,MethodProxymethodProxy)throwsThrowable{// 如果在付钱时没给够钱则标记budget为异常值if(method.getName().equals(receiveMoney)){intmoney(int)args[0];this.statusmoneybudget;}if(status){returnmethod.invoke(partner,args);}returnnull;}});}7.3 AOP 概述与术语了解了 AOP 思想的由来及动态代理后下面开始了解 AOP 的相关概念和对应的术语理解相关的概念有助于进行后续的实操。7.3.1 AOP 概述在 Spring Framework 的官方文档中有专门的一个章节介绍 Spring 的 AOP。简单抽取文档中的关键要素大致可以得到以下 3 点。(1) AOP 是 OOP 的补充7.1 节中通过介绍一些基于 OOP 的设计模式发现这些设计模式并不能完全有效地解决分散的相同逻辑所造成的重复代码问题。而 AOP 可以将这些重复代码逻辑抽取为一个切面在运行时通过动态代理织入原有的对象依然能实现预期的效果。(2) AOP 关注的是核心切面7.1.6 节中可以看到切面可以简单地理解为分散在不同类中的相同逻辑。AOP 在对指定类中指定方法进行增强时需要直接编写这些增强的逻辑并切入原有的代码中。(3) AOP 也是 Spring IOC 的补充如果没有 AOPIOC 本身也是 Spring 非常强大的特性。不过AOP 可以对 IOC 容器中需要的 Bean 增强原有的功能比如给普通 Service 层代码实现事务控制​。根据上述内容进一步总结AOP 要完成的核心工作依然是程序的解耦。借助 AOP 思想将分散在各个类中方法的重复逻辑抽取为一个切面并在运行时生成代理对象将这些重复逻辑组合进原有对象实际上就完成了原有业务与扩展逻辑之间的解耦。通过这种解耦最大的好处显而易见业务逻辑只需要关注业务逻辑本身扩展逻辑只关心扩展逻辑以及切入业务逻辑的位置即可。最后本节尝试总结一个尽可能表述完整的 AOP 概述。面向切面编程 (Aspect Oriented ProgrammingAOP) 是 OOP 的补充。OOP 关注的核心是对象AOP 的核心是切面 (Aspect)。AOP 可以在不修改功能代码本身的前提下使用动态代理技术对已有代码逻辑进行增强。AOP 面向切面编程是 OOP 面向对象编程的补充。AOP 可以在不修改功能代码本身的前提下使用动态代理技术对已有代码逻辑进行增强。AOP 的好处是程序的解耦。将分散在各个类中方法的重复逻辑抽取为一个切面实现原有业务与扩展逻辑之间的解耦。业务逻辑只需要关注业务逻辑本身扩展逻辑只关心扩展逻辑以及切入业务逻辑的位置。7.3.2 AOP 的演变历史AOP 的发展历程相当有年头在 Spring Framework 还没有出现之前开源界也有一些其他的 AOP 解决方案。1.AOP 理论的提出早在 1990 年有一个名为 Xerox Palo Alto Research Lab (PARC) 的组织就对 OOP 的设计思想进行了分析当时这个组织就已经分析出 OOP在抽取重复逻辑时的局限性于是这个组织的成员费尽心思研究出了一套理论使用它就可以将通用的重复逻辑抽取出来并在合适的时机把这些逻辑再组合进原始的业务类中这就是 AOP 的早期设计思想。随着研究的不断深入AOP 理论和思想也慢慢完善逐渐形成了一套完整的设计思想。2.第一代 AOP 的诞生当 Xerox Palo Alto Research Lab 这个组织在研究 AOP 理论的同时美国东北大学的一个博士生和他的团队也在研究如何解决 OOP 重复逻辑的抽取问题后来这个团队研究并制定出一套 AOP 框架它就是 AspectJ。随后到了 2002 年AspectJ 转移到 Eclipse 开源基金会组织并逐渐成为 Java 开源社区中非常流行的 AOP 框架。AspectJ 被称为第一代 AOP 的代表它采用静态字节码编译的方式使用特殊的编译器将事先写好的通知逻辑织入目标类中这样产生的 .class 字节码文件就已经是带有增强通知的代理类。这种静态 AOP 的最大好处是快因为 .class 文件本身就已经是被增强过的接下来的动作跟普通的字节码没有任何区别至于缺点也很明显每次修改任何增强逻辑时都要重新编译所有要被增强的业务类并重新打包工程这个过程相对麻烦。3.第二代 AOP 的诞生在 AspectJ 成型之后的一段时间开源界又出现了一个动态 AOP第二代 AOP框架称为 AspectWerkz它最初的设计就与 AspectJ 不一样它的出发点就是动态 AOP所以后来在 2005 年 AspectJ 跟 AspectWerkz 达成协议将 AspectWerkz 的内容合并至 AspectJ 中。从那之后 AspectJ 成为独有的、同时支持静态 AOP 和动态 AOP 的强大 AOP 框架。此外JBoss 也推出了它的AOP框架但由于传统的重量级框架和组件已经被时代抛弃我们已经很难接触到它们仅供了解。动态 AOP 的优点是不再需要频繁地编译业务类切面修改完毕后只需要单独重新编译切面类其余部分不太需要变化缺点也就随之而来由于不是在字节码的编译环节完成的通知织入因此需要在类加载时/运行时动态织入增强逻辑这会在一定程度上对程序的运行性能有所影响。4.Spring Framework 与 AspectJAspectJ 已经非常强大Spring Framework 对其产生了兴趣所以后来的 Spring Framework 2.0 版本中直接声明了对 AspectJ 的支持可以使用 AspectJ 的方式定义切面类、声明通知方法等。不过话又说回来如果 Spring Framework 直接把 AspectJ 的核心全部纳入体系内似乎不太现实因为当时的 AspectJ 在开源界已有一席之地​Spring Framework 的创始人在权衡之后做出决定整合 AspectJ 的方式只是使用了它的声明和定义方式具体的底层实现还是采用 Spring Framework 原生底层支持这样做既兼容了主流技术又不会影响 Spring Framework 本身的底层架构设计可谓一举两得。随着后续 Spring Framework 的地位逐渐强大其他 AOP 框架慢慢销声匿迹最终留下的就是 Spring Framework AspectJ 的组合方式。7.3.3 AOP 的基本术语AOP 的重要基础知识就是 AOP 术语。在 Spring Framework 的官方文档中紧接着 AOP 的描述之后就是 Spring Framework 定义的 AOP 相关术语。考虑到部分读者是刚接触 AOP或者以前没有彻底理解 AOP 的这些基本术语所以本节会使用一种不同的方式用尽可能通俗易懂的现实场景举例帮助读者理解 AOP 的基本术语。预设场景如图所示在预设场景中左边的主管视为 “原始对象”​主管提供账户充值、账号解封等业务表示一个 Class 中定义的几个方法中间的业务经理视为 “中间的代理层”​他平时招揽客人并且将客人的需求传达给主管右边的客人视为 “客户端”​发生业务办理的动作都是由他发起。1.Target目标对象目标对象是第一个非常容易理解的概念即被代理的对象。很明显在上面的预设场景中左边的主管就是目标对象。回到前面的动态代理的例子这个 partner 就可以被称为目标对象。publicstaticPartnergetPartner(intmoney){// partner即目标对象Partnerpartnerpartners.remove(0);return(Partner)Proxy.newProxyInstance(......);}2.Proxy代理对象代理对象也很容易理解就是 Proxy.newProxyInstance 返回的结果。在上面的预设场景中中间的业务经理 左边的主管组合起来形成一个代理对象请读者注意一点代理对象中还包含原始对象本身​。3.JoinPoint连接点所谓连接点可以简单地理解为目标对象的所属类中定义的所有方法。在上面的预设场景中很明显主管提供的几项业务账户充值、账号解封就属于连接点。回到动态代理的例子Partner 接口中声明的两个方法就是连接点。publicinterfacePartner{voidreceiveMoney(intmoney);voidplayWith(Playerplayer);}4.Pointcut切入点切入点的含义是那些被拦截 / 被增强的连接点。可以这样理解代理层会选择目标对象的一部分连接点作为切入点在执行目标对象的方法前/后做出额外的动作。由这个解释不难得出切入点与连接点的关系应该是包含关系。请注意切入点一定是连接点连接点不一定是切入点。被使用的连接点就是切入点。5.Advice通知Advice直译为通知但这个概念似乎很抽象所以我们会换一个相对容易理解的词增强的逻辑也就是增强的代码。在上面的预设场景中业务经理发现有客人的时候并没有直接交给主管而是先执行了他自己的逻辑搞清楚客人要办理账户账户充值业务这就是业务经理针对账户充值这个连接点的增强逻辑。由此可以得出一个这样的结论代理对象目标对象通知。到这里读者是否突然意识到一个问题切入点和通知要配合在一起使用。有了切入点之后需要搭配增强的逻辑AOP 才能为目标对象创建具有增强功能的代理。6.Aspect切面紧接着是切面切面比较容易理解切面 切入点 通知。实际上切面不仅仅包含通知还有一个不常见的部分是引介下面将会提到。7.Weaving织入从名字上听起来织入更像一个动作而且即便不理解概念也能凭借大胆猜想得到差不多的结论织入就是将通知应用到目标对象进而生成代理对象的过程。代理对象 目标对象 通知这个算式中的加号就表示织入动作。目标对象和通知都准备就绪时需要一个动作将它们绑定到一起。8.Introduction引介引介 / 引入这个概念对标的是通知通知是针对切入点提供增强的逻辑而引介是针对 Class 类型它可以在不修改原始类的代码的前提下在运行时为原始类动态添加新的属性 / 方法。引介在目前的企业应用、场景中已经很少出现所以读者对它的重视程度一定要放低当然感兴趣的读者学习一下也无可厚非。7.3.4 通知的类型通知指的是对目标方法加的扩展逻辑。Spring Framework 的官方文档中介绍了 AOP 术语紧接着列举了 Spring Framework 中定义的通知的类型。Spring Framework 中支持的通知的类型包含以下 5 种这些通知的类型是基于 AspectJ 的在 7.4 节中将要用到。Before前置通知目标对象的方法调用之前触发。After后置通知目标对象的方法调用之后触发。AfterReturning返回通知目标对象的方法调用完成在返回结果值之后触发。AfterThrowing异常通知目标对象的方法运行中抛出/触发异常后触发。请注意AfterReturning 与 AfterThrowing 两者是互斥的如果方法调用成功无异常就会有返回值如果方法抛出了异常就不会有返回值。Around环绕通知编程式控制目标对象的方法调用。环绕通知是所有通知类型中可操作范围最大的一种因为它可以直接获取目标对象以及要执行的方法所以环绕通知可以任意在目标对象的方法调用前/后扩展额外的逻辑甚至不调用目标对象的方法。
返回列表