架构之AOP面向切面思想之集中式登陆架构设计

一 、开始之前:

集中式登陆架构设计,此登陆并非登陆请求,而是判断当前是否登陆状态,如不是登陆状态,跳转登陆界面;否则进行对应的跳转逻辑 。该业务是一个全局业务,全局业务抽取到一个切面。
如果是OOP思想编程的话,采用SharedPreferenced来保存isLogined登陆状态,如果isLogined == true跳转到我的积分、我的专区等等。否则跳转到LoginActivity

二 、实现方式一 :运行时动态代理的方式实现

采用运行时动态代理的方式实现,具体查看代码CentralizedLoginArchitecture
重点介绍方式二:

三、实现方式二:AspectJ

1. 举例,有如下需求:

在这里插入图片描述
类似需求如:用户的行为统计分析,也是一个全局业务。也是可以通过AOP切面的思想来完成。

言归正传,开始AspectJAspectJ是面向切面编程的一个框架,拓展了Java的语言,并且定义实现AOP的语法。

2. 名词解释:

切入点,PointCut
通知,Advice
连接点,Joint Point, Before After Around

3. 集成AspectJ

集成AspectJ可以通过插件的方式集成,也可以通过gradle直接集成。

1. 插件集成
  1. 新建主module app
  2. 新建android library aspectj
2. gradle集成
  1. 项目根build.gradle添加依赖:
    // Top-level build file where you can add configuration options common to all sub-projects/modules.
    
    buildscript {
        repositories {
            maven {
                url 'http://maven.aliyun.com/nexus/content/groups/public/'
            }
            google()
            jcenter()
    //        mavenCentral()
        }
        dependencies {
            classpath 'com.android.tools.build:gradle:3.5.0'
    
            // 版本界限:As-3.0.1 + gradle4.4-all (需要配置r17的NDK环境)
            // 或者:As-3.2.1 + gradle4.6-all (正常使用,无警告)
            classpath 'org.aspectj:aspectjtools:1.8.9'
            classpath 'org.aspectj:aspectjweaver:1.8.9'
        }
    }
    
    allprojects {
        repositories {
            maven {
                url 'http://maven.aliyun.com/nexus/content/groups/public/'
            }
            google()
            jcenter()
    //        mavenCentral()
        }
    }
    
    task clean(type: Delete) {
        delete rootProject.buildDir
    }
    
    
    值得注意的是,如果在同步项目过程中报错:Received close_notify during handshake,可以尝试如上,采用阿里云的仓库
    	maven {
            url 'http://maven.aliyun.com/nexus/content/groups/public/'
        }
    
  2. 修改app的moudle中build.gradle:编译时用Aspect专门的编译器,不再使用传统的javac
    // 版本界限:As-3.0.1 + gradle4.4-all (需要配置r17的NDK环境)
    // 或者:As-3.2.1 + gradle4.6-all (正常使用,无警告)
    buildscript { // 编译时用Aspect专门的编译器,不再使用传统的javac
        repositories {
            maven {
                url 'http://maven.aliyun.com/nexus/content/groups/public/'
            }
    //        mavenCentral()
        }
        dependencies {
            classpath 'org.aspectj:aspectjtools:1.8.9'
            classpath 'org.aspectj:aspectjweaver:1.8.9'
        }
    }
    
  3. 整体如下:
    apply plugin: 'com.android.application'
    
    // 版本界限:As-3.0.1 + gradle4.4-all (需要配置r17的NDK环境)
    // 或者:As-3.2.1 + gradle4.6-all (正常使用,无警告)
    buildscript { // 编译时用Aspect专门的编译器,不再使用传统的javac
        repositories {
            maven {
                url 'http://maven.aliyun.com/nexus/content/groups/public/'
            }
    //        mavenCentral()
        }
        dependencies {
            classpath 'org.aspectj:aspectjtools:1.8.9'
            classpath 'org.aspectj:aspectjweaver:1.8.9'
        }
    }
    
    android {
        compileSdkVersion 29
        buildToolsVersion "29.0.2"
    
    
        defaultConfig {
            applicationId "com.example.aspectj"
            minSdkVersion 15
            targetSdkVersion 29
            versionCode 1
            versionName "1.0"
    
            testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
        }
    
        buildTypes {
            release {
                minifyEnabled false
                proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
            }
        }
    
    }
    
    dependencies {
        implementation fileTree(dir: 'libs', include: ['*.jar'])
    
        implementation 'androidx.appcompat:appcompat:1.0.2'
        implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
        testImplementation 'junit:junit:4.12'
        androidTestImplementation 'androidx.test:runner:1.1.1'
        androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1'
    
        // 引入aspectj的包
        implementation 'org.aspectj:aspectjrt:1.8.13'
    }
    
    
    /**
     * 以下groovy代码不需要太关心,只是作为一个支持
     */
    // 版本界限:As-3.0.1 + gradle4.4-all (需要配置r17的NDK环境)
    // 或者:As-3.2.1 + gradle4.6-all (正常使用,无警告)
    import org.aspectj.bridge.IMessage
    import org.aspectj.bridge.MessageHandler
    import org.aspectj.tools.ajc.Main
    
    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",
                             "-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;
                }
            }
        }
    }
    
    

4. 代码实操

需求:点击登录;查看我的专区、我的优惠券、我的积分(查看之前先检查是否登录状态,如是则跳转相应的功能区,否则跳转登录页面重新登录);此外都需要做行为统计分析。

问题:如何实现用户行为统计分析?
答:友盟等第三方统计。但如果要求自己实际实现 ,那只能通过AOP的思想来实现。

统计用户点击某功能行为。(实际项目中,存储到本地,每隔N天上传到服务器)。

1. 用户行为统计
  1. 自定义行为注解

    //用户点击痕迹(行为统计)  自定义注解参看:公开课 IOC容器
    @Target(ElementType.METHOD)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface ClickBehavior {
        String value();
    }
    
  2. 行为注解作用在需要进行行为统计的代码上。

        // 用户行为统计
        @ClickBehavior(value = "登录")
        public void login(View view) {
            Log.e(TAG, "login: 验证通过,登录成功");
        }
    
  3. 定义AspectJ
    书写该类时需要注意,不能写错一个符号,否则不容易排查。
    AspectJ是由PointCut(切入点)Advice(通知)构成的。 而Advice有:Before After Around三种情况

    1. 使用注解 @Aspect来定义一个切面类。
      @Aspect
      public class ClickBehaviorAspect {
      }
      
      
    2. 通过@Pointcut 寻找需要处理的切入点
          // 1. 应用中使用到了哪些注解,放到当前的切入点进行处理(找到需要处理的切入点)
          // execution 以方法执行时作为切点,触发Aspect类。 语法:execution()
          // * *(..) 通配符,可以处理ClickBehavior这个类所有的方法,也可以指定com.example.aspectj.MainActivity.login()方法作为切入点
          @Pointcut("execution(@com.example.aspectj.annotation.ClickBehavior * *(..))") //
          public void methodPointCut() {
          }
      
      关于通配符* *(..) 可以查看execution(* com.xxx..(…))
    3. 处理切入点
      	// 2. 对切入点如何处理,
          // Around 围绕切入点如何处理
          @Around("methodPointCut()")
          public Object jointPoint(ProceedingJoinPoint joinPoint) throws Throwable {
          	// 遵循三个规则,1.返回值Object 2.参数ProceedingJoinPoint 3. 抛出异常 Throwable
          }
      
2. 集中式登录
  1. 同样定义 LoginCheck 注解
    // 检查是否登录
    @Target(ElementType.METHOD)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface LoginCheck {
    }
    
  2. 使用LoginCheck 注解
        //登录成功之后 用户行为统计
        @ClickBehavior(value = "我的积分")
        @LoginCheck()
        public void score(View view) {
            Log.e(TAG, "score: 跳转我的积分");
            startActivity(new Intent(MainActivity.this, OtherActivity.class));
        }
    
  3. 定义Aspect并处理切入点
    @Aspect
    public class LoginCheckAspect {
        private static final String TAG = LoginCheckAspect.class.getSimpleName();
    
        @Pointcut("execution(@com.example.aspectj.annotation.LoginCheck * *(..))")
        public void methodLoginCheck() {
        }
    
        @Around("methodLoginCheck()")
        public Object check(ProceedingJoinPoint joinPoint) throws Throwable {
            Context context = (Context) joinPoint.getThis();
            boolean isLogin = false; //该处进行登录模拟, 实际项目中,从SharedPreference 从读取
            if (isLogin) {
                Log.e(TAG, "检测到已登录");
                return joinPoint.proceed();//检测到登录,继续执行后续代码
            } else {
                Log.e(TAG, "检测到未登录");
                context.startActivity(new Intent(context, LoginActivity.class));
                return null; //未登录情况下,后续代码都不需要执行了。
            }
        }
    }
    
    

详见代码 AspectJ

四、AspectJ使用过程中碰到的坑

  1. 临界版本as3.0.1和gradle4.4-all需要配置ndk的r17环境
  2. as3.2.1和gradle4.6-all 正常使用,无过时警告
  3. as3.4.0和gradle5.1.1-all有过时的api警告

五、参考

AspectJ介绍(一)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章