简介

AspectJ是一个面向切面的框架,它扩展了Java语言。AspectJ定义了AOP语法,它有一个专门的编译器用来生成遵守Java字节编码规范的Class文件.利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。

主要功能

数据埋点,日志记录,性能统计,安全控制,事务处理,异常处理等等

目的(为什么要用AspectJ)

将日志记录,性能统计,安全控制,事务处理,异常处理等代码从业务逻辑代码中划分出来,通过对这些行为的分离,我们希望可以将它们独立到非指导业务逻辑的方法中,进而改变这些行为的时候不影响业务逻辑的代码。

原理

AspectJ在代码的编译期间扫描目标程序,根据切点(PointCut)匹配,将开发者编写的Aspect程序编织(Weave)到目标程序的.class文件中,对目标程序作了重构(重构单位是JoinPoint),目的就是建立目标程序与Aspect程序的连接(获得执行的对象、方法、参数等上下文信息),从而达到AOP的目的。

Gradle 配置示例

要引入AspectJ到Android工程中,最重要的就是两个包:

//在buildscript中添加该编织器,gradle构建时就会对class文件进行编织 //在buildscript中添加该工具包,在构建工程的时候执行一些任务:打日志等

classpath 'org.aspectj:aspectjtools:1.8.11'
classpath 'org.aspectj:aspectjweaver:1.8.9'
复制代码

//在dependencies中添加该依赖,提供@AspectJ语法

compile 'org.aspectj:aspectjrt:1.8.9'
复制代码

//在app中的gradle

import org.aspectj.bridge.IMessage
import org.aspectj.bridge.MessageHandler
import org.aspectj.tools.ajc.Main
复制代码

//打印gradle日志

android.applicationVariants.all { variant ->
    JavaCompile javaCompile = variant.javaCompile
    javaCompile.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)]

        MessageHandler handler = new MessageHandler(true);
        new Main().run(args, handler)

        def log = project.logger
        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:
                case IMessage.INFO:
                    log.info message.message, message.thrown
                    break;
                case IMessage.DEBUG:
                    log.debug message.message, message.thrown
                    break;
            }
        }
    }
}
复制代码

AspectJ官网配置

fernandocejas.com/2014/08/03/…

语法

切面——Aspect

在AOP中切面是标记执行被切点标记方法的逻辑处理。就是切点要处理的具体逻辑在切面这个模块。

连接点——JoinPoint

它是切面插入执行应用程序的地方,他能被方法调用,也能新增方法,JoinPoint是一个执行链,每一个JoinPoint是一个单独的闭包,在执行的时候将上下文环境赋予闭包执行方法体逻辑。

AspectJ的joinpoint有如下:

method call 函数调用
method execution 函数执行
constructor call 构造函数调用
constructor execution 构造函数执行
field get 获取某个变量
field set 设置某个变量
pre-initialization Object在构造函数中做得一些工作。
initialization Object在构造函数中做得工作
static initialization 类初始化
handler 异常处理,比如try catch(xxx)中,对应catch内的执行
advice execution 里面有3个标记(Around Before After)
复制代码

切点——PointCut

切点的声明决定需要切割的JoinPoint的集合,Pointcut可以控制你把哪些advice应用于JoinPoint上去,通常通过正则表达式来进行匹配应用,决定了那个jointpoint会获得通知。分为call、execution、target、this、within等关键字等。

within(TypePattem) TypePattern标示package或者类。TypePatter可以使用通配符
withincode(ConstructorSignaturelMethodSignature) 表示某个构造函数或其他函数执行过程中涉及到的JPoint
cflow(pointcuts) cflow是call flow的意思,cflow的条件是一个pointcut
cflowbelow(pointcuts)  表示调用pointcuts函数时所包含的JPoint,不包括pointcuts这个JPoint本身
this(Type)  JPoint的this对象是Type类型
target(Type) JPoint的target对象是Type类型
args(TypeSignature) 用来对JPoint的参数进行条件搜索的
复制代码

匹配规则

(1)类型匹配语法 类型匹配的通配符: *:匹配任何数量字符; ..:匹配任何数量字符的重复,如在类型模式中匹配任何数量子包;而在方法参数模式中匹配任何数量参数。 +:匹配指定类型的子类型;仅能作为后缀放在类型模式后边。 AspectJ使用 且(&&)、或(||)、非(!)来组合切入点表达式。

(2)匹配模式 call(<注解?> <修饰符?> <返回值类型> <类型声明?>.<方法名>(参数列表) <异常列表>?)

精确匹配
//表示匹配 com.sunmi.MainActivity类中所有被@Describe注解的public void方法。
@Pointcut("call(@Describe public void com.sunmi.MainActivity.init(Context))")
public void pointCut(){}

单一模糊匹配
//表示匹配 com.sunmi.MainActivity类中所有被@Describe注解的public void方法。
@Pointcut("call(@Describe public void com.sunmi.MainActivity.*(..)) ")
public void pointCut(){}

//表示匹配调用Toast及其子类调用的show方法,不论返回类型以及参数列表,并且该子类在以com.sunmi开头的包名内
@Pointcut("call(* android.widget.Toast+.show(..)) && (within(com.sunmi..*))")
public void toastShow() {
}

组合模糊匹配
//表示匹配任意Activity或者其子类的onStart方法执行,不论返回类型以及参数列表,且该类在com.sunmi包名内
@Pointcut("execution(* *..Activity+.onStart(..))&& within(com.sunmi.*)")
public void onStart(){}
复制代码

示例代码

有注解

声明一个注解 AopPoint

@Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.METHOD)
    public @interface AopPoint {
        String value();
        int type() default 0;
    }
复制代码

在Activity 中,定义了2个按钮的点击事件,在方法上都标记注解,指定了value 和type。

@AopPoint(value = "首页点击",type = 1)
    public void doFunc1(View view) {
        SystemClock.sleep(1000);
    }

    @AopPoint("分类点击")
    public void doFunc2(View view) {
        SystemClock.sleep(1000);
    }
复制代码

编写一个切面类

@Aspect
    public class AopAspect {
    @Pointcut("execution(@com.example.davis.aspectdemo.AopPoint * *(..))")
    public void aopCut(){

    }
    @Around("aopCut()")
    public Object dealMethod(ProceedingJoinPoint joinPoint) throws Throwable {

        MethodSignature signature= (MethodSignature) joinPoint.getSignature();
        AopPoint aopPoint=signature.getMethod().getAnnotation(AopPoint.class);

        String value=aopPoint.value();
        int type=aopPoint.type();

        TimeTool timeTool=new TimeTool();
        timeTool.start();

        Object result=joinPoint.proceed();

        timeTool.stop();
        Log.e("aopCut",value+" 执行时间="+timeTool.getTotalTimeMillis() +" type类型是"+type);

        return result;
    }
    }
复制代码

结果



无注解

1、这个是扫描Activity 中onCreate方法的调用

@Aspect
    public class FuncTAspect {

    @Before("execution(* android.app.Activity.onCreate(..))")
    public void onActivityMethodBefore(JoinPoint joinPoint) throws Throwable {
        String key = joinPoint.getSignature().toString();

        Log.e("FuncTAspect", "onActivityMethodBefore: " + key+"\n"+joinPoint.getThis());
    }
    }
复制代码

结果只能扫描到onCreate方法

如果把onCreate改成通配符* android.app.Activity.onCreate(..) 改成android.app.Activity.*(..)

结果



2、捕获catch异常

@Aspect
    public class ExceptionHandleAspect {

    private static final String TAG = "ExceptionHandleAspect";

    /**
     * 截获空指针异常
     *
     * @param e
     */
    @Pointcut("handler(java.lang.Exception)&&args(e)")
    public void handle(Exception e) {
    }

    /**
     * 在catch代码执行之前做一些处理
     *
     * @param joinPoint
     * @param e         异常参数
     */
    @Before(value = "handle(e)", argNames = "e")
    public void handleBefore(JoinPoint joinPoint, Exception e) {
        Log.e(TAG, joinPoint.getSignature().toLongString() + " handleBefore() :" + e.toString());
    }
    }
复制代码

在方法里制造一个Null指针

public void doFunc1(View view) {
        try {
            AppItem appItem=null;
            appItem.number=2;
        }catch (Exception e){}

    }
复制代码

结果



代码

github.com/xusoku/Aspe…

参考链接

AOP之AspectJ 技术原理详解及实战总结