欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Aop技术框架在移动端的实施应用

程序员文章站 2022-05-14 08:03:52
...


面向过程编程(Procedure Oriented Programming),即OPP

注重算法,功能不同的函数调用。常用的C语言

优点:任务分解,按部就班

面向对象编程(Object Oriented Programming),即OOP

将一个功能/问题分割成不同的对象模块。常用的C++,Java

优点:模块化结构化,易扩展易维护

貌似OOP的编程方式已经能够解决我们大多数的问题,为什么还要有AOP编程?

什么是AOP(What)

AOP的定义

Aspect Oriented Programming的缩写,即『面向切面编程』,即通过预编译方式和运行期动态代理实现程序功能统一维护的一种技术。

AOP是OOP的延续,是软件开发中的一个热点,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。

AOP其实是OOP的补充,OOP从横向上区分出一个个的类来,而AOP则从纵向上向对象中加入特定的代码。用一句更加形象的话讲,AOP就像一把刀,它能将整个过程按照你想要的切点进行切片,让整个过程清晰可见。

Aop技术框架在移动端的实施应用

AOP中的几个概念

  • Aspect 切面:切面是切入点和通知的集合。--------------------->自定义的切面文件

  • PointCut 切入点:切入点是指那些通过使用一些特定的表达式过滤出来的想要切入Advice的连接点。

  • Advice 通知:通知是向切点中注入的代码实现方法。------------->具体实现方法

  • Joint Point 连接点:所有的目标方法都是连接点.

  • Weaving 编织:主要是在编译期使用AJC将切面的代码注入到目标中, 并生成出代码混合过的.class的过程.

    一句话: 注入代码(advices)到目标位置(joint points)的过程

AspectJ 介绍

AspectJ 是一个面向切面编程的一个框架,它扩展了java 语言,并定义了实现 AOP 的语法。我们知道,在将.java 文件编译为.class 文件时默认使用 javac 编译工具,而AspectJ 会有一套符合 java 字节码编码规范的编译工具来替代 javac,在将.java 文件编译为.class 文件时,会动态的插入一些代码来做到对某一类特定东西的统一处理

Aop的工具库:

  • AspectJ:

    一个 JavaTM语言的面向切面编程的无缝扩展(适用Android)。

    • 简介:可以织入所有类;支持编译期和加载时代码注入;编写简单,功能强大。需要使用ajc编译器编译,ajc编译器是java编译器的扩展,具有其所有功能。
  • Javassist for Android:

    用于字节码操作的知名 java 类库 Javassist 的 Android 平台移植版。

    • 简介:可以织入绝大部分类;运行时生成,减少不必要的生成开销;通过将切面逻辑写入字节码,减少了生成子类的开销,不会产生过多子类。运行时加入切面逻辑,产生性能开销。
  • DexMaker:

    Dalvik 虚拟机上,在编译期或者运行时生成代码的 Java API。

    • 简介:支持编译期和加载时代码注入;运行在Android Dalvik VM上,利用Java编写,来动态生成DEX字节码的API。
  • ASMDEX:

    一个类似 ASM 的字节码操作库,运行在Android平台,操作Dex字节码。

    • 简介:可以织入所有类;支持编译期和加载时代码注入。修改字节码,需要对class文件比较熟悉,编写过程复杂。

      Aop技术框架在移动端的实施应用

为什么要有AOP?(Why)

前面讲的的Aop就像一把刀,在程序运行时,切中你要的代码,为什么这样做呢?或者说,这样做有什么好处呢?

举个栗子:

打日志这样的操作,当我需要知道程序执行到某一步,或者观察某些参数时,打日志成了必要的debug手段。常规做法每一个方法加一个日志,如果仅一个文件或者工程非常小的时候,没有问题,但是当工程代码量非常大的时候,这样的方法,就显得非常低效。而通过AOP方式,可以横向的去切割类中的方法和属性,甚至可以不关心什么类。
这样的横向切片方式,可以完成更好的监控和调试,将问题统一处理,是OOP很好的补充。

怎么在客户端中应用(How)

  1. 通过gradle集成

    项目路径gradle导入

       classpath  'com.hujiang.aspectjx:gradle-android-plugin-aspectjx:2.0.4'
    

    app目录下的gradle导入插件

    apply plugin: 'android-aspectjx'
    
  2. 定义注解类

    @Retention(RetentionPolicy.RUNTIME)
    @Target({ElementType.CONSTRUCTOR, ElementType.METHOD})
    public @interface CheckLogin {
    }
    

    这里补充一个知识点,就是注解的生命周期,大家肯定注意到,我用的是RetentionPolicy.RUNTIME,JAVA注解声明周期,一共有三个,分别是:

    1. RetentionPolicy.SOURCE:注解只保留在源文件,当Java文件编译成class文件的时候,注解被遗弃;
    2. RetentionPolicy.CLASS:注解被保留到class文件,但jvm加载class文件时候被遗弃,可以在编译阶段做预处理,生成辅助代码
    3. RetentionPolicy.RUNTIME:注解不仅被保存到class文件中,jvm加载class文件之后,仍然存在,可以通过反射,拿到注解;

    生命周期分别对应于:Java源文件(.java文件) —> .class文件 —> 内存中的字节码。
    可以看出:注解生命周期长度 SOURCE < CLASS < RUNTIME

  3. 根据具体业务场景,定义切点和实现通知

    /**
     * @author : created by lixuecheng
     * Date : 2019/11/13 16:00
     * VERSION : 1.0
     * DES : 切面检查登录
     **/
    @Aspect
    public class CheckLoginAspect {
        //切入点切片表达式
        @Pointcut("execution(@com.aopchecklogin.annotation.CheckLogin * *(..))")
        public void methodAnnotated() {}
    
        //通知 
        /**
        **  前置通知(Before)、
        **  后置通知(AfterReturning)、
        **  异常通知(AfterThrowing)、
        **  最终通知(After)
        **  环绕通知(Around)
        **
        **/
        @Around("methodAnnotated()")
        public void aroundJoinPoint(ProceedingJoinPoint joinPoint) {
            Context context = getContext(joinPoint.getThis());
            String sessionkey = "";
            if(context != null) {
                sessionkey  = (String)SPUtils.get(context, Cons.SP_SESSION_KEY, "");
            } else {
                return;
            }
    //        String sessionkey  = (String)SPUtils.get(context, Cons.SP_SESSION_KEY, "");
            if(sessionkeyIsInvalid(sessionkey)) {
                joinPoint.proceed();
            } else {
                showHintDialog();
            }
        }
    
        private Context getContext(Object object) {
            if (object instanceof Activity) {
                return (Activity) object;
            } else if (object instanceof Fragment) {
                Fragment fragment = (Fragment) object;
                return fragment.getActivity();
            } else if (object instanceof View) {
                View view = (View) object;
                return view.getContext();
            }
            return null;
        }
    }
    
    
  4. 在连接点处切入

    @CheckLogin
    @Override
    public void onCheckedChanged(RadioGroup group, @IdRes int checkedId) {
        //切换底部导航逻辑
    }
    

总结,通过以上四步,实际上完成的是通过切入点,把某些功能提取出来,组成一个切面,然后对切面方法进行编程,实现我们自己的业务需求,同志们,面向切面编程的概念有木有啊。。。

几个重要场景的分析(Where)

性能监控: 在方法调用前后记录调用时间,方法执行太长或超时报警。

无痕埋点: 在需要埋点的地方添加对应统计代码,构建非如入侵式的埋点统计系统

记录日志: 在方法执行前后记录系统日志。

权限验证: 方法执行前验证是否有权限执行当前方法,没有则抛出没有权限执行异常,由业务代码捕捉。

热修复: 网易有一个热修复就是基于AOP框架

全局登录session校验: 统一处理用户身份校验

界面双击屏蔽:防止界面被双击,也称防抖,直接切入控制view的onclick方法

写在最后

AOP原理

AspectJ 实际上就是用一种特定语言编写切面,通过自己的语法编译工具 ajc 编译器来编译,生成一个新的代理类,该代理类增强了业务类。

AspectJ 做的事情如下:

  1. 首先从文件列表里取出所有的文件名,读取文件,进行分析;
  2. 扫描含有 aspect 的切面文件;
  3. 根据切面中定义规则,拦截匹配的 JoinPoint ;
  4. 继续读取切面定义的规则,根据 around 或 before ,采用不同策略织入切面。

其他

  1. 不是每个链接点都需要写注解,demo中运用的只是其中一种方式

  2. 通知方式中Before和After,Around,在前端中最为常用,他们在实现原理上是有区别的。

    Before和After的实现就是在原代码上直接插入通知方法,Around通知方式,是通过代理和闭包的方式
    注意: Before和After这两个advice的参数是一般传JoinPoint类型或者不传,Around的方法参数必须是ProceedingJoinPoint类型,另外 切点表达式Pointcut注解表达式和方法表达式略有不同,如果是注解,必须以@符号开头。例如:

 @Pointcut("execution(@com.aopchecklogin.annotation.CheckLogin * *(..))")
    public void methodAnnotated() {}

而表达式如果是方法,则比较随意,配合通配符即可,可以自己尝试摸索

  1. 切入点中的表达式,我们一般应用execution即可,还有其他方式,比如call

  2. 至于原理方便,更深入的就是动态代理、静态代理、拦截、反射等

两个小问题

  1. pointcut表达式能不能配合正则表达式,做一点其他方面的尝试?

  2. 注解一个静态方法会发生什么?

    大家可以尝试下~

相关标签: Android基础