架構之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介紹(一)

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