AOP的应用

引言

AOP,即面向切面编程,是对OOP的补充。在2016年我第一次看到AOP,但并未深入去学习,只是默默的记在自己的技术堆栈txt上,归类到“高级技术”(自我提升第三阶段)那一块。然而在最近2个月学习设计模式的过程中,在《设计模式之禅完整版 秦晓波》中的“代理模式”中又看到了,并初步了解AOP的实现原理。就在上周看网上公开课的视频时发现有一个“AOP面向切面设计编程”,接着后续的1个半小时就在看视频中度过,看完收获满满。
经过记录、整理后与大家分享下(这里IDE为AS)。


实战

配置Aspectj依赖

下载Aspectj.jar

前往官网:http://www.eclipse.org/aspectj/downloads.php下载最新的jar包,然后解压出aspectj.jar文件;

导包
  1. copy aspectj.jar到项目的lib文件夹下
  2. 添加依赖
gradle配置

这里有必要提一下,使用AOP时,我们是用aspectj替换javac进行java文件的编译,因此在gradle的配置上会比较麻烦点。
后面给的是官网的配置地址: http://fernandocejas.com/2014/08/03/aspect-oriented-programming-in-android/
但是这个已经太久没更新了,而且我试过了,不能用了。下面来看看我查到的可以用的配置:
在module的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'
        classpath 'org.aspectj:aspectjweaver:1.8.9'
    }
}

apply plugin: 'com.android.application'
//这里不需要官方的那个plugin
repositories {
    mavenCentral()
}
android {
    compileSdkVersion 26
    buildToolsVersion "26.0.1"
    defaultConfig {
        applicationId "com.example.aopproject"
        minSdkVersion 15
        targetSdkVersion 26
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
    lintOptions {
        abortOnError false
    }
}

dependencies {
    compile fileTree(include: ['*.jar'], dir: 'libs')
    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:26.0.0-alpha1'
    compile 'com.android.support.constraint:constraint-layout:1.0.2'
    testCompile 'junit:junit:4.12'
    compile files('libs/aspectjrt.jar')
}


final def log = project.logger
final def variants = project.android.applicationVariants

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",//这个是jdk的版本
                         "-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;
            }
        }
    }
}

自定义注解

这里需要一些注解的知识,小伙伴们不熟的可能需要去补一补:)
代码如下:

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)//运行时可以通过得到,如果Class则无法再Around里面获取相关的信息
public @interface BehaviorAnn {
    String value();
    int level();
}

这里需要注意的是,如果在@Retention中使用RetentionPolicy.CLASS的话,那么后续如果你想获取自定义注解是不可行的,这个我是被视频坑了,不知道为什么视频里写CLASS居然也能获取==!。

注解解释器

既然有自定义注解,那必然也会有对应的解释器了,而且核心的操作基本都在这进行处理了。
下面让我们一步一步来写解释器。

@Aspect

给解释器类需要添加@Aspect注解。

@Aspect
public class BehaviorAspect {
...    
}

@Pointcut

获取切点。

private static final String POINTCUT_METHOD = "execution(@com.example.aopproject.aop.BehaviorAnn  * *(..))";

//获取切点
@Pointcut(POINTCUT_METHOD)
public void annBehavior(){
 //空的
}

这里对POINTCUT_METHOD立面的进行下说明。
用过正则表达式的可能会熟悉点,那2个其实就是个通配符,这2个号的前面至少需要一个空格。具体如下:

com.example.aopproject.aop.BehaviorAnn: 要处理的注解
第一个*号:代表所有的类
第二个*号:代表所有方法
(..):代表匹配所有的形参

下面举例说明:

execution(@com.example.aopproject.BehaviorAnn  * sayGoodbye(..)):匹配sayGoodbye方法,参数不限制--方法前面有个空格,官方是用一个.来代替空格
execution(@com.example.aopproject.BehaviorAnn * *(int)):匹配形参只有一个int
execution(@com.example.aopproject.BehaviorAnn * *(String,int)):匹配形参第一个为String,第二个为int的方法

通知处理

分类

@AspectJ 注解提供六种通知Advice类型:

@Before:前置通知
@Around:环绕通知
@AfterThrowing:异常抛出通知
@@AfterReturning:后置通知
@After:最终通知,相当于代码 finally(之前没有的)

所谓的前置通知其实也就是在调用方法之前先调用该方法,后置同理。而环绕则可以自定义先后顺序,需要调用原来的方法的时候就调用point.proceed()即可。

@Around的使用

上代码:

@Around("annBehavior()")//括号里的就是前面空实现的annBehavior(),当然直接用POINTCUT_METHOD也是可以的
public Object dealPoint(ProceedingJoinPoint point)
    throws Throwable {
    // 方法执行前
    MethodSignature methodSignature = (MethodSignature) point.getSignature();// 获取方法签名
    BehaviorAnn behaviorTrace = methodSignature.getMethod().getAnnotation(BehaviorAnn.class);// 获取注解(不是RUNTIME获取不到的)
    int level = behaviorTrace.level();// 调用注解的方法
    String className = methodSignature.getDeclaringType().getSimpleName();
    String methodName = methodSignature.getName();

    // 方法执行时
    final TimeWatcher stopWatch = new TimeWatcher();
    stopWatch.start();

    Object object = null;
        try {
            object = point.proceed();
        } catch (Exception e) {
            e.printStackTrace();
        }

    stopWatch.stop();

    // 方法执行完成
    DebugLog.log(className, buildLogMessage(methodName, stopWatch.getTotalTimeMillis()));
    return object;
}

需要注意的一下地方也在代码中指明了。

@AfterThrowing和@AfterReturning

下面看下@AfterThrowing和@AfterReturning的用法:

@AfterThrowing(value = POINTCUT_START_METHOD, throwing = "ex")
public void AfterThrowing(Throwable ex) {//和括号中的保持一致
    Log.d(TAG, "AfterThrowing:" + ex.getMessage());
}

@AfterReturning(value = POINTCUT_START_METHOD, returning = "result")
public void AfterReturning(JoinPoint point, Object result) {//和括号中的保持一致
    Log.d(TAG, ((String) result));
    Log.d(TAG, "AfterReturning");
}

自定义注解的应用

最后就是给我们需要检测的方法添加上自定义的注解。

@BehaviorAnn("问候")
private void greet() {
    SystemClock.sleep(3000L);
    Log.d(TAG, "How are U?");
}

小结

1.按我个人理解,整个AOP的过程简单来说就是遇到有自定义注解的会将其作为切点,直接切入到Aspect中的进行通知处理,而通知处理中的point.proceed()其实就是回到了自身方法的实现。
2. 方法中传入如果传入JoinPoint参数就可以获得拦截的切点信息。
3. 关于这几个处理的最好是根据需要选择其中的部分或者全部方法,按照下面的顺序编写代码Before—Around—AfterThrowing—AfterReturning—After。小伙伴可以尝试换下顺序,打印日志进行观察结果。
4. AOP主要的使用场景:①日志记录②权限管理③事务管理④性能监测⑤并发⑥错误处理。自后会给个本文性能监测(打印方法运行耗时)的demo,至于其他几个场景的应用(尤其是权限管理),希望有demo的小伙伴可以分享下。
5. 如果想了解点Spring中的aop,可以前往http://blog.csdn.net/javaliuzhiyue/article/details/9357849
6. 本文的小demo下载地址:AOP例子

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值