Android AOP三剑客之AspectJ

作者: Code猎人 | 来源:发表于2018-09-27 16:28 被阅读23次

    前言

    本章节目的不是详细的介绍AspectJ的细节,而是最近项目用到了AspectJ,通过一个简单例子来看下定义切片以及使用切片的流程是怎样的。

    AspectJ

    • AspectJ 是使用最为广泛的 AOP 实现方案,适用于 Java 平台,官网地址:http://www.eclipse.org/aspectj/ 。AspectJ 是在静态织入代码,即在编译期注入代码的。

    • AspectJ 提供了一套全新的语法实现,完全兼容 Java(跟 Java 之间的区别,只是多了一些关键词而已)。同时,还提供了纯 Java 语言的实现,通过注解的方式,完成代码编织的功能。因此我们在使用 AspectJ 的时候有以下两种方式:

      • 使用 AspectJ 的语言进行开发

      • 通过 AspectJ 提供的注解在 Java 语言上开发

    • 因为最终的目的其实都是需要在字节码文件中织入我们自己定义的切面代码,不管使用哪种方式接入 AspectJ,都需要使用 AspectJ 提供的代码编译工具 ajc 进行编译。

    • 在 Android Studio 上一般使用注解的方式使用 AspectJ,因为 Android Studio 没有 AspectJ 插件,无法识别 AspectJ 的语法(不过在 Intellij IDEA 收费版上可以使用 AspectJ 插件),所以后面的语法说明和示例都是以注解的实现方式。

    常用术语

    在了解AspectJ的具体使用之前,先了解一下其中的一些基本的术语概念,这有利于我们掌握AspectJ的使用以及AOP的编程思想。

    JoinPoints

    JoinPoints(连接点),程序中可能作为代码注入目标的特定的点。在AspectJ中可以作为JoinPoints的地方包括:


    PointCuts

    PointCuts(切入点),其实就是代码注入的位置。与前面的JoinPoints不同的地方在于,其实PointCuts是有条件限定的JoinPoints。比如说,在一个Java源文件中,会有很多的JoinPoints,但是我们只希望对其中带有@debug注解的地方才注入代码。所以,PointCuts是通过语法标准给JoinPoints添加了筛选条件限定。

    Advice

    Advice(通知),其实就是注入到class文件中的代码片。典型的 Advice 类型有 before、after 和 around,分别表示在目标方法执行之前、执行后和完全替代目标方法执行的代码。

    Aspect

    Aspect(切面),Pointcut 和 Advice 的组合看做切面。
    Weaving
    注入代码(advices)到目标位置(joint points)的过程

    接下来通过项目看一下实践过程

    传送门:android-aop-samples

    在annotation里定义注解

    @Retention(RetentionPolicy.CLASS)
    @Target(ElementType.METHOD)
     public @interface CheckLogin {
     }
    

    在android studio的android工程中使用AspectJ的时候,我们需要在项目的build.gradle的文件中添加一些配置:

    dependencies {
        classpath 'org.aspectj:aspectjtools:1.8.9'
        ...
    }
    

    在新建module里定义AspectjPlugin,也可以直接写到gradle里面,固定写法没啥说的

    public class AspectjPlugin implements Plugin<Project> {
    
    
    void apply(Project project) {
        project.dependencies {
            compile 'org.aspectj:aspectjrt:1.8.9'
        }
        final def log = project.logger
        log.error "========================";
        log.error "Aspectj切片开始编织Class!";
        log.error "========================";
        project.android.applicationVariants.all { variant ->
            def 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;
                    }
                }
            }
        }
    }
    }
    

    在app的build.gradle里面

      import com.app.plugin.AspectjPlugin
      apply plugin: AspectjPlugin
    

    定义切片

    @Aspect
    public class CheckLoginAspect {
    
    @Pointcut("execution(@com.app.annotation.aspect.CheckLogin * *(..))")//方法切入点
    public void methodAnnotated() {
    }
    
    @Around("methodAnnotated()")//在连接点进行方法替换
    public void aroundJoinPoint(ProceedingJoinPoint joinPoint) throws Throwable {
    
    
        if (!SharedPreferenceUtil.isLogin()) {
            Snackbar.make(AopApplication.getAppContext().getCurActivity().getWindow().getDecorView(), "请先登录!", Snackbar.LENGTH_LONG)
                    .setAction("登录", new View.OnClickListener() {
                        @Override
                        public void onClick(View view) {
                            SharedPreferenceUtil.setLogin(AopApplication.getAppContext(), true);
                            Toast.makeText(AopApplication.getAppContext(), "登录成功", Toast.LENGTH_SHORT).show();
                        }
                    }).show();
    
            return;
        }
    
        joinPoint.proceed();//执行原方法
    }
    }
    

    在MainActivity里面使用注解@CheckLogin,看下build/intermediates/classes编译出来的class里面的插入代码

    @CheckLogin
    public void doMarkDown()
    {
    JoinPoint localJoinPoint = Factory.makeJP(ajc$tjp_0, this, this);Object[] arrayOfObject = new 
    Object[2];arrayOfObject[0] = this;arrayOfObject[1] = 
    localJoinPoint;CheckLoginAspect.aspectOf().aroundJoinPoint(new 
    MainActivity.AjcClosure1(arrayOfObject).linkClosureAndJoinPoint(69648));
     }
    
    static final void doMarkDown_aroundBody0(MainActivity ajc$this, JoinPoint paramJoinPoint)
    {
    Toast.makeText(AopApplication.getAppContext(), , 1).show();
    }
    

    使用总结

    1.定义注解
    2.添加入口plugin或者直接写在gradle里
    3.定义切片,设置@Pointcut使用execution来设置方法的切入点为com.app.annotation.aspect包下的CheckLogin
    4.编写切片处理逻辑在Advice里,Advice就是我们插入的代码可以以何种方式插入,有Before 还有 After、Around
    5.在项目里使用切片,达到在指定位置插入代码的目的,可以在具体项目里面同一种场景使用该注解达到处理切面的问题,大大减少了代码的书写,更加是AOP的具体体现,对OOP的一种弥补

    最后

    本章节只是介绍了少部分的AspectJ的使用,还是那句老话,AspectJ本身并没有技术难点,难的是怎么设计出好用的切面,无论是log还是监控日志都可以使用该方式进行尝试.

    作为老司机,这是弯道超车的必备秘籍,天下武功、唯快不破!

    相关文章

      网友评论

        本文标题:Android AOP三剑客之AspectJ

        本文链接:https://www.haomeiwen.com/subject/lxgkoftx.html