AOP架构之路-AspectJ

来源:互联网 发布:lol比赛视频软件 编辑:程序博客网 时间:2024/06/07 15:53

1. 什么是AOP

AOP(Aspect-Oriented Programming,面向方面编程),可以说是OOP(Object-Oriented Programing,面向对象编程)的补充和完善。OOP引入封装、继承和多态性等概念来建立一种对象层次结构,用以模拟公共行为的一个集合。当我们需要为分散的对象引入公共行为的时候,OOP则显得无能为力。也就是说,OOP允许你定义从上到下的关系,但并不适合定义从左到右的关系。例如日志功能。日志代码往往水平地散布在所有对象层次中,而与它所散布到的对象的核心功能毫无关系。对于其他类型的代码,如Android中的(Log代码、ptag/pv上报代码、监控、权限检查)也是如此。这种散布在各处的无关的代码被称为横切(cross-cutting)代码,在OOP设计中,它导致了大量代码的重复,而不利于各个模块的重用。

而AOP技术则恰恰相反,它利用一种称为“横切”的技术,剖解开封装的对象内部,并将那些影响了多个类的公共行为封装到一个可重用模块,并将其名为 “Aspect”,即方面。所谓“方面”,简单地说,就是将那些与业务无关,却为业务模块所共同,便于减少系统的重复代码,降低 模块间的耦合度,并有利于未来的可操作性和可维护性。AOP代表的是一个横向的关系,如果说“对象”是一个空心的圆柱体,其中封装的是对象的属性和行为; 那么面向方面编程的方法,就仿佛一把利刃,将这些空心圆柱体剖开,以获得其内部的消息。而剖开的切面,也就是所谓的“方面”了。然后它又以巧夺天功的妙手 将这些剖开的切面复原,不留痕迹。

1.1 AOP的横切

AOP把软件系统分为两个部分:核心关注点和横切关注点。业务处理的主要流程是核心关注点,与之关系不大的部分是横切关注点。横切关注点的一个特点是,他们经常发生在核心关注点的多处,而各处都基本相似。AOP的作用在于分离系统中的各种关注点,将核心关注点和横切关注点分离开来。在Android App中,哪些是我们需要的横切关注点?个人认为主要包括以下几个方面: Log代码、ptag/pv上报代码、监控、权限检查等。

2.什么是AspectJ?

AspectJ 意思就是Java的Aspect,Java的AOP。它其实不是一个新的语言,它就是一个代码编译器(ajc,后面以此代替),在Java编译器的基础上增加了一些它自己的关键字识别和编译方法。因此,ajc也可以编译Java代码。它在编译期将开发者编写的Aspect程序编织到目标程序中,对目标程序作了重构,目的就是建立目标程序与Aspect程序的连接(耦合,获得对方的引用(获得的是声明类型,不是运行时类型)和上下文信息),从而达到AOP的目的(这里在编译期还是修改了原来程序的代码,但是是ajc替我们做的)。

1、非侵入式监控: 可以在不修监控目标的情况下监控其运行,截获某类方法,甚至可以修改其参数和运行轨迹!
2、学习成本低: 它就是Java,只要会Java就可以用它。
3、功能强大,可拓展性高: 它就是一个编译器+一个库,可以让开发者最大限度的发挥,实现形形色色的AOP程序!

首先需要在build.gradle添加配置
下面是我的配置

import com.android.build.gradle.LibraryPluginimport org.aspectj.bridge.IMessageimport org.aspectj.bridge.MessageHandlerimport org.aspectj.tools.ajc.Mainapply plugin: 'com.android.library'android {    compileSdkVersion 23    buildToolsVersion "23.0.2"    defaultConfig {        minSdkVersion 14        targetSdkVersion 19        versionCode 1        versionName "1.0"        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"    }    buildTypes {        release {            minifyEnabled false            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'        }    }}buildscript {    repositories {        mavenCentral()    }    dependencies {        classpath 'com.android.tools.build:gradle:2.1.3'        classpath 'org.aspectj:aspectjtools:1.8.9'        classpath 'org.aspectj:aspectjweaver:1.8.9'    }}android.libraryVariants.all { variant ->    LibraryPlugin plugin = project.plugins.getPlugin(LibraryPlugin)    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", plugin.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;            }        }    }}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'    })    testCompile 'junit:junit:4.12'    compile 'com.android.support:support-v4:23.2.0'    compile 'org.aspectj:aspectjrt:1.8.9'}

日志类

package com.hao.aoplibrary;/** * 创建一个日志信息 * @author ZHANGHAOHAO * @date 2017/3/29 */public class BuildMessage {    public static String buildLogMessage(String clazzSimpleName, String methodName, double methodDuration) {        StringBuilder message = new StringBuilder();        message.append(clazzSimpleName);        message.append(":");        message.append(methodName);        message.append(" --> ");        message.append("[");        message.append(methodDuration);        message.append("ms");        message.append("]      \n");        return message.toString();    }}

监控类

package com.hao.aoplibrary;import java.util.concurrent.TimeUnit;public class StopWatch {    private long startTime;    private long endTime;    private long elapsedTime;    public StopWatch() {    }    private void reset() {        startTime = 0;        endTime = 0;        elapsedTime = 0;    }    public void start() {        reset();        startTime = System.nanoTime();    }    public void stop() {        if (startTime != 0) {            endTime = System.nanoTime();            elapsedTime = endTime - startTime;        } else {            reset();        }    }    public long getTotalTimeMillis() {        return (elapsedTime != 0) ? TimeUnit.NANOSECONDS.toMillis(endTime - startTime) : 0;    }}

一、实现一个自定义注解,截获注解做操作

首先定义一个注解类

package com.hao.aoplibrary.anno;import java.lang.annotation.Documented;import java.lang.annotation.ElementType;import java.lang.annotation.Retention;import java.lang.annotation.RetentionPolicy;import java.lang.annotation.Target;/** * 自定义注解 * * @author ZHANGHAOHAO089 * @date 2017/3/29 */@Target(ElementType.METHOD)@Documented@Retention(RetentionPolicy.RUNTIME)public @interface MyAnnotation {    String name();}

AspectJ实现截获类

package com.hao.aoplibrary;import android.util.Log;import com.hao.aoplibrary.anno.MyAnnotation;import org.aspectj.lang.ProceedingJoinPoint;import org.aspectj.lang.annotation.Around;import org.aspectj.lang.annotation.Aspect;import org.aspectj.lang.annotation.Pointcut;import org.aspectj.lang.reflect.MethodSignature;import java.lang.reflect.Method;/** * 自定义注解实例 * * @author ZHANGHAOHAO089 * @date 2017/3/29 */@Aspectpublic class AspectAnno {    @Around("execution(!synthetic * *(..)) && onLogMethod()")    public Object doLogMethod(final ProceedingJoinPoint joinPoint) throws Throwable {        return logMethod(joinPoint);    }    @Pointcut("@within(com.hao.aoplibrary.anno.MyAnnotation)||@annotation(com.hao.aoplibrary.anno.MyAnnotation)")    public void onLogMethod() {    }    private Object logMethod(ProceedingJoinPoint joinPoint) throws Throwable {        MethodSignature signature = (MethodSignature) joinPoint.getSignature();        Method method = signature.getMethod();        MyAnnotation myAnnotation = method.getAnnotation(MyAnnotation.class);        if (myAnnotation!=null) {            String name = myAnnotation.name();            Log.i("ZH-Anno", "name--->" + name);            //TODO 根据传进来的值做操作        }        Object result = joinPoint.proceed();        return result;    }}

二、截获原方法,并替换

package com.hao.aoplibrary;import android.util.Log;import org.aspectj.lang.ProceedingJoinPoint;import org.aspectj.lang.annotation.Around;import org.aspectj.lang.annotation.Aspect;import org.aspectj.lang.annotation.Pointcut;import static com.hao.aoplibrary.BuildMessage.buildLogMessage;/** * 截获某包下的的所有非static方法(static方法另外加一个static修饰的execution或者call即可:execution( static * *..Activity+.*(..)) * * @author ZHANGHAOHA * @date 2017/3/29 */@Aspectpublic class AspectAround {    private static final String POINTCUT_METHOD = "execution(* com.hao.myaoptest.*.*(..)) && target(Object) && this(Object)";    @Pointcut(POINTCUT_METHOD)    public void methodAnnotated() {}    /**     * 截获原方法,并替换     */    @Around("methodAnnotated()")    public Object weaveJoinPoint(ProceedingJoinPoint joinPoint) throws Throwable {        //初始化计时器        StopWatch stopWatch = new StopWatch();        //开始监听        stopWatch.start();        //调用原方法的执行。        Object result = joinPoint.proceed();        //监听结束        stopWatch.stop();        String classType = joinPoint.getTarget().getClass().getName();        Class<?> clazz = Class.forName(classType);        String clazzSimpleName = clazz.getSimpleName();        String methodName = joinPoint.getSignature().getName();        String msg = buildLogMessage(clazzSimpleName, methodName, stopWatch.getTotalTimeMillis());        Log.i("ZH-AROUND", msg);        return result;    }}

三、截获方法,在方法之前和之后做一些操作

package com.hao.aoplibrary;import android.util.Log;import org.aspectj.lang.JoinPoint;import org.aspectj.lang.annotation.After;import org.aspectj.lang.annotation.Aspect;import org.aspectj.lang.annotation.Before;import org.aspectj.lang.annotation.Pointcut;import static com.hao.aoplibrary.BuildMessage.buildLogMessage;/** * call实例 * * @author ZHANGHAOHA * @date 2017/3/29 */@Aspectpublic class AspectCall {    StopWatch stopWatch;    private static final String POINTCUT_METHOD = "call(* com.hao.myaoptest.MainActivity.initView(..)) && target(Object) && this(Object)";    @Pointcut(POINTCUT_METHOD)    public void methodCall() {}    /**     * 在执行方法之前执行     */    @Before("methodCall()")    public void beforeCall(JoinPoint joinPoint) throws Throwable {//        //初始化计时器        stopWatch = new StopWatch();//        //开始监听        stopWatch.start();        String classType = joinPoint.getTarget().getClass().getName();        Class<?> clazz = Class.forName(classType);        String clazzSimpleName = clazz.getSimpleName();        String methodName = joinPoint.getSignature().getName();        Log.i("ZH-before-CALL", clazzSimpleName + ":" + methodName);    }    /**     * 在执行方法之后执行     */    @After("methodCall()")    public void afterCall(JoinPoint joinPoint) throws Throwable {        //监听结束        stopWatch.stop();        String classType = joinPoint.getTarget().getClass().getName();        Class<?> clazz = Class.forName(classType);        String clazzSimpleName = clazz.getSimpleName();        String methodName = joinPoint.getSignature().getName();        String msg = buildLogMessage(clazzSimpleName, methodName, stopWatch.getTotalTimeMillis());        Log.i("ZH-after-CALL", msg);    }}

追究其原理

在编译期对目标对象、方法做标记,对目标类、方法进行重构,将PointCut插入目标中,截获该目标的信息以及上下文环境,以达到非侵入代码监控的目的——注意,它只能获得对象的声明,如果对象的声明式接口,那么默认情况下(不使用this、target约束切点),获取的是声明类型,而不是具体运行时的类。

1、编写Aspect:声明Aspect、PointCut和Advise。

2、ajc编织: AspectJ编译器在编译期间对所切点所在的目标类进行了重构,在编译层将AspectJ程序与目标程序进行双向关联,生成新的目标字节码,即将AspectJ的切点和其余辅助的信息类段插入目标方法和目标类中,同时也传回了目标类以及其实例引用。这样便能够在AspectJ程序里对目标程序进行监听甚至操控。

3、execution: 顾名思义,它截获的是方法真正执行的代码区,Around方法块就是专门为它存在的。调用Around可以控制原方法的执行与否,可以选择执行也可以选择替换。

4、call: 同样,从名字可以看出,call截获的是方法的调用区,它并不截获代码真正的执行区域,它截获的是方法调用之前与调用之后(与before、after配合使用),在调用方法的前后插入JoinPoint和before、after通知。它截获的信息并没有execution那么多,它无法控制原来方法的执行与否,只是在方法调用前后插入切点,因此它比较适合做一些轻量的监控(方法调用耗时,方法的返回值等)。

5 、Around替代原理:目标方法体被Around方法替换,原方法重新生成,名为XXX_aroundBody(),如果要调用原方法需要在AspectJ程序的Around方法体内调用joinPoint.proceed()还原方法执行,是这样达到替换原方法的目的。达到这个目的需要双方互相引用,桥梁便是Aspect类,目标程序插入了Aspect类所在的包获取引用。AspectJ通过在目标类里面加入Closure(闭包)类,该类构造函数包含了目标类实例、目标方法参数、JoinPoint对象等信息,同时该类作为切点原方法的执行代理,该闭包通过Aspect类调用Around方法传入Aspect程序。这样便达到了关联的目的,便可以在Aspect程序中监控和修改目标程序。

6 、 Before与After: Before与After只是在方法被调用前和调用之后添加JoinPoint和通知方法(直接插入原程序方法体中),调用AspectJ程序定义的Advise方法,它并不替代原方法,是在方法call之前和之后做一个插入操作。After分为returnning和throwing两类,前者是在正常returning之后调用,后者是在throwing发生之后调用。默认的After是在finally处调用,因此它包含了前面的两种情况。

7 、重要关键字 : 在其它关键字中,必须要注意的就是this、target的使用和区别,同时还有一个很重要的方法Signature.getDeclaringType() AspectJ是在编译期截获的对象信息,因此它获得的标签只是对象的声明(比如:接口、抽象类),而不是运行时具体的对象。如果想要获得运行时对象,就需要用this、target关键字

学习之初学习的blog
http://blog.csdn.net/innost/article/details/49387395
http://blog.csdn.net/crazy__chen/article/details/52014672
http://blog.csdn.net/woshimalingyi/article/details/51476559
https://yq.aliyun.com/articles/58739

0 0