AOP之使用AspectJ案例

来源:互联网 发布:蓝牙耳机控制软件 编辑:程序博客网 时间:2024/06/13 23:53

这篇文章作为自己学习笔记:
国内翻译教程


AOP翻译过来 “面向切面”。如果非要用一句话来理解的话:”在程序编译或者运行时,在代码某处切入另一段代码”。

AOP的出现是弥补OOM的不足。

作者:知乎用户
链接:https://www.zhihu.com/question/24863332/answer/48376158
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
面向切面编程(AOP是Aspect Oriented Program的首字母缩写) ,我们知道,面向对象的特点是继承、多态和封装。而封装就要求将功能分散到不同的对象中去,这在软件设计中往往称为职责分配。实际上也就是说,让不同的类设计不同的方法。这样代码就分散到一个个的类中去了。这样做的好处是降低了代码的复杂程度,使类可重用。 但是人们也发现,在分散代码的同时,也增加了代码的重复性。什么意思呢?比如说,我们在两个类中,可能都需要在每个方法中做日志。按面向对象的设计方法,我们就必须在两个类的方法中都加入日志的内容。也许他们是完全相同的,但就是因为面向对象的设计让类与类之间无法联系,而不能将这些重复的代码统一起来。 也许有人会说,那好办啊,我们可以将这段代码写在一个独立的类独立的方法里,然后再在这两个类中调用。但是,这样一来,这两个类跟我们上面提到的独立的类就有耦合了,它的改变会影响这两个类。那么,有没有什么办法,能让我们在需要的时候,随意地加入代码呢?这种在运行时,动态地将代码切入到类的指定方法、指定位置上的编程思想就是面向切面的编程。 一般而言,我们管切入到指定类指定方法的代码片段称为切面,而切入到哪些类、哪些方法则叫切入点。有了AOP,我们就可以把几个类共有的代码,抽取到一个切片中,等到需要时再切入对象中去,从而改变其原有的行为。这样看来,AOP其实只是OOP的补充而已。OOP从横向上区分出一个个的类来,而AOP则从纵向上向对象中加入特定的代码。有了AOP,OOP变得立体了。如果加上时间维度,AOP使OOP由原来的二维变为三维了,由平面变成立体了。从技术上来说,AOP基本上是通过代理机制实现的。 AOP在编程历史上可以说是里程碑式的,对OOP编程是一种十分有益的补充

举个例子:

我们需要做用户行为统计,当用户点击个界面的按钮 ,会触发onClick或者onClick2
(界面有多个按钮 一个按钮对应一个onclick方法)

  public void onClick(View view) {        //记录当前用户点击了某个view从而触发onClick方法        long l = System.currentTimeMillis();        /**         * 假设 这里发送 统计到 后台         *         *         *///        send(XXXXXX);        Log.e("fmy","我点了按钮");    }    public void onClic2k(View view) {        //记录当前用户点击了某个view从而触发onClick2方法        long l = System.currentTimeMillis();        /**         * 假设 这里发送 统计到 后台         *         *         *///        send(XXXXXX);        Log.e("fmy","我点了按钮");    }

很显然两个方法有重复的代码段:

 //记录当前用户点击了某个view从而触发onClick方法        long l = System.currentTimeMillis();        /**         * 假设 这里发送 统计到 后台         *         *         *///        send(XXXXXX);

这时候我们会把这个方法 封装到一个类中,然后调用类方法即可如下

  public void onClick(View view) {      //封装统计方法       A.statistics();        Log.e("fmy","我点了按钮");    }    public void onClic2k(View view) {        A.statistics();        Log.e("fmy","我点了按钮");    }

可是这样A类的方法和onClick产生了耦合,与我们提倡的低耦合高内聚相反。

这时候我们就孕育而生了AOP思想。在代码运行时或者编译时插入代码

这里使用AspectJ在Android下完成一个Demo程序。

使用环境:

  • IDE:Android Studio
  • 目的:利用AspectJ完成用户行为统计

创建一个项目:
包含两个moudle。

  1. acepctlibrary –>>利用AspectJ写的一个行为统计
  2. app –>>测试acepctlibrary类库

acepctlibrary:

一个Android Library

修改build.gradle

import com.android.build.gradle.LibraryPluginimport org.aspectj.bridge.IMessageimport org.aspectj.bridge.MessageHandlerimport org.aspectj.tools.ajc.Mainbuildscript {    repositories {        mavenCentral()    }    dependencies {        classpath 'com.android.tools.build:gradle:2.3.3'        classpath 'org.aspectj:aspectjtools:1.8.1'    }}apply plugin: 'com.android.library'repositories {    mavenCentral()}dependencies {    compile 'org.aspectj:aspectjrt:1.8.1'    compile files('libs/aspectjrt.jar')}android {    compileSdkVersion 25    buildToolsVersion '25.0.3'    lintOptions {        abortOnError false    }}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", 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;            }        }    }}

编写一个注解:

package com.example.acepctlibrary;import java.lang.annotation.ElementType;import java.lang.annotation.Retention;import java.lang.annotation.RetentionPolicy;import java.lang.annotation.Target;/** * Created by fmy on 2017/9/12. */@Retention(RetentionPolicy.CLASS)@Target(ElementType.METHOD)public @interface Statistics {     String name () ;    int type ();}

处理器:

package com.example.acepctlibrary;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 org.aspectj.lang.reflect.MethodSignature;import java.text.SimpleDateFormat;import java.util.Date;/** * Created by fmy on 2017/9/12. */@Aspectpublic class TraceAspect {    /**     * 设置切入点规则。可以指定返回值 形参等 如果*则表示 所有被次注解的都会被作为切入     *     */    private static final String KEY = "execution(@com.example.acepctlibrary.Statistics  * *(..))";    /**     * 设置切入点规则 只要是被Statistics注解修饰的都会被作为切入点     */    @Pointcut(KEY)    public void annoBehavior()    {    }    private static final String TAG = "fmy";    SimpleDateFormat simpleDateFormat=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");    // @Around传入切入点 就是方法名    @Around("annoBehavior()")    public  Object dealPoint(ProceedingJoinPoint point) throws  Throwable{        //方法执行前        MethodSignature methodSignature= (MethodSignature) point.getSignature();        Statistics  behaviorTrace=methodSignature.getMethod().getAnnotation(Statistics.class);        String contentType=behaviorTrace.name();        int type=behaviorTrace.type();        Log.i(TAG,contentType+"使用时间:   "+simpleDateFormat.format(new Date()));        long beagin=System.currentTimeMillis();        //方法执行时        Object object=null;        try {            object=point.proceed();        }catch (Exception e)        {        }        //方法执行完成        Log.i(TAG,"消耗时间:  "+(System.currentTimeMillis()-beagin)+"ms");        return  object;    }}

app

一个测试上面library

注意这里也要修改buil.gradl:

import org.aspectj.bridge.IMessageimport org.aspectj.bridge.MessageHandlerimport org.aspectj.tools.ajc.Mainapply plugin: 'com.android.application'buildscript {    repositories {        mavenCentral()    }    dependencies {        classpath 'org.aspectj:aspectjtools:1.8.9'        classpath 'org.aspectj:aspectjweaver:1.8.9'    }}android {    compileSdkVersion 25    buildToolsVersion '25.0.3'    defaultConfig {        applicationId "com.example.fmy.aspectdemo"        minSdkVersion 15        targetSdkVersion 25        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.loggerfinal def variants = project.android.applicationVariantsvariants.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;            }        }    }}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:25.+'    compile 'com.android.support.constraint:constraint-layout:1.0.2'    testCompile 'junit:junit:4.12'    compile project(':acepctlibrary')}
package com.example.fmy.aspectdemo;import android.support.v7.app.AppCompatActivity;import android.os.Bundle;import android.util.Log;import android.view.View;import com.example.acepctlibrary.Statistics;public class MainActivity extends AppCompatActivity {    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);    }    @Statistics(name="",type=1)    public void onClick(View view) {        Log.e("fmy","我点了按钮");    }}

GIT源码附上

原创粉丝点击