AOP面向切面编程【彩民之家论坛9066777】

2019-09-18 21:57 来源:未知

在这个类我试验了一下两种单例模式的性能差别,刚都说了AOP可以用来检测性能嘛。不过没在高并发的环境下其实两种都差不多。

public class LoginActivity extends AppCompatActivity implements View.OnClickListener, RadioGroup.OnCheckedChangeListener { private RadioGroup radioGroup; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_login); test(); } @CheckLogin public void test(){ Log.i("tag","判断是否登录"); } 

Join Points

Join Points 就是程序运行时的一些执行点,例如:我要打印所有Activity的onCreate方法,onCreate方法被调用就是一个Join Points。除了方法被调用,还有很多,例如方法内部“读、写”变量,异常处理等,如下表:

Join Point 说明 Pointcuts语法
Method call 方法被调用 call(MethodPattern)
Method execution 方法执行 execution(MethodPattern)
Constructor call 构造函数被调用 call(ConstructorPattern)
Constructor execution 构造函数执行 execution(ConstructorPattern)
Field get 读取属性 get(FieldPattern)
Field set 写入属性 set(FieldPattern)
Pre-initialization 与构造函数有关,很少用到 preinitialization(ConstructorPattern)
Initialization 与构造函数有关,很少用到 initialization(ConstructorPattern)
Static initialization static 块初始化 staticinitialization(TypePattern)
Handler 异常处理 handler(TypePattern)
Advice execution 所有 Advice 执行 adviceexcution()

彩民之家论坛9066777 1

call和execution区别.png

好啦,所有准备工作已完成。那么拿着梭子就是干,别想那么多了。上车吧!

1、在android studio中直接配置AspectJ,这个配置很重要,如果失败,后面就无法成功,先贴出我的配置,在app的build.gradle中做如下配置

Pointcuts 示例

以下示例表示在aspectjx插件下,相同包是指同一个aar/jar包,AspectJ常规配置下不同包不能执行“execution”织入

  • **:表示是任意包名
  • ..:表示任意类型任意多个参数
AfterReturning示例

在"com.luyao.aop.aspectj.AspectJActivity"执行getHeight()方法返回高度后打印这个高度值。

package com.luyao.aop.aspectj;
public class AspectJActivity extends Activity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        getHeight();
    }
    public int getHeight() {
            return 1280;
    }
}

@AfterReturning(pointcut = "call(* com.luyao.aop.aspectj.AspectJActivity.getHeight())", returning = "height")
public void getHeight(int height) {  // height必须和上面"height"一样
    Log.e("luy", "height:"   height);
}

反编译后的代码:

彩民之家论坛9066777 2

AfterReturning示例

text() 方法执行时就是一个切点。在执行 test 时,会回调上面的 Aspect类 的 executionAspectJ() 方法。然后会依次执行 @Before,@After。至于 After 和 Around 谁先谁后,这个我不太清楚,如果两个都写的话会报错。所以不知先后。有兴趣的可以自己试试。那么我们再看看 aroundAspectJ(ProceedingJoinPoint joinPoint) 这个方法里的逻辑。

AOP是Aspect Oriented Programming的缩写,即『面向切面编程』。它和我们平时接触到的OOP都是编程的不同思想,OOP,即『面向对象编程』,它提倡的是将功能模块化,对象化,而AOP的思想,则不太一样,它提倡的是针对同一类问题的统一处理,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。

within

任意连接点:包括类/对象初始化块,field,方法,构造器

  1. within(com.xyz.service.*)
    com.xyz.service包下任意连接点
  2. within(com.xyz.service..*)
    com.xyz.service包或子包下任意连接点
  3. within(TestAspect)
    TestAspect类下的任意连接点
  4. within(@com.xyz.service.BehavioClass *)
    持有com.xyz.service.BehavioClass注解的任意连接点

还有这里的 Pointcut 注解,就是切点,即触发该类的条件。里面的字符串都有哪些呢。让我们来看看:

package com.zx.aopdemo.login;import java.lang.annotation.ElementType;import java.lang.annotation.Retention;import java.lang.annotation.RetentionPolicy;import java.lang.annotation.Target;@Target(ElementType.METHOD) //可以注解在方法 上@Retention(RetentionPolicy.RUNTIME) //运行时存在public @interface CheckLogin {}
定义注解
@Target(ElementType.METHOD) // 修饰的是方法
@Retention(RetentionPolicy.CLASS) // 编译时注解
public @interface BehaviorTrace {
    String value(); // 功能点名称
    int type(); // 唯一确定功能点的值
}

通过定义@BehaviorTrace 来给"摇一摇"和"朋友圈"方法添加注解

这里要使用Aspect的编译器编译必须给类打上标注,@Aspect。还有这里的Pointcut注解,就是切点,即触发该类的条件。里面的字符串如下

Pointcuts

上表中,同一个函数,还分为call类型和execution类型的JPoint,如何选择自己想要的JPoint呢,这就是Pointcuts的功能:提供一种方法使得开发者能够选择自己感兴趣的JoinPoints。例如:我要打印所有Activity的onCreate方法,Pointcuts需要筛选的就是所有Activity的onCreate方法,而不是任意类的onCreate方法。除了上表与Join Point 对应的选择外,Pointcuts 还有其他选择方法:

Pointcuts 语法 说明 示例
within(TypePattern) TypePattern标示package或者类 TypePatter可以使用通配符 表示某个package或者类中的所有JPoint。比如within(Test):Test类中所有JPoint
withincode(Constructor Signature/Method Signature) 表示某个构造函数或其他函数执行过程中涉及到的JPoint 比如 withinCode(* TestDerived.testMethod(..)) 表示testMethod涉及的JPoint。withinCode( *.Test.new(..))表示Test构造函数涉及的JPoint
cflow(pointcuts) cflow是call flow的意思,cflow的条件是一个pointcut 比如cflow(call TestDerived.testMethod):表示调用TestDerived.testMethod函数时所包含的JPoint,包括testMethod的call这个JPoint本身
cflowbelow(Pointcut) cflow是call flow的意思 比如cflowblow(call TestDerived.testMethod):表示调用TestDerived.testMethod函数时所包含的JPoint,不包括testMethod的call这个JPoint本身
this(Type) Join Point 所属的 this 对象是否 instanceOf Type 或者 Id 的类型 JPoint是代码段(不论是函数,异常处理,static block),从语法上说,它都属于一个类。如果这个类的类型是Type标示的类型,则和它相关的JPoint将全部被选中。图2示例的testMethod是TestDerived类。所以this(TestDerived)将会选中这个testMethod JPoint
target(Type) JPoint的target对象是Type类型 和this相对的是target。不过target一般用在call的情况。call一个函数,这个函数可能定义在其他类。比如testMethod是TestDerived类定义的。那么target(TestDerived)就会搜索到调用testMethod的地方。但是不包括testMethod的execution JPoint
args(TypeSignature) 用来对JPoint的参数进行条件搜索的 比如args(int,..),表示第一个参数是int,后面参数个数和类型不限的JPoint。

Pointcut 表达式还可以 !、&&、|| 来组合,语义和java一样。上面 Pointcuts 的语法中涉及到一些 Pattern,下面是这些 Pattern 的规则,[]里的内容是可选的:

Pattern 规则
MethodPattern [!] [@Annotation] [public,protected,private] [static] [final] 返回值类型 [类名.]方法名(参数类型列表) [throws 异常类型]
ConstructorPattern [!] [@Annotation] [public,protected,private] [final] [类名.]new(参数类型列表) [throws 异常类型]
FieldPattern [!] [@Annotation] [public,protected,private] [static] [final] 属性类型 [类名.]属性名
TypePattern 其他 Pattern 涉及到的类型规则也是一样,可以使用 ‘!’、’‘、’..’、’ ’,’!’ 表示取反,’‘ 匹配除 . 外的所有字符串,’*’ 单独使用事表示匹配任意类型,’..’ 匹配任意字符串,’..’ 单独使用时表示匹配任意长度任意类型,’ ’ 匹配其自身及子类,还有一个 ‘…’表示不定个数。也可以使用 &&、|| 操作符

下面主要介绍下上表中的MethodPattern
MethodPattern对应的一个完整的表达式为:@注解 访问权限 返回值的类型 包名.函数名(参数)

  • @注解和访问权限(public/private/protect,以及static/final)属于可选项。如果不设置它们,则默认都会选择。以访问权限为例,如果没有设置访问权限作为条件,那么public,private,protect及static、final的函数都会进行搜索。
  • 返回值类型就是普通的函数的返回值类型。如果不限定类型的话,就用*通配符表示
  • 包名.函数名用于查找匹配的函数。可以使用通配符,包括和..以及 号。其中号用于匹配除.号之外的任意字符,而..则表示任意子package, 号表示子类。 比如:
1) java.*.Date:可以表示java.sql.Date,也可以表示java.util.Date  
2) Test*:可以表示TestBase,也可以表示TestDervied  
3) java..*:表示java任意子类
4) java..*Model :表示Java任意package中名字以Model结尾的子类,比如TabelModel,TreeModel 等  
  • 最后来看函数的参数。参数匹配比较简单,主要是参数类型,比如:
1) (int, char):表示参数只有两个,并且第一个参数类型是int,第二个参数类型是char 
2) (String, ..):表示至少有一个参数。并且第一个参数类型是String,后面参数类型不限.
3) ..代表任意参数个数和类型  
4) (Object ...):表示不定个数的参数,且类型都是Object,这里的...不是通配符,而是Java中代表不定参数的意思  

彩民之家论坛9066777 3

  • Aspect 切面:切面是切入点和通知的集合。

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

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

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

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

aspectjx插件配置

在根build.gradle下配置:

 dependencies {
        classpath 'com.hujiang.aspectjx:gradle-android-plugin-aspectjx:1.0.10'
 }

在app/build.gradle下配置:

apply plugin: 'android-aspectjx'
dependencies {
    compile 'org.aspectj:aspectjrt:1.8.9'
}

aspectjx默认会遍历项目编译后所有的.class文件和依赖的第三方库去查找符合织入条件的切点,为了提升编译效率,可以加入过滤条件指定遍历某些库或者不遍历某些库。

aspectjx {
    //织入遍历符合条件的库
    includeJarFilter 'universal-image-loader', 'AspectJX-Demo/library'
    //排除包含‘universal-image-loader’的库
    excludeJarFilter 'universal-image-loader'
}

配置注意

  1. 黑名单必须放在app/build.gralde下才生效
  2. app下的代码会自动加入白名单
  3. gradle插件版本目前不支持3.0以上
  4. 只支持AspectJ annotation的方式
  5. 建议配置白名单,否则编译时会遇到冲突

AOP 是Aspect-Oriented Progreming的缩写,在 OOP 设计中有个单一职责原则,在很多时候都不会有问题,但是当很多模块都需要同一个功能的时候,这个时候还用 OOP 就会很麻烦。那么 AOP 在Android中的应用就应运而生。

 /** * 处理切面 * * @param joinPoint * @return */ @Around("executionCheckLogin public Object checkLogin(ProceedingJoinPoint joinPoint) throws Throwable { Log.i(TAG, "checkLogin: "); MethodSignature signature = (MethodSignature) joinPoint.getSignature(); CheckLogin checkLogin = signature.getMethod().getAnnotation(CheckLogin.class); if (checkLogin != null) { Context context =  joinPoint.getThis(); if (MyApplication.isLogin) { Log.i(TAG, "checkLogin: 登录成功 "); return joinPoint.proceed(); } else { Log.i(TAG, "checkLogin: 请登录"); Toast.makeText(context, "请登录", Toast.LENGTH_SHORT).show(); return null; } } return joinPoint.proceed(); }

两种配置区别

AspectJ常规配置不支持AAR或者JAR切入的,只会对编译的代码进行织入,AspectJX插件配置支持AAR, JAR及Kotlin的应用。这里需要注意的,在AspectJ常规配置中有这样的代码:"-inpath", javaCompile.destinationDir.toString(),代表只对源文件进行织入。在查看Aspectjx源码时,发现在“-inputs”配置加入了.jar文件,使得class类可以被织入代码。这么理解来看,AspectJ也是支持对class文件的织入的,只是需要对它进行相关的配置,而配置比较繁琐,所以诞生了AspectJx等插件。

aspectjx github链接点此

那知道了这些基本上就能定义自己想要的切点了。比如我只想在 MainActivity 的 onCreate() 方法执行时作为切点。那么字符串应该是 “* com.example.administrator.aspectjdemo.MainActivity.onCreate”,那么这个切点就很明确,只有一个。

类型 描述
Before 前置通知, 在目标执行之前执行通知
After 后置通知, 目标执行后执行通知
Around 环绕通知, 在目标执行中执行通知, 控制目标执行时机
AfterReturning 后置返回通知, 目标返回时执行通知
AfterThrowing 异常通知, 目标抛出异常时执行通知

2 AspectJ

最后项目地址:

翻译 Android中的AOP编程

编写布局文件:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.luyao.aop.aspectj.AspectJActivity"
    android:orientation="vertical">

    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:onClick="shake"
        android:text="摇一摇"
        android:textSize="20sp"/>

    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:onClick="friend"
        android:textSize="20sp"
        android:text="朋友圈"/>

</LinearLayout>

彩民之家论坛9066777 4

参考如下文章,感谢作者:aspect-oriented-programming-in-android

2.3 简单使用案例

需求:如图,假设有2个功能分别是"朋友圈"和"摇一摇",功能很简单,点击按钮触发睡眠和打印日志。统计这2个功能的耗时。

彩民之家论坛9066777 5

思路:一般思路是在调用这2个方法之前后分别获取当前系统的时间戳,然后相减得到耗时,关键代码如下:

// 摇一摇点击事件处理
public void shake(View view) {
    long begin = SystemClock.currentThreadTimeMillis();

    Log.i(TAG, "进入摇一摇方法体");
    SystemClock.sleep(3000);

    long end = SystemClock.currentThreadTimeMillis();
    Log.i(TAG, "耗时:"   (end - begin));
}

// 朋友圈点击事件处理
public void friend(View view) {
    long begin = System.currentThreadTimeMillis();

    Log.i(TAG, "进入朋友圈方法体");
    SystemClock.sleep(2000);

    long end = System.currentThreadTimeMillis();
    Log.i(TAG, "耗时:"   (end - begin));
}

上面这种处理方法对于功能点少还好处理,如果很多方法都需要统计,每个方法都这样写无疑加了很大的工作量,导致代码阅读逻辑不清晰,重构不方便,违背单一原则。如果使用 AspectJ,可以通过一行注解,解决所有需要统计耗时的方法。具体代码如下:

彩民之家论坛9066777 6

创建完Aspect类之后,还需要一个注解类,它的作用是:哪里需要做切点,那么哪里就用注解标注一下,这样方便快捷。

aspectJ常规配置

在app/build.gradle下配置:

import org.aspectj.bridge.IMessage
import org.aspectj.bridge.MessageHandler
import org.aspectj.tools.ajc.Main
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'
        classpath 'org.aspectj:aspectjweaver:1.8.9'
    }
}

variants.all { variant ->
    if (!variant.buildType.isDebuggable()) {
        log.debug("Skipping non-debuggable build type '${variant.buildType.name}'.")
        return;
    }

    JavaCompile javaCompile = variant.javaCompile
    javaCompile.doLast {
        String[] args = ["-showWeaveInfo",
                         "-1.8",
                         "-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.thrown
                    break;
                case IMessage.WARNING:
                    log.warn message.message, message.thrown
                    break;
                case IMessage.INFO:
                    log.info message.message, message.thrown
                    break;
                case IMessage.DEBUG:
                    log.debug message.message, message.thrown
                    break;
            }
        }
    }
}

dependencies {
    compile files('libs/aspectjrt.jar')  //将aspectjrt.jar包拷贝至app/libs目录下
}

彩民之家论坛9066777 7

彩民之家论坛9066777 8AspectJ中的Join Point.png

使用@BehaviorAspect
public class AspectJActivity extends Activity {
    private static final String TAG = "luy";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_aspect_j);
    }

    @BehaviorTrace(value = "摇一摇", type = 1)
    public void shake(View view) {
        Log.i(TAG, "进入摇一摇方法体");
        SystemClock.sleep(3000);
    }

    @BehaviorTrace(value = "朋友圈", type = 2)
    public void friend(View view) {
        Log.i(TAG, "进入朋友圈方法体");
        SystemClock.sleep(2000);
    }
}

编写完毕,接下来测试,点击摇一摇打印日志:

拿到需要切的方法啦,执行前
进入摇一摇方法体
摇一摇(1) 耗时:3000ms

点击朋友圈打印日志:

拿到需要切的方法啦,执行前
进入朋友圈方法体
朋友圈(2) 耗时:2000ms

作者简介 原创微信公众号郭霖 WeChat ID: guolin_blog

4、Activity使用登录的注解

execution
  1. execution(* com.howtodoinjava.EmployeeManager.*( .. ))
    匹配EmployeeManger接口中所有的方法
  2. execution(* EmployeeManager.*( .. ))
    当切面方法和EmployeeManager接口在相同的包下时,匹配EmployeeManger接口中所有的方法
  3. execution(public * EmployeeManager.*(..))
    当切面方法和EmployeeManager接口在相同的包下时,匹配EmployeeManager接口的所有public方法
  4. execution(public EmployeeDTO EmployeeManager.*(..))
    匹配EmployeeManager接口中权限为public并返回类型为EmployeeDTO的所有方法。
  5. execution(public EmployeeDTO EmployeeManager.*(EmployeeDTO, ..))
    匹配EmployeeManager接口中权限为public并返回类型为EmployeeDTO,第一个参数为EmployeeDTO类型的所有方法。
  6. execution(public EmployeeDTO EmployeeManager.*(EmployeeDTO, Integer))
    匹配EmployeeManager接口中权限为public、返回类型为EmployeeDTO,参数明确定义为EmployeeDTO,Integer的所有方法。
  7. "execution(@com.xyz.service.BehaviorTrace * *(..))"
    匹配注解为"@com.xyz.service.BehaviorTrace",返回值为任意类型,任意包名下的任意方法。

郭林大神原创android 书籍:《第一行代码 android》

3、创建注解类

Advice

之前介绍的是如何找到切点,现在介绍的Advice就是告诉我们如何切,换个说法就是告诉我们要插入的代码以何种方式插入,比如说有以下几种:

名称 描述
Before 在方法执行之前执行要插入的代码
After 在方法执行之后执行要插入的代码
AfterReturning 在方法执行后,返回一个结果再执行,如果没结果,用此修辞符修辞是不会执行的
AfterThrowing 在方法执行过程中抛出异常后执行,也就是方法执行过程中,如果抛出异常后,才会执行此切面方法。
Around 在方法执行前后和抛出异常时执行(前面几种通知的综合)

ContextCompat.checkSelfPermission(context,permission) == PackageManager.PERMISSION_GRANTED 这句代码是检测权限。Android提供的类,可以直接使用。那么到这里思路差不多清晰了。以后想要做用户行为统计,或是性能检测,亦或是权限或者用户权限管理,AOP都是你的不二之选。

为什么这么配置?因为AspectJ是对java的扩展,而且是完全兼容java的。但是编译时得用Aspect专门的编译器,这里的配置就是使用Aspect的编译器,单独加入aspectj依赖是不行的。到这里准备工作已完成,可以开始看看具体实现了。

编写 Aspect
@Aspect // 此处一定要定义,否则不会该类不会参与编译
public class BehaviorAspect {

    @Pointcut("execution(@com.luyao.aop.aspectj.BehaviorTrace  * *(..))") // 定义切点
    public void annoBehavior() {
    }

    @Around("annoBehavior()") // 定义怎么切,也可以这么写 @Around("execution(@com.luyao.aop.aspectj.BehaviorTrace  * *(..))")
    public void dealPoint(ProceedingJoinPoint point) throws Throwable {
        //方法执行前
        MethodSignature methodSignature = (MethodSignature) point.getSignature();
        BehaviorTrace behaviorTrace = methodSignature.getMethod().getAnnotation(BehaviorTrace.class); // 拿到注解
        long begin = System.currentTimeMillis();
        Log.i("luy", "拿到需要切的方法啦,执行前");

        point.proceed(); // 执行被切的方法

        //方法执行完成
        long end = System.currentTimeMillis();
        Log.i("luy", behaviorTrace.value()   "("   behaviorTrace.type()   ")"   " 耗时:"    (end - begin)   "ms");
    }

}

AOP很多是拿来做用户行为统计和性能检测的。那我这里写一个手机权限检测的使用方法。

大家也看到了,代码变得简洁了,而且重复的操作越多,优势越明显,更重要的是,方便需求改变时候的修改,便于维护。在这里,我通过一个@CheckLogin注解的方式就去除了判断登录这样的操作,这是什么个情况?这就是今天为大家带来的Android AOP详解。接下来,我先为大家带来AOP的一些基础概念,再来讲解具体的实现方式。

AfterThrowing示例

如果我们经常需要收集抛出异常的方法信息,可以使用@AfterThrowing。比如我们要在任意类的任意方法抛出异常时,打印这个异常信息:

public class AspectJActivity extends Activity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        divideZero();
    }
    public void divideZero() {
        int i = 2 / 0;
    }
}

@AfterThrowing(pointcut = "call(* *..*(..))", throwing = "throwable")  // "throwable"必须和下面参数名称一样
public void anyFuncThrows(Throwable throwable) {
    Log.e("luy", "throwable--->"   throwable);   // throwable--->java.lang.ArithmeticException: divide by zero
}

反编译后的代码:

彩民之家论坛9066777 9

AfterThrowing示例

注意点:

  1. @AfterThrowing 不支持 Field -> get & set,一般用在 Method 和 Constructor
  2. 捕获的是抛出异常的方法,即使这个方法的调用方已经处理了此异常。上面例子中即使divideZero()调用了try catch, 也能被anyFuncThrows织入。

这里的说明和示例大家看看就大概明白什么意思了,具体怎么使用还是亲自去实践才行的。

AspectJ实际上是对AOP编程思想的一个实践,AOP虽然是一种思想,但就好像OOP中的Java一样,一些先行者也开发了一套语言来支持AOP。目前用得比较火的就是AspectJ了,它是一种几乎和Java完全一样的语言,而且完全兼容Java(AspectJ应该就是一种扩展Java,但它不是像Groovy那样的拓展。)。当然,除了使用AspectJ特殊的语言外,AspectJ还支持原生的Java,只要加上对应的AspectJ注解就好。所以,使用AspectJ有两种方法:

withincode

假设方法functionA, functionB都调用了dummy,但只想在functionB调用dummy时织入代码。

public void functionA() { dummy() }
public void functionB() { dummy() } 
public void dummy() {}  // 只在functionB调用的时候织入代码

@Aspect  // 加上@Aspect注解表示此类会被aspectj编译器编译,相关的Pointcut才会被织入
public class MethodTracer {
  // withincode: 在functionB方法内
  @Pointcut("withincode(void org.sdet.aspectj.MainActivity.functionB(..))")
  public void invokeFunctionB() {}

  // call: 调用dummy方法
  @Pointcut("call(void org.sdet.aspectj.MainActivity.dummy(..))")
  public void invokeDummy() {}

  // 在functionB内调用dummy方法
  @Pointcut("invokeDummy() && invokeFunctionB()")
  public void invokeDummyInsideFunctionB() {}

  // 在functionB方法内,调用dummy方法之前invoke下面代码(目前仅打印xxx)
  @Before("invokeDummyInsideFunctionB()")
  public void beforeInvokeDummyInsideFunctionB(JoinPoint joinPoint) {
    System.out.printf("Before.InvokeDummyInsideFunctionB.advice() called on '%s'", joinPoint);
  }
}

彩民之家论坛9066777 10彩民之家论坛9066777 11

Android基于AOP的非侵入式监控之——AspectJ实战

2.1 环境配置

注解其实很简单的,里面的类型并不多。想要了解的童鞋我下篇文章做个学习笔记,大家一起学习一下。

我想类似于这样的个人中心的界面,大家都不会陌生吧。那几个有箭头的地方都是可以点击进行页面跳转的,但是需要先判断用户是否登录,如果已经登录,则正常跳转,如果没有登录,则跳转到登录页面先登录,但凡是有注册,登录的APP,这样的操作,大家应该都很熟悉吧。一般情况下,我们的逻辑是这样的...

2.2 语法

通常AspectJ需要编写aj文件,然后把AOP代码放到aj后缀名文件中,如下:

public pointcut  testAll(): call(public  *  *.println(..)) && !within(TestAspect) ;  

在Android开发中,建议不要使用aj文件。因为aj文件只有AspectJ编译器才认识,而Android编译器不认识这种文件。所以当更新了aj文件后,编译器认为源码没有发生变化,不会编译它。所以AspectJ提供了一种基于注解的方法,如下:

@Pointcut(“call(public  *  *.println(..)) && !within(TestAspect)")//方法切入点
public void testAll() { }

本篇来自Single Shu888的投稿,分享了如何在Android中实现面向切面编程,希望能对大家有所帮助。

GitHub地址(欢迎下载完整Demo)

1.2 Android AOP主流框架

名称 描述
Xposed ROOT社区著名开源项目,需要root权限(运行时)
Dexposed 阿里AOP框架,改造Xposed,只支持Android2.3 - 4.4(运行时)
APT 注解处理器,通过注解生成源代码,代表框架:DataBinding,Dagger2, ButterKnife, EventBus3 、DBFlow、AndroidAnnotation
AspectJ AspectJ定义了AOP语法,所以它有一个专门的编译器用来生成遵守Java字节编码规范的Class文件,在编译期注入代码。代表框架:Hugo(Jake Wharton)
Javassist、ASM 执行字节码操作的库。它可以在一个已经编译好的类中添加新的方法,或者是修改已有的方法,可以绕过编译,直接操作字节码,从而实现代码注入。代表框架:热修复框架HotFix 、InstantRun

彩民之家论坛9066777 12

APT,AspectJ,Javassist对应的编译时期.jpg

彩民之家论坛9066777 13彩民之家论坛9066777 14

test()方法执行时就是一个切点。在执行test()时,会回调上面的CheckLoginAspectJ类的executionCheckLogin()方法。然后会执行如下方法

写在最后

本文介绍了AOP的思想、AOP的几种工具和AspectJ的基本用法。在实际开发项目中,当有需求时,了解AOP可以多一种思维方式去解决问题。同时,AspectJ织入代码会增加编译时间,使用时也需要考虑。

Android的博大精深,不是一言两语能够说明道清的,它的魅力只有亲身去接触才能体会。就像美女一样:

彩民之家论坛9066777 15个人公众号.gif

Around 示例

例如我想在"com.luyao.aop.aspectj.AspectJActivity "执行setContentView方法前后打印当前系统时间:

package com.luyao.aop.aspectj;
public class AspectJActivity extends Activity {
    private static final String TAG = "luyao";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_aspect_j);
    }
}

@Around("call(* com.luyao.aop.aspectj.AspectJActivity.setContentView(..))")
public void invokeSetContentView(ProceedingJoinPoint proceedingJoinPoint) throws Throwable{
    Log.e("luy", "执行setContentView方法前:"   System.currentTimeMillis());
    proceedingJoinPoint.proceed();
    Log.e("luy", "执行setContentView方法后:"   System.currentTimeMillis());
}

反编译后的代码:

彩民之家论坛9066777 16

彩民之家论坛9066777 17

Around 示例

文章原创作者GuoLin 书籍推荐

/** * 跳转到我的关注页面 */ @CheckLogin public void toMyAttention() { Intent intent = new Intent(this, WaitReceivingActivity.class); startActivity; }

1.1 什么是AOP,与OOP的区别

  • OOP:即ObjectOriented Programming,面向对象编程。功能都被划分到一个一个的模块里边,每个模块专心干自己的事情,模块之间通过设计好的接口交互。
  • AOP:即Aspect Oriented Programming,面向切面编程。通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。
OOP AOP
面向目标 面向名词领域 面向动词领域
思想结构 纵向结构 横向结构
注重方面 注重业务逻辑单元的划分 偏重业务处理过程的某个步骤或阶段

下图:有三个模块:登陆、转账、大文件上传,现在需要加入性能检测功能,统计这三个模块每个方法耗时多少,OOP思想做法是设计一个性能检测模块,提供接口供这三个模块调用。这样每个模块都要调用性能检测模块的接口,如果接口有改动,需要在这三个模块中每次调用的地方修改,这样做的弊端有:代码冗余,逻辑不清晰,重构不方便,违背单一原则。运用AOP的思想做法是:在这些独立的模块间,在特定的切入点进行hook,将共同的逻辑添加到模块中而不影响原有模块的独立性。如下图OOP实现转AOP实现,在不同的模块中加入性能检测功能,并不影响原有的架构。

彩民之家论坛9066777 18

OOP实现转AOP实现.png

新的一周开始了,大家早上好!

深入理解Android之AOP

Before、After示例

Before和After原理和用法一样,只是一个在方法前插入代码,一个在方法后面插入代码,在此只介绍Before。例如:在"com.luyao.aop.aspectj.AspectJActivity"执行onCreate里的代码之前打印"hello world"

package com.luyao.aop.aspectj;
public class AspectJActivity extends Activity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
    }
}

@Before("execution(* com.luyao.aop.aspectj.AspectJActivity.on*(android.os.Bundle))")
public void onActivityMethodBefore(JoinPoint joinPoint) throws Throwable {
    Log.e("luy", "hello world");
}}

查看反编译后的代码:

彩民之家论坛9066777 19

Before示例

如果使用的是以方法相关为切点,那么使用 MethodSignature 来接收 joinPoint 的 Signature。如果是属性或其他的,那么可以使用 Signature类 来接收。之后可以使用 Signature 来获取注解类。通过注解可以拿到需要检测的权限名称。但是检测权限又需要上下文,那么通过 jointPoint.getThis() 获取使用该注解的 Activity 的上下文,至于 Fragment 的我还没试过,有兴趣的自己试试。那么我们再来看看 PermissionManager 这个类。

扫描下面二维码或者公众号搜索Android老鸟开发经验谈,即可关注,持续推出优秀文章

1 前言

不同的切点可以用与或来连接。看清楚哦,前面有个*号。因为 onCreate 方法时编译时执行,所以在回调时传入的参数必须是父类 JoinPoint。上面除了 execution 还有其他很多切点类型。比如 call方法 回调时,get 和 set 分别是获取和执行时。等等,就不举例了。

彩民之家论坛9066777 20个人中心.jpg

回到我们的例子上来,创建完 Aspect类 之后,还需要一个注解类来做笔,哪里需要做切点,那么哪里就用注解标注一下,这样方便快捷。解决了 OOP 的单一原则问题。

/** * 跳转到我的关注页面 */ public void toMyAttention() { // 判断当前用户是否登录 if(LoginHelper.isLogin { // 如果登录才跳转,进入我的关注页面 Intent intent = new Intent(this, WaitReceivingActivity.class); startActivity; }else{ //跳转到登录页面,先登录 Intent intent = new Intent(this, LoginActivity.class); startActivity; } }

那么先从搭环境开始吧,待会会把 aspectJ 的jar包放在Git上,下载1.8.5.jar包之后,安装在电脑上。直接点安装即可。

Android 开发中使用 AOP

正文

  • 完全使用AspectJ的语言。这语言一点也不难,和Java几乎一样,也能在AspectJ中调用Java的任何类库。AspectJ只是多了一些关键词罢了。
  • 或者使用纯Java语言开发,然后使用AspectJ注解,简称@AspectJ

至于为什么这么配置,AspectJ 是对java的扩展,而且是完全兼容java的。但是编译时得用 Aspect 专门的编译器,这里的配置就是使用 Aspect 的编译器。这里还在 libs 导入了一个jar包。创建依赖。

项目需求描述

首先创建一个类,用来处理触发切面的回调:

先给出我解决了上述问题之后的代码

在哪使用呢。方法名和属性名上都可以。

2、创建切面AspectJ用来处理触发切面的回调

完。。。。。。。。。。。。。。。。。。。。。

@Aspectpublic class CheckLoginAspectJ { private static final String TAG = "CheckLogin"; /** * 找到处理的切点 * * * 可以处理CheckLogin这个类所有的方法 */ @Pointcut("execution(@com.zx.aopdemo.login.CheckLogin * * public void executionCheckLogin() { } /** * 处理切面 * * @param joinPoint * @return */ @Around("executionCheckLogin public Object checkLogin(ProceedingJoinPoint joinPoint) throws Throwable { Log.i(TAG, "checkLogin: "); MethodSignature signature = (MethodSignature) joinPoint.getSignature(); CheckLogin checkLogin = signature.getMethod().getAnnotation(CheckLogin.class); if (checkLogin != null) { Context context =  joinPoint.getThis(); if (MyApplication.isLogin) { Log.i(TAG, "checkLogin: 登录成功 "); return joinPoint.proceed(); } else { Log.i(TAG, "checkLogin: 请登录"); Toast.makeText(context, "请登录", Toast.LENGTH_SHORT).show(); return null; } } return joinPoint.proceed(); }}

我先在这里申明一下,我这篇 AOP 只是学习笔记,适合入门级选手,如果阁下是大神想要参考 AOP,那么我给个我认为写的很好的 AOP 链接:

在Pointcut这里,我使用了execution,也就是以方法执行时为切点,触发Aspect类。而execution里面的字符串是触发条件,也是具体的切点。我来解释一下参数的构成。“execution(@com.zx.aopdemo.login.CheckLogin * *”这个条件是所有加了CheckLogin注解的方法或属性都会是切点,范围比较广。

彩民之家论坛9066777 21

apply plugin: 'com.android.application'import org.aspectj.bridge.IMessageimport org.aspectj.bridge.MessageHandlerimport org.aspectj.tools.ajc.Mainbuildscript { repositories { mavenCentral() } dependencies { classpath 'org.aspectj:aspectjtools:1.8.9' classpath 'org.aspectj:aspectjweaver:1.8.9' }}repositories { mavenCentral()}final def log = project.loggerfinal 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.javaCompile javaCompile.doLast { String[] args = ["-showWeaveInfo", "-1.8", "-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 MessageHandler handler = new MessageHandler; 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.thrown break; case IMessage.WARNING: log.warn message.message, message.thrown break; case IMessage.INFO: log.info message.message, message.thrown break; case IMessage.DEBUG: log.debug message.message, message.thrown break; } } }}android { compileSdkVersion 25 buildToolsVersion "25.0.2" defaultConfig { applicationId "com.zx.aopdemo" minSdkVersion 17 targetSdkVersion 25 versionCode 1 versionName "1.0" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } }}dependencies { compile fileTree(dir: 'libs', include: ['*.jar']) androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', { exclude group: 'com.android.support', module: 'support-annotations' }) compile 'com.android.support:appcompat-v7:25.3.1' compile 'com.android.support.constraint:constraint-layout:1.0.2' compile 'org.aspectj:aspectjrt:1.8.9' testCompile 'junit:junit:4.12'}

再来看看 @Around,Around 是指 JPoint 执行前或执行后备触发,而 around 就替代了原 JPoint。除了 Around 还有其他几种方式。

基础概念

彩民之家论坛9066777 22

“com.zx.aopdemo.login.CheckLogin”这是我的项目包名下需要指定类的绝对路径。再来看看@Around,Around是指JPoint执行前或执行后被触发,除了Around还有其他几种方式。

彩民之家论坛9066777 23

这段代码确实没有任何问题,也完全符合我们的需求,也就是在所有需要判断登录的地方去if else做重复的逻辑操作,但是如果这样的判断有10多处,甚至几十处,我们就得重复很多次这样的体力劳动,或者有一天需求变动,我们估计要改动多处,想想都可怕。而且类似的还有网络判断,权限管理,Log日志的统一管理这样的问题。那么,我们也没有更优雅的方式来解决这一类的问题呢,答案是有的,烦请各位接着往下看。

在我的例子中,我使用了 execution,也就是以方法执行时为切点,触发 Aspect类。而 execution 里面的字符串是触发条件,也是具体的切点。我来解释一下参数的构成。“execution(@com.example.administrator.aspectjdemo.AspectJAnnotation * *” 这个条件是所有加了 AspectJAnnotation 注解的方法或属性都会是切点,范围比较广。

如果使用的是以方法相关为切点,那么使用MethodSignature来接收joinPoint的Signature。如果是属性或其他的,那么可以使用Signature类来接收。之后可以使用Signature来获取注解类。,那么通过jointPoint.getThis()获取使用该注解的的上下文对象

那么我们先来说说什么是 AOP。说到 AOP 也许不太了解,那么 OOP 一定是知道的。Object-Oriented Progreming面向对象编程。学Java基础的时候肯定都都会学面向对象思想和三大特性。这里就不详细介绍了。

这里要使用Aspect的编译器编译必须给类打上标注,@Aspect

com.example.administrator.aspectjdemo.AspectJAnnotation:这是我的项目包名下需要指定类的绝对路径

彩民之家论坛9066777 24

下一步下一步就好。这里我只介绍 Android Studio 的。Eclipse 也是可以使用的。Studio 就好配置了,在 Gradle 中进行配置。贴一下我的 Gradle 配置:

我就是参照他的博客和官网例子学习的。

..:表示任意类型任意多个参数

彩民之家论坛9066777 25

**:表示是任意包名

Single Shu888的博客地址:

淘宝链接:

彩民之家论坛9066777 26

彩民之家论坛9066777 27

版权声明:本文由彩民之家高手论坛发布于编程技术,转载请注明出处:AOP面向切面编程【彩民之家论坛9066777】