Android AOP框架 AspectJ的使用

2024-06-16 12:58

本文主要是介绍Android AOP框架 AspectJ的使用,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

什么是AOP

AOP为Aspect Oriented Programming的缩写,意思是面向切面编程。
AOP主要实现的目的是针对业务处理过程中的切面进行提取,它所面对的是处理过程中的某个步骤或阶段,以获得逻辑过程中各部分之间低耦合性的隔离效果。也就是时候可以,在不需要修改目的程序代码的前提下,可以插入新的代码块,并且好处是,唯一修改,多处生效。
本质上是在编译时期由java文件生成class文件时,将aop代码插入到原来的程序中。

AOP应用场景

Spring中为了将日志记录,性能统计,安全控制,事务处理,异常处理等代码从业务逻辑代码中划分出来,通过对这些行为的分离,我们希望可以将它们独立到非指导业务逻辑的方法中,进而改变这些行为的时候不影响业务逻辑的代码
Android应用场景:
条件判断:
检查权限是否开启
检查网络状态是否可用
检查用户登录

统计:
用户行为统计
等。。。

AOP与OOP

面向对象的特点是继承、多态和封装。为了符合单一职责的原则,OOP将功能分散到不同的对象中去。让不同的类设计不同的方法,这样代码就分散到一个个的类中。可以降低代码的复杂程度,提高类的复用性。往往分散代码的同时,也增加了代码的重复性,比如某些情况下,两个类,都需要同一个方法,那么按照面向对象的思路,我们要分别在两个类中都写一遍,又比如,在两个类中的方法加入日志统计,那么我们按照以往的编程思想,就要两个类的方法中都加入日志内容。也许他们是完全相同的,但是因为OOP的设计让类与类之间无法联系,而不能将这些重复的代码统一起来。AOP就是为了解决这类问题而产生的,它是在运行时动态地将代码切入到类的指定方法、指定位置上的编程思想。AOP 是 OOP 的有力补充,合理的使用,会对我们的开发带来极大的方便。

用户登录状态
检查网络状态是否可用
在实际开发中为例 在 个人中心页面,许多功能是需要检测用户登录状态的。

// An highlighted block
setOnclick(){
case 1: if(checkUser()) doEvent1();break;
case 2: if(checkUser()) doEvent2();break;
}

aop来实现------>

case 1doEvent1();@CheckUser
void doEvent1(){}

用CheckUser这个注解去监听doEvent1() 方法,先去判断是否要登录,再执行原来的doEvent1()方法。

类似的 多个不同的类中有相似的判断,也可以用aop去处理,这样的好处是减少了大量的重复代码,比如添加日志功能,或者测试程序性能,这些与本身程序无关的逻辑。

AspectJ

AspectJ的特点:它是Java语言的无缝扩展,几乎是与java完全一致的语言,兼容java平台,易于学习使用,除了使用AspectJ特殊的语言外,AspectJ还支持原生的Java,只要加上对应的AspectJ注解就好。

基础概念

  1. Join Points(连接点)
    Join Points,简称JPoints,是AspectJ的核心思想之一,它就像一把刀,把程序的整个执行过程切成了一段段不同的部分。例如,构造函数被调用、构造函数内部执行、函数被调用、函数执行内部、异常处理等等,这些都是Join Points,实际上,也就是你想把新的代码插在程序的哪个地方,是插在构造方法中,还是插在某个方法调用前,或者是插在某个方法中,这个地方就是Join Points,当然,不是所有地方都能给你插的,只有能插的地方,才叫Join Points。

  2. Pointcuts(切入点)
    告诉代码注入工具,在何处注入一段特定代码的表达式。例如,在哪些 joint points 应用一个特定的 Advice。切入点可以选择唯一一个,比如执行某一个方法,也可以有多个选择,可简单理解为带条件的Join Points,作为我们需要的代码切入点。

  3. Advice(通知)
    如何注入到我的class文件中的代码。典型的 Advice 类型有 before、after 和 around,分别表示在目标方法执行之前、执行后和完全替代目标方法执行的代码。

  4. Aspect(切面)
    Pointcut 和 Advice 的组合看做切面。例如,我们在应用中通过定义一个 pointcut 和给定恰当的advice,添加一个日志切面。

  5. Weaving(织入)
    注入代码(advices)到目标位置(joint points)的过程。

下面以AspectJ为例介绍下Aop在android开发中的应用。

AspectJ接入项目

接入方式介绍

在项目主module下 创建 一个 aspectj.gradle 文件,文件内容如下

import org.aspectj.bridge.IMessage
import org.aspectj.bridge.MessageHandler
import org.aspectj.tools.ajc.Main
buildscript {repositories {mavenCentral()}dependencies {classpath 'org.aspectj:aspectjtools:1.8.9'}
}
repositories {mavenCentral()
}dependencies {
//    compile project(':gintonic')compile 'org.aspectj:aspectjrt:1.8.9'
}final def log = project.logger
final def variants = project.android.applicationVariantsvariants.all { variant ->
//    if (!variant.buildType.isDebuggable()) {
//        log.debug("Skipping non-debuggable build type '${variant.buildType.name}'.")
//        return;
//    }JavaCompile javaCompile = variant.javaCompilejavaCompile.doLast {String[] args = ["-showWeaveInfo","-1.5","-inpath", javaCompile.destinationDir.toString(),"-aspectpath", javaCompile.classpath.asPath,"-d", javaCompile.destinationDir.toString(),"-classpath", javaCompile.classpath.asPath,"-bootclasspath", project.android.bootClasspath.join(File.pathSeparator)]log.debug "ajc args: " + Arrays.toString(args)MessageHandler handler = new MessageHandler(true);new Main().run(args, handler);for (IMessage message : handler.getMessages(null, true)) {switch (message.getKind()) {case IMessage.ABORT:case IMessage.ERROR:case IMessage.FAIL:log.error message.message, message.thrownbreak;case IMessage.WARNING:log.warn message.message, message.thrownbreak;case IMessage.INFO:log.info message.message, message.thrownbreak;case IMessage.DEBUG:log.debug message.message, message.thrownbreak;}}}
}

接下来在主moudle 中依赖 apply from:‘aspectj.gradle’

以上完成了项目对AspectJ的依赖,接下来去使用AspectJ的功能,
有两种方式可以通过实现切入程序 一种是以原始的AspectJ语法,另一种是通过注解的方式。

使用

方式1

注解方式:

结构可以参考下图
在这里插入图片描述
首先声明性能检测的注解
PerformanceTrace

package org.android.aop.annotation;import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;/***Retention 注解保留策略 枚举类型*SOURCE  注解在编译的时候丢弃,只在原文件中有效*CLASS   注解在class文件中有效,运行时不会被VM保留*RUNTIME 注解在class文件中,并在运行时由VM保留,因此可以反射地读取注释*/
@Retention(RetentionPolicy.CLASS)
/***Target 注解的作用目标*@Target(ElementType.TYPE)   接口、类、枚举、注解*@Target(ElementType.FIELD)  字段、枚举的常量*@Target(ElementType.METHOD) 方法*@Target(ElementType.PARAMETER) 方法参数*@Target(ElementType.CONSTRUCTOR)  构造函数*@Target(ElementType.LOCAL_VARIABLE) 局部变量*@Target(ElementType.ANNOTATION_TYPE) 注解*@Target(ElementType.PACKAGE) 包*/
@Target({ ElementType.CONSTRUCTOR, ElementType.METHOD })
public @interface PerformanceTrace {}

接下来声明@Aspect

/*** 切面声明 切面的切点 函数和构造函数* @author Administrator*/
@Aspect
public class AspectTrace {//ydc startprivate static final String TAG = "LinearLayout";//1 注解方式:private static final String POINTCUT_METHOD ="execution(@org.android.aop.annotation.PerformanceTrace * *(..))";private static final String POINTCUT_CONSTRUCTOR ="execution(@org.android.aop.annotation.PerformanceTrace*.new(..))";@Pointcut(POINTCUT_METHOD)public void methodAnnotatedWithPerformanceTrace() {}@Pointcut(POINTCUT_CONSTRUCTOR)public void constructorAnnotatedPerformanceTrace() {}@Around("methodAnnotatedWithPerformanceTrace() || constructorAnnotatedPerformanceTrace()")public Object weaveJoinPoint(ProceedingJoinPoint joinPoint) throws Throwable {
//    Log.e(TAG,"weaveJoinPoint");//获取方法信息对象MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();//获取当前对象String className = methodSignature.getDeclaringType().getSimpleName();String methodName = methodSignature.getName();Object[] paramValues = joinPoint.getArgs();String[] paramNames = ((CodeSignature) joinPoint.getSignature()).getParameterNames();for(int i=0;i<paramNames.length;i++){Log.e(TAG,"原方法参数 "+(paramNames[i]+","+paramValues[i]));}//获取当前对象,通过反射获取类别详细信息 com.womai.activity.home.HomeActivityString className2 = joinPoint.getThis().getClass().getName();Log.e(TAG,""+className2);//初始化计时器final StopWatch stopWatch = new StopWatch();//开始监听stopWatch.start();//调用原方法的执行。Object result = joinPoint.proceed();//监听结束stopWatch.stop();Log.e(TAG,"函数耗时: "+className+"  "+buildLogMessage(methodName, stopWatch.getTotalTimeMillis()));return result;}
}

这样,我们可以使用上面的注解@PerformanceTrace 去检测我们想要检测得函数的执行耗时。比如这样:

   @PerformanceTraceprivate void doTestAop(String a){Log.e("LinearLayout","执行原方法  Thread.sleep 500ms");try {Thread.sleep(500);} catch (InterruptedException e) {e.printStackTrace();}}

在需要检测得方法上添加@PerformanceTrace注解,在编译阶段,我们的切面程序就会添加到doTestAop函数上,可以在下面这个目录下查看编译生成的class文件
在这里插入图片描述

    @PerformanceTraceprivate void doTestAop(String a) {JoinPoint var3 = Factory.makeJP(ajc$tjp_0, this, this, a);AspectTrace var10000 = AspectTrace.aspectOf();Object[] var4 = new Object[]{this, a, var3};var10000.weaveJoinPoint((new HomeActivity$AjcClosure1(var4)).linkClosureAndJoinPoint(69648));}

这里使用的是around 表示对方法整体进行拦截,前面介绍 Advice 类型有 before、after 和 around,分别表示在目标方法执行之前、执行后和完全替代目标方法执行的代码。

当使用@After的时候,@After(“methodAnnotatedWithDemandTrace() || constructorAnnotatedDemandTrace()”)

    @PerformanceTraceprivate void doTestAop(String a) {JoinPoint var2 = Factory.makeJP(ajc$tjp_0, this, this, a);try {Log.e("LinearLayout", "执行原方法  Thread.sleep 500ms");} catch (Throwable var5) {AspectTrace.aspectOf().weaveJoinPoint2(var2);throw var5;}AspectTrace.aspectOf().weaveJoinPoint2(var2);}

当使用@Before的时候,@Before(“methodAnnotatedWithDemandTrace() || constructorAnnotatedDemandTrace()”)

    @PerformanceTraceprivate void doTestAop(String a) {JoinPoint var2 = Factory.makeJP(ajc$tjp_0, this, this, a);AspectTrace.aspectOf().weaveJoinPoint2(var2);Log.e("LinearLayout", "执行原方法  Thread.sleep 500ms");}
方式2

AspectJ语法
示例:@Before("execution(* android.app.Activity.on(…))")**
使用execution或者call 去将代码注入到指定的方法位置。

  //Before和After的用法@Before("execution(* android.app.Activity.on*(android.os.Bundle))")public void onActivityMethodBefore(JoinPoint joinPoint) throws Throwable {String key = joinPoint.getSignature().toString();Log.e(TAG, "onActivityMethodBefore: " + key);}@After("execution(* android.app.Activity.on*(android.os.Bundle))")public void onActivityMethodAfter(JoinPoint joinPoint) throws Throwable {String key = joinPoint.getSignature().toString();Log.e(TAG, "onActivityMethodAfter: " + key);}

例如 监听Activity的on开始的方法。

  protected void onCreate(Bundle savedInstanceState) {JoinPoint var3 = Factory.makeJP(ajc$tjp_0, this, this, savedInstanceState);try {AspectTrace.aspectOf().onActivityMethodBefore(var3);super.onCreate(savedInstanceState);....省略代码....});} catch (Throwable var6) {AspectTrace.aspectOf().onActivityMethodAfter(var3);throw var6;}AspectTrace.aspectOf().onActivityMethodAfter(var3);}

可以看到
分别在原onCreate的开始,和结尾插入了AspectTrace.aspectOf().onActivityMethodBefore(var3);
AspectTrace.aspectOf().onActivityMethodAfter(var3);

这里除了用execution 还可以用call, call与 execution的区别call 是在方法调用的外面,execution 是在方法内部

@Around("execution(* collectPageName(String,com.womai.MyApp,String))")
public Object executionMethod(ProceedingJoinPoint joinPoint) throws Throwable {joinPoint.proceed();MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();String className = methodSignature.getDeclaringType().getSimpleName();String methodName = methodSignature.getName();String result = "aroundWeaverPoint";Log.e(TAG,"----------------------------->aroundWeaverPoint"+"|"+joinPoint.getThis()+"|"+joinPoint.getArgs()+"|"+joinPoint.getKind()+"|"+joinPoint.getArgs()+"|"+className+"|"+methodName);return  result;//替换原方法的返回值
}
@Before("call(* collectPageName(String,com.womai.MyApp,String))")
public void callMethod(JoinPoint joinPoint){Log.e(TAG,"beforecall");
}

编译后的方法

    protected void onStart() {String var8 = "";MyApp var9 = this.myApp;String var10 = this.className;var10000 = ajc$tjp_1;Object[] var12 = new Object[]{var10, var9, var8};JoinPoint var7 = Factory.makeJP(var10000, this, this, var12);AspectTrace.aspectOf().callMethod(var7);//原方法this.collectPageName(var10, var9, var8);}private void collectPageName(String key, MyApp myApp, String title) {StaticPart var10000 = ajc$tjp_2;Object[] var8 = new Object[]{key, myApp, title};JoinPoint var7 = Factory.makeJP(var10000, this, this, var8);AspectTrace var10 = AspectTrace.aspectOf();Object[] var9 = new Object[]{this, key, myApp, title, var7};var10.executionMethod((new AbstractActivity$AjcClosure1(var9)).linkClosureAndJoinPoint(69648));}

ProceedingJoinPoint 是继承 JoinPoint 并暴露了proceed() 方法,这个方法的作用是执行原函数。调用原方法的执行, joinPoint.proceed();

call,execution,within,withincode 具体语法使用示例

call和execution

二者区别在于 call 是 函数被调用的地方, execution 是函数内部执行。

语法结构:execution / call ([修饰符] 返回值类型 方法名(参数) [异常模式]) 蓝色表示可选部分。

例子:

execution / call(public *.*(..))  所有的public方法。
execution / call(* hello(..))            所有的hello()方法
execution / call(String hello(..))   所有返回值为String的hello方法。
execution / call(* hello(String))    所有参数为String类型的hello()
execution / call(* hello(String..))      至少有一个参数,且第一个参数类型为String的hello方法
execution / call(* com.aspect..*(..))   所有com.aspect包,以及子孙包下的所有方法
execution / call(* com..*.*Dao.find*(..))  com包下的所有一Dao结尾的类的一find开头的方法
within 和 withincode
  // within 织入 除了HomeActivity以外的 collectPageName方法@Pointcut("execution(* collectPageName(String,com.womai.MyApp,String))&& !within(com.womai.activity.home.HomeActivity)")//&& within(com.womaipublic void withinMethod() {}@Before("withinMethod()")public void withinCall(JoinPoint joinPoint){Log.e(TAG,"withinCall" +   "   "+joinPoint.toString());}

withincode

// 在testAOP2()方法内
@Pointcut("withincode(* com.xys.aspectjxdemo.MainActivity.testAOP2(..))")
public void invokeAOP2() {
}// 调用testAOP()方法的时候
@Pointcut("call(* com.xys.aspectjxdemo.MainActivity.testAOP(..))")
public void invokeAOP() {
}// 同时满足前面的条件,即在testAOP2()方法内调用testAOP()方法的时候才切入
@Pointcut("invokeAOP() && invokeAOP2()")
public void invokeAOPOnlyInAOP2() {
}@Before("invokeAOPOnlyInAOP2()")
public void beforeInvokeAOPOnlyInAOP2(JoinPoint joinPoint) {String key = joinPoint.getSignature().toString();Log.d(TAG, "onDebugToolMethodBefore: " + key);
}

参考更多

AspectJ 语法文档
AspectJ in Android
AOP 之 AspectJ 全面剖析 in Android
AOP之AspectJ在Android中的应用

这篇关于Android AOP框架 AspectJ的使用的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1066520

相关文章

中文分词jieba库的使用与实景应用(一)

知识星球:https://articles.zsxq.com/id_fxvgc803qmr2.html 目录 一.定义: 精确模式(默认模式): 全模式: 搜索引擎模式: paddle 模式(基于深度学习的分词模式): 二 自定义词典 三.文本解析   调整词出现的频率 四. 关键词提取 A. 基于TF-IDF算法的关键词提取 B. 基于TextRank算法的关键词提取

使用SecondaryNameNode恢复NameNode的数据

1)需求: NameNode进程挂了并且存储的数据也丢失了,如何恢复NameNode 此种方式恢复的数据可能存在小部分数据的丢失。 2)故障模拟 (1)kill -9 NameNode进程 [lytfly@hadoop102 current]$ kill -9 19886 (2)删除NameNode存储的数据(/opt/module/hadoop-3.1.4/data/tmp/dfs/na

Hadoop数据压缩使用介绍

一、压缩原则 (1)运算密集型的Job,少用压缩 (2)IO密集型的Job,多用压缩 二、压缩算法比较 三、压缩位置选择 四、压缩参数配置 1)为了支持多种压缩/解压缩算法,Hadoop引入了编码/解码器 2)要在Hadoop中启用压缩,可以配置如下参数

Makefile简明使用教程

文章目录 规则makefile文件的基本语法:加在命令前的特殊符号:.PHONY伪目标: Makefilev1 直观写法v2 加上中间过程v3 伪目标v4 变量 make 选项-f-n-C Make 是一种流行的构建工具,常用于将源代码转换成可执行文件或者其他形式的输出文件(如库文件、文档等)。Make 可以自动化地执行编译、链接等一系列操作。 规则 makefile文件

使用opencv优化图片(画面变清晰)

文章目录 需求影响照片清晰度的因素 实现降噪测试代码 锐化空间锐化Unsharp Masking频率域锐化对比测试 对比度增强常用算法对比测试 需求 对图像进行优化,使其看起来更清晰,同时保持尺寸不变,通常涉及到图像处理技术如锐化、降噪、对比度增强等 影响照片清晰度的因素 影响照片清晰度的因素有很多,主要可以从以下几个方面来分析 1. 拍摄设备 相机传感器:相机传

Android实现任意版本设置默认的锁屏壁纸和桌面壁纸(两张壁纸可不一致)

客户有些需求需要设置默认壁纸和锁屏壁纸  在默认情况下 这两个壁纸是相同的  如果需要默认的锁屏壁纸和桌面壁纸不一样 需要额外修改 Android13实现 替换默认桌面壁纸: 将图片文件替换frameworks/base/core/res/res/drawable-nodpi/default_wallpaper.*  (注意不能是bmp格式) 替换默认锁屏壁纸: 将图片资源放入vendo

pdfmake生成pdf的使用

实际项目中有时会有根据填写的表单数据或者其他格式的数据,将数据自动填充到pdf文件中根据固定模板生成pdf文件的需求 文章目录 利用pdfmake生成pdf文件1.下载安装pdfmake第三方包2.封装生成pdf文件的共用配置3.生成pdf文件的文件模板内容4.调用方法生成pdf 利用pdfmake生成pdf文件 1.下载安装pdfmake第三方包 npm i pdfma

零基础学习Redis(10) -- zset类型命令使用

zset是有序集合,内部除了存储元素外,还会存储一个score,存储在zset中的元素会按照score的大小升序排列,不同元素的score可以重复,score相同的元素会按照元素的字典序排列。 1. zset常用命令 1.1 zadd  zadd key [NX | XX] [GT | LT]   [CH] [INCR] score member [score member ...]

Android平台播放RTSP流的几种方案探究(VLC VS ExoPlayer VS SmartPlayer)

技术背景 好多开发者需要遴选Android平台RTSP直播播放器的时候,不知道如何选的好,本文针对常用的方案,做个大概的说明: 1. 使用VLC for Android VLC Media Player(VLC多媒体播放器),最初命名为VideoLAN客户端,是VideoLAN品牌产品,是VideoLAN计划的多媒体播放器。它支持众多音频与视频解码器及文件格式,并支持DVD影音光盘,VCD影

git使用的说明总结

Git使用说明 下载安装(下载地址) macOS: Git - Downloading macOS Windows: Git - Downloading Windows Linux/Unix: Git (git-scm.com) 创建新仓库 本地创建新仓库:创建新文件夹,进入文件夹目录,执行指令 git init ,用以创建新的git 克隆仓库 执行指令用以创建一个本地仓库的