手机网站建设模板下载,网站建设 小程序开发 营销推广,未来软件网站建设,网站默认首页点击上方“Java知音”#xff0c;选择“置顶公众号”技术文章第一时间送达#xff01;作者#xff1a;FeelsChaoticjuejin.im/post/5c57b2d5e51d457ffd56ffbb前言本文将从另一个角度讲解 AOP#xff0c;从宏观的实现原理和设计本质入手。大部分讲 AOP 的博文都是一上来就罗… 点击上方“Java知音”选择“置顶公众号”技术文章第一时间送达作者FeelsChaoticjuejin.im/post/5c57b2d5e51d457ffd56ffbb前言本文将从另一个角度讲解 AOP从宏观的实现原理和设计本质入手。大部分讲 AOP 的博文都是一上来就罗列语法然后敲个应用 demo就完了 。但学习不能知其然不知其所以然。对 AOP 我提出了几点思考AspectJ 为什么会大热AspectJ 是怎样工作的和 Spring AOP 有什么区别什么场景下适用我们能不能自己实现一个 AOP 方法一、引入敲一个小 Demo 来引入主题假设我想不依赖任何 AOP 方法在特定方法的执行前后加上日志打印。第一种方式写死代码定义一个目标类接口把 before() 和 after() 方法写死在 execute() 方法体中非常不优雅我们改进一下。第二种方式静态代理但是存在一个问题随着打印日志的需求增多Proxy 类越来越多我们能不能保持只有一个代理呢这时候我们就需要用到 JDK 动态代理了。第三种方式动态代理新建动态代理类客户端调用这又引出一个问题日志打印和业务逻辑耦合在一起我们希望把前置和后置抽离出来作为单独的增强类。第四种方式动态代理 分离增强类新建增强类接口和实现类用反射代替写死方法解耦代理和操作者客户端调用但是用了反射性能太差了而且动态代理用起来也不方便有没有更好的办法我们发现 Demo 存在种种问题静态代理每次都要自己新建个代理类太繁琐重用性又差一个代理不能同时代理多种类动态代理可以重用但性能太差代理类耦合进被代理类的调用阶段万一我需要改下 before、after 的方法名可能会点燃一个炸弹代理拦截了一个类就会拦截这个类的所有方法难道我还要在代理类里加个 if-else 判断特定方法过滤拦截我们可以不可以只拦截特定的方法如果我既要打印日志又要计算方法执行用时每次都要去改增强类吗我们的诉求很简单1. 性能高2. 松耦合3. 步骤方便4. 灵活性高。那主流的 AOP 框架是怎么解决这个问题的呢我们赶紧来看看二、AOP 方法不同的 AOP 方法原理略微有些不同我们先看下 AOP 实现方式有哪些所有 AOP 方法本质就是拦截、代理、反射(动态情况下)实现原理可以看作是代理 / 装饰设计模式的泛化为什么这么说我们来详细分析一下。Java由浅入深揭开 AOP 实现原理三、静态织入原理以 AspectJ 为例静态织入原理就是静态代理我们以 AspectJ 为例。1. AspectJ 设计思路前面说到 Demo 存在的种种问题AspectJ 是怎么解决的呢AspectJ 提供了两套强大的机制(1)切面语法 | 解决业务和切面的耦合AspectJ 中的切面就解决了这个问题。Before(execution(* android.view.View.OnClickListener.onClick(..)))我们可以通过切面将增强类与拦截匹配条件(切点)组合在一起从而生成代理。这把是否要使用切面的决定权利还给了切面我们在写切面时就可以决定哪些类的哪些方法会被代理从而逻辑上不需要侵入业务代码。而普通的代理模式并没有做到切面与业务代码的解耦虽然将切面的逻辑独立进了代理类但是决定是否使用切面的权利仍然在业务代码中。这才导致了 Demo 中种种的麻烦。AspectJ 提供了两套对切面的描述方法1.我们常用的基于 java 注解切面描述的方法写起来十分方便兼容 Java 语法Aspectpublic class AnnoAspect { Pointcut(execution(...)) public void jointPoint() { } Before(jointPoint()) public void before() { //... } After(jointPoint()) public void after() { //... }}2.基于 aspect 文件的切面描述方法这种语法不兼容 Java 语法。public aspect AnnoAspect { pointcut XX():execution(...); before(): XX() { //... } after(): XX() { //... }} (2)织入工具 | 解决代理手动调用的繁琐那么切面语法让切面从逻辑上与业务代码解耦但是我要怎么找到特定的业务代码织入切面呢两种解决思路一种就是提供注册机制通过额外的配置文件指明哪些类受到切面的影响不过这还是需要干涉对象创建的过程另外一种解决思路就是在编译期或类加载期先扫描切面并将切面代码通过某种形式插入到业务代码中。那 AspectJ 织入方式有两种一种是 ajc 编译可以在编译期将切面织入到业务代码中。另一种就是 aspectjweaver.jar 的 agent 代理提供了一个 Java agent 用于在类加载期间织入切面。2. 通过 class 反推 AspectJ 实现机制(1)Before 机制国际惯例写个 Demo1.自定义 AutoLog 注解2.编写 LogAspect 切面3.在切入点中加上注解反编译后(请点开大图查看)发现 AspectJ 会把调用切面的方法插入到切入点中且封装了切入点所在的方法名、所在类、入参名、入参值、返回值等等信息传递给切面这样就建立了切面和业务代码的关联。我们跟进 LogAspect.aspectOf().aroundJoinPoint(localJoinPoint); 一探究竟。我们发现了什么其实 Before 和 After 的插入就是在匹配到的 JoinPoint 调用前后插入 Advise 方法以此来达到拦截目标 JoinPoint 的作用。如下图所示(2)Around 机制1.自定义 SingleClick 注解2.编写 SingleClickAspect 切面3.业务方加上注解打开编译后的 class 文件(请点开大图查看)我们发现和 Before、After 织入不一样了前者的织入只是在匹配的 JoinPoint 前后插入 Advise 方法仅仅是插入。而 Around 拆分了业务代码和 Advise 方法把业务代码迁移到新函数中通过一个单独的闭包拆分来执行相当于对目标 JoinPoint 进行了一个代理所以 Around 情况下我们除了编写切面逻辑还需要手动调用 joinPoint.proceed() 来调用闭包执行原方法。我们看下 proceed() 都做了些什么那这个 arc 是什么什么时候拿到的呢继续回溯在 AroundClosure 闭包中会把运行时对象和当前连接点 joinPoint 对象传入调用 linkClosureAndJoinPoint() 绑定两端这样在 Around 中就可以通过 ProceedingJoinPoint.proceed() 调用 AroundClosure进而调用到目标方法了。那么一图总结 Around 机制我们从 AspectJ 编译后的 class 文件可以明显看出执行的逻辑proceed 方法就是回调执行被代理类中的方法。所以 AspectJ 做的事情如下首先从文件列表里取出所有的文件名读取文件进行分析扫描含有 aspect 的切面文件根据切面中定义规则拦截匹配的 JoinPoint 继续读取切面定义的规则根据 around 或 before 采用不同策略织入切面。(3)Before After 机制与 Around 机制区别Before、After 仅仅是织入了 Advise 方法Around 使用了代理 闭包的方式进行替换3. AspectJ 底层技术总结分析完 class 你会发现AspectJ 实际上就是用一种特定语言编写切面通过自己的语法编译工具 ajc 编译器来编译生成一个新的代理类该代理类增强了业务类。AspectJ 就是一个代码生成工具编写一段通用的代码然后根据 AspectJ 语法定义一套代码生成规则AspectJ 就会帮你把这段代码插入到对应的位置去。Java知音扩展代码神器拒绝重复编码这款IDEA插件了解一下.....AspectJ 语法就是用来定义代码生成规则的语法。扩展编译器引入特定的语法来创建 Advise从而在编译期间就织入了Advise 的代码。如果使用过 Java Compiler Compiler (JavaCC)你会发现两者的代码生成规则的理念惊人相似。JavaCC 允许你在语法定义规则文件中加入你自己的 Java 代码用来处理读入的各种语法元素。四、动态织入原理以 Spring AOP 为例动态织入原理就是动态代理。1. Spring AOP 执行原理Spring AOP 利用截取的方式对被代理类进行装饰以取代原有对象行为的执行不会生成新类。2. Spring AOP VS AspectJ可能有的小伙伴会困惑了Spring AOP 使用了 AspectJ怎么是动态代理呢那是因为 Spring 只是使用了与 AspectJ 一样的注解没有使用 AspectJ 的编译器转向采用动态代理技术的实现原理来构建 Spring AOP 的内部机制(动态织入)这是与 AspectJ(静态织入)最根本的区别。Spring 底层的动态代理分为两种 JDK 动态代理和 CGLibJDK 动态代理用于对接口的代理动态产生一个实现指定接口的类注意动态代理有个约束目标对象一定是要有接口的没有接口就不能实现动态代理只能为接口创建动态代理实例而不能对类创建动态代理。CGLIB 用于对类的代理把被代理对象类的 class 文件加载进来修改其字节码生成一个继承了被代理类的子类。使用 cglib 就是为了弥补动态代理的不足。3. JDK 动态代理的原理我们前面的 Demo 第三种方式使用了动态代理我们不禁有了疑问动态代理类及其对象实例是如何生成的调用动态代理对象方法为什么可以调用到目标对象方法我们通过 Proxy.newProxyInstance 可以动态生成指定接口的代理类的实例。我们来看下newProxyInstance内部实现机制。代理对象会实现接口的所有方法实现的方法交由我们自定义的 handler 来处理。我们看下 getProxyClass0 方法只凭一个类加载器、一个接口是怎么创建代理类的注意一下Android 中动态代理类是直接生成而 Java 是生成代理类的字节码再根据字节码生成代理类。那么客户端就可以 getProxy() 拿到生成的代理类 com.sun.proxy.$Proxy0这个代理类继承自 Proxy 并实现了我们被代理类的所有接口在各个接口方法的内部通过反射调用了 InvocationHandlerImpl 的 invoke 方法。总结下步骤获得被代理类的接口信息生成一个实现了代理接口的动态代理类通过反射获得代理类的构造函数利用构造函数生成动态代理类的实例对象在调用具体方法前调用 invokeHandler 方法来处理。后记1. 设计模式不能脱离业务场景不知不觉我们复习了一下代理模式设计模式必须依赖大量的业务场景脱离业务去看设计模式是没有意义的。因为脱离了应用场景即使理解了模式的内容和结构也学不会在合适的时候应用。设计模式推荐设计模式内容聚合2. 敢于追求优雅的代码首先你要敢于追求优雅的代码就像我们开头的打印日志的需求不断提出问题不断追求更好的解决方案在新的方案上挖掘新的问题……如果你完全不追求设计那自然是不会想到去研究设计模式的。ENDJava面试题专栏【41期】盘点那些必问的数据结构算法题之链表【42期】盘点那些必问的数据结构算法题之二叉堆【43期】盘点那些必问的数据结构算法题之二叉树基础【44期】盘点那些必问的数据结构算法题之二分查找算法【45期】盘点那些必问的数据结构算法题之基础排序【46期】盘点那些必问的数据结构算法题之快速排序【47期】六大类二叉树面试题汇总解答【48期】盘点Netty面试常问考点什么是 Netty 的零拷贝【49期】面试官SpringMVC的控制器是单例的吗?【50期】基础考察ClassNotFoundException 和 NoClassDefFoundError 有什么区别我知道你 “在看”