AOP编程

一、基本概念介绍

AOP全称Aspect Oriented Programming,即面向切面编程。通过预编译方式或者运行期动态代理等实现程序功能统一维护的一种技术,利用AOP可以实现对业务逻辑中不同部分进行分离,从而降低耦合,提高程序的可复用性,主要用于实现日志记录、性能统计、埋点统计、安全控制、异常处理等。

在没有AOP之前,如果各个模块都要打印日志,就要自己处理,都要认为编写日志代码。若以后日志模块修改API,则使用它们的地方都要改。AOP的目标就是解决上述提到的这种麻烦的问题。AOP的目标就是把这些功能集中起来,放在一个统一的地方来控制和管理。比如我们可以设置Aspect,来管理软件中一些特殊函数调用的权限问题,如检验登录流程等。

1.术语

  • 横切关注点(Cross-cutting concerns)
    在面对对象编程中,我们希望在不同的模块代码中添加一些类似的代码,如希望在数据访问层中类中添加日志,或者在View的点击处添加点击事件的埋点统计。横切关注点就是那些经常发生的在业务逻辑处理中需要关注的地方,在代码中添加一些相同的附属功能等。

  • 连接点(Joint point)
    程序中可能作为代码注入目标的点,如方法调用或者方法入口。

  • 通知(Advice)
    注入到class文件中的代码。典型的有before(在目标方法执行前的动作)、after(在目标方法执行后的动作)、around(替换目标方法的动作)。


    image.png
  • 切入点(Pointcut)
    确定什么时机触发通知。

  • JoinPoint类型


    image.png
  • 常用通配符


    image.png
  • MethodSignature
    定义MethodSignature的条件表达式与定义一个方法类型,结构如下:

    • 表达式
      [@注解] [访问权限] 返回值的类型 类全路径名(包名+类名).函数名(参数)
    • 例子
      public (..) :表示任意参数任意包下的任意函数名任意返回值的public方法
      @com.example.TestAnnotation com.example..(int) :表示com.example下被TestAnnotation注解了的带一个int类型参数的任意名称任意返回值的方法
  • ConstructorSignature
    ConstructorSignature与MethodSignature类似,只不过构造函数没有返回值,而且函数名必须叫new。

    • 表达式
      [@注解] [访问权限] 类全路径名(包名+类名).new(参数)
    • 例子
      public *..People.new(..) :表示任意包名下面People这个类的public构造器,参数列表任意
  • FieldSignature
    与在类中定一个一个成员变量的格式相类似

  • 表达式
    [@注解] [访问权限] 类型 类全路径名.成员变量名

  • 例子
    String com.example..People.lastName :表示com.example包下面的People这个类中名为lastName的String类型的成员变量

  • FieldSignature
    TypeSignature其实就是用来指定一个类的。因此我们只需要给出一个类的全路径的表达式即可


    image.png

    上面几个是比较常用的选择关键字。
    同时,Pointcut之间可以使用"&&|!"这些逻辑符号进行拼接,将两个Pointcut进行拼接,完成一个最终的对Pointcut的选择操作

  • 切面(Aspect)
    切入点和通知的组合成一个切面,如在应用通过定义一个Pointcut和Advice来添加一个日志切面。
    织入(Weaving)
    注入代码到目标位置的过程。

2.AOP织入类型

  • 编译时织入
    在Java类文件编译的时候进行织入,如使用AspectJ的织入编译器。
  • 类加载时织入
    通过自定义类加载器ClassLoader的方式在目标类被加载到虚拟机之前进行类的字节代码的增强。
  • 运行时织入
    切面在运行的某个时刻被动态织入,基本原理是使用Java的动态代理技术。

二、AspectJ实现AOP

AspectJ功能强大,支持编译期的代码织入,不影响应用的性能,同时提供易于使用的API。

1.AspectJ基本使用

  • build.gradle配置
    在build.gradle(Project)添加classpath关联
// Top-level build file where you can add configuration options common to all sub-projects/modules.

buildscript {
    repositories {
        jcenter()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:2.3.3'
        classpath 'org.aspectj:aspectjtools:1.8.1'
    }
}

allprojects {
    repositories {
        jcenter()
    }
}

task clean(type: Delete) {
    delete rootProject.buildDir
}
  • 单独一个module用于编写aop切面代码,在该module的build.gradle中添加如下配置(如果我们的切面代码不是独立为一个module的话可以忽略这一步),可以参考如下配置
apply plugin: 'com.android.library'
import com.android.build.gradle.LibraryPlugin
import org.aspectj.bridge.IMessage
import org.aspectj.bridge.MessageHandler
import org.aspectj.tools.ajc.Main

android {
    compileSdkVersion 26
    buildToolsVersion "26.0.3"

    defaultConfig {
        minSdkVersion 14
        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'
        }
    }
}

android.libraryVariants.all { variant ->
    LibraryPlugin plugin = project.plugins.getPlugin(LibraryPlugin)
    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", 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'
    })
    compile 'com.android.support:appcompat-v7:26.+'
    testCompile 'junit:junit:4.12'
    compile 'org.aspectj:aspectjrt:1.8.1'
}
  • 在app module的build.gradle文件中添加AOP配置,可以参考如下配置
apply plugin: 'com.android.application'
import org.aspectj.bridge.IMessage
import org.aspectj.bridge.MessageHandler
import org.aspectj.tools.ajc.Main

android {
    compileSdkVersion 26
    buildToolsVersion "26.0.3"
    defaultConfig {
        applicationId "com.hj.aopdemo"
        minSdkVersion 14
        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'
        }
    }
}

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.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.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 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.+'
    compile 'com.android.support.constraint:constraint-layout:1.0.2'
    compile 'org.aspectj:aspectjrt:1.8.1'
    testCompile 'junit:junit:4.12'
    compile project(':aoplib')
}

通过如上配置,我们就完成了在Android Studio中的项目工程接入AspectJ的配置工作
这个配置有点繁琐,因此网上其实已经有人写了相应的gradle插件,具体可以参考:
AspectJ Gradle插件

2.实例代码

接下来一个实例包含如何配置,以及基本的操作,如日志输出,异常特殊处理。

  • 异常处理代码
@Aspect
public class ExceptionAspect {

    @Pointcut("handler(Exception)")
    public void handlerException(){}
    @Pointcut("within(*..MainActivity)")
    public void codeInActivity() {}

    @Before("codeInActivity() && handlerException()")
    public void beforeException(JoinPoint joinPoint) {
        if (joinPoint != null) {
            Signature signature =joinPoint.getSignature();
            String className = signature.getDeclaringType().getSimpleName();
            Log.d(className, "beforeException");
        }
    }
}
  • 日志切面代码
@Aspect
public class TraceAspect {
    private static final String POINTCUT_METHED = "execution(@com.hj.aoplib.annotation.DebugTrace * *(..))";//含DebugTrace注解的方法,任意返回值,任意名字,任意参数
    private static final String POINTCUT_CONSTRUCTOR = "execution(@com.hj.aoplib.annotation.DebugTrace *.new(..))";//含DebugTrace注解的类,构造函数,任意参数

    @Pointcut(POINTCUT_METHED)
    public void methodAnnotatedWithDebugTrace() {}
    @Pointcut(POINTCUT_CONSTRUCTOR)
    public void constructorAnnotatedWithDebugTrace() {}

    @Around("methodAnnotatedWithDebugTrace() || constructorAnnotatedWithDebugTrace()")
    public Object weaveJoinPoint(ProceedingJoinPoint joinPoint) throws Throwable {
        if (joinPoint != null) {
            MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
            String className = methodSignature.getDeclaringType().getSimpleName();
            String methodName = methodSignature.getMethod().getName();

            Log.d(className, methodName + "weaveJoinPoint");
            final StopWatch stopWatch = new StopWatch();
            stopWatch.start();
            Object obj = joinPoint.proceed();
            stopWatch.stop();
            //记录方法耗时并输出
            Log.d(className, buildLogMessage(methodName, stopWatch.getTotalTimeMillis()));
            return obj;
        }
        return null;
    }

    private static String buildLogMessage(String methodName, long methodDuration) {
        StringBuilder message = new StringBuilder();
        message.append("耗时 --> ");
        message.append(methodName);
        message.append(" --> ");
        message.append("[");
        message.append(methodDuration);
        message.append("ms");
        message.append("]");

        return message.toString();
    }
}
  • 影响位置
public class MainActivity extends AppCompatActivity {

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

    @DebugTrace
    public void test(View v) {
        Log.d("MainActivity", "test-weaveJoinPoint");
        try {
            Thread.sleep(100);
            throw new IllegalArgumentException("Exception");
        } catch (Exception e) {
        }
    }
}

具体源码请访问GitHub,地址如下https://github.com/hhhhskfk/aopdemo

JakeWharton/hugo(基于AspectJ实现的AOP日志记录函数库)
源码

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • Android 中的 AOP 编程 原文链接 : Aspect Oriented Programming in A...
    mao眼阅读 18,470评论 19 82
  • 随着开发经验的累积,我们的代码质量也应该随之更加优雅、简洁。应该减少系统的重复代码,降低模块间的耦合度,并有利于未...
    若无初见阅读 852评论 0 2
  • 一> 代理模式 概述 代理(Proxy)是一种设计模式, 提供了对目标对象另外的访问方式;即通过代理访问目标对象。...
    奋斗的老王阅读 1,138评论 0 50
  • 听说,蝴蝶是忠贞不渝的昆虫,当一只湮灭,化为飞烬,另一只也不会独活,也会追随它,碧落黄泉,不离不弃…… 哪怕牺牲生...
    半生一念阅读 455评论 0 0
  • 是什么让我底下骄傲的头颅 是什么让我弯下挺拔的身姿 是什么让我跪下钢铁的双膝 是什么让我淌下滂沱的眼泪 使我一败涂...
    李建鄅阅读 250评论 2 12