会用就行了?你知道 AOP 框架的原理吗?

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

但是存在一个问题,随着打印日志的需求增多,Proxy 类越来越多,我们能不能保持只有一个代理呢?这时候我们就需要用到 JDK 动态代理了。

第三种方式:动态代理

新建动态代理类

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

客户端调用

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

这又引出一个问题,日志打印和业务逻辑耦合在一起,我们希望把前置和后置抽离出来,作为单独的增强类。

第四种方式:动态代理 + 分离增强类

新建增强类接口和实现类

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

用反射代替写死方法,解耦代理和操作者

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

客户端调用

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

但是用了反射性能太差了,而且动态代理用起来也不方便,有没有更好的办法?

我们发现 Demo 存在种种问题
  • 静态代理每次都要自己新建个代理类,太繁琐,重用性又差,一个代理不能同时代理多种类;
  • 动态代理可以重用,但性能太差;
  • 代理类耦合进被代理类的调用阶段,万一我需要改下 before、after 的方法名,可能会点燃一个炸弹;
  • 代理拦截了一个类,就会拦截这个类的所有方法,难道我还要在代理类里加个 if-else 判断特定方法过滤拦截?我们可以不可以只拦截特定的方法?
  • 如果我既要打印日志,又要计算方法执行用时,每次都要去改增强类吗?

我们的诉求很简单:1. 性能高;2. 松耦合;3. 步骤方便;4. 灵活性高。

那主流的 AOP 框架是怎么解决这个问题的呢?我们赶紧来看看!

二、AOP 方法

不同的 AOP 方法原理略微有些不同,我们先看下 AOP 实现方式有哪些:

AOP方式机制说明
静态织入静态代理直接修改原类,比如编译期生成代理类的 APT
静态织入自定义类加载器使用类加载器启动自定义的类加载器,并加一个类加载监听器,监听器发现目标类被加载时就织入切入逻辑,以 Javassist 为代表
动态织入动态代理字节码加载后,为接口动态生成代理类,将切面植入到代理类中,以 JDK Proxy 为代表
动态织入动态字节码生成字节码加载后,通过字节码技术为一个类创建子类,并在子类中采用方法拦截的技术拦截所有父类方法的调用织入逻辑。属于子类代理,以 CGLIB 为代表

所有 AOP 方法本质就是:拦截、代理、反射(动态情况下),实现原理可以看作是代理 / 装饰设计模式的泛化,为什么这么说?我们来详细分析一下。

三、静态织入原理,以 AspectJ 为例

静态织入原理就是静态代理,我们以 AspectJ 为例。

1. AspectJ 设计思路

前面说到 Demo 存在的种种问题,AspectJ 是怎么解决的呢?AspectJ 提供了两套强大的机制:

(1)切面语法 | 解决业务和切面的耦合

AspectJ 中的切面,就解决了这个问题。

@Before(“execution(* android.view.View.OnClickListener.onClick(…))”)

我们可以通过切面,将增强类与拦截匹配条件(切点)组合在一起,从而生成代理。这把是否要使用切面的决定权利还给了切面,我们在写切面时就可以决定哪些类的哪些方法会被代理,从而逻辑上不需要侵入业务代码

而普通的代理模式并没有做到切面与业务代码的解耦,虽然将切面的逻辑独立进了代理类,但是决定是否使用切面的权利仍然在业务代码中。这才导致了 Demo 中种种的麻烦。

AspectJ 提供了两套对切面的描述方法:

  1. 我们常用的基于 java 注解切面描述的方法,写起来十分方便,兼容 Java 语法;

@Aspect
public class AnnoAspect {
@Pointcut(“execution(…)”)
public void jointPoint() {
}

@Before(“jointPoint()”)
public void before() {
//…
}

@After(“jointPoint()”)
public void after() {
//…
}
}

  1. 基于 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 机制

国际惯例写个 Demo

  1. 自定义 AutoLog 注解

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  1. 编写 LogAspect 切面

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  1. 在切入点中加上注解

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

反编译后(请点开大图查看)

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

发现 AspectJ 会把调用切面的方法插入到切入点中,且封装了切入点所在的方法名、所在类、入参名、入参值、返回值等等信息,传递给切面,这样就建立了切面和业务代码的关联

我们跟进 LogAspect.aspectOf().aroundJoinPoint(localJoinPoint); 一探究竟。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

我们发现了什么?其实 Before 和 After 的插入就是在匹配到的 JoinPoint 调用前后插入 Advise 方法,以此来达到拦截目标 JoinPoint 的作用。 如下图所示:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

(2)@Around 机制
  1. 自定义 SingleClick 注解

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  1. 编写 SingleClickAspect 切面

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  1. 业务方加上注解

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

打开编译后的 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 做的事情如下:

最后

简历首选内推方式,速度快,效率高啊!然后可以在拉钩,boss,脉脉,大街上看看。简历上写道熟悉什么技术就一定要去熟悉它,不然被问到不会很尴尬!做过什么项目,即使项目体量不大,但也一定要熟悉实现原理!不是你负责的部分,也可以看看同事是怎么实现的,换你来做你会怎么做?做过什么,会什么是广度问题,取决于项目内容。但做过什么,达到怎样一个境界,这是深度问题,和个人学习能力和解决问题的态度有关了。大公司看深度,小公司看广度。大公司面试你会的,小公司面试他们用到的你会不会,也就是岗位匹配度。

选定你想去的几家公司后,先去一些小的公司练练,学习下面试技巧,总结下,也算是熟悉下面试氛围,平时和同事或者产品PK时可以讲得头头是道,思路清晰至极,到了现场真的不一样,怎么描述你所做的一切,这绝对是个学术性问题!

面试过程一定要有礼貌!即使你觉得面试官不尊重你,经常打断你的讲解,或者你觉得他不如你,问的问题缺乏专业水平,你也一定要尊重他,谁叫现在是他选择你,等你拿到offer后就是你选择他了。

金九银十面试季,跳槽季,整理面试题已经成了我多年的习惯!在这里我和身边一些朋友特意整理了一份快速进阶为Android高级工程师的系统且全面的学习资料。涵盖了Android初级——Android高级架构师进阶必备的一些学习技能。

附上:我们之前因为秋招收集的二十套一二线互联网公司Android面试真题(含BAT、小米、华为、美团、滴滴)和我自己整理Android复习笔记(包含Android基础知识点、Android扩展知识点、Android源码解析、设计模式汇总、Gradle知识点、常见算法题汇总。)

droid高级架构师进阶必备的一些学习技能。**

附上:我们之前因为秋招收集的二十套一二线互联网公司Android面试真题(含BAT、小米、华为、美团、滴滴)和我自己整理Android复习笔记(包含Android基础知识点、Android扩展知识点、Android源码解析、设计模式汇总、Gradle知识点、常见算法题汇总。)

[外链图片转存中…(img-nRfFpIV7-1720122140425)]

  • 19
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值