架构设计四之面向切面编程

前言

        大家都知道OOP,它是一种计算机编程架构。OOP 的一条基本原则是计算机程序是由单个能够起到子程序作用的单元或对象组合而成(百度百科)。
OP和AOP是什么关系呢?在OOP的世界中,问题或者功能都被划分到一个一个的模块里边。每个模块专心干自己的事情,模块之间通过设计好的接口交互。从图示来看,OOP世界中,最常见的表示比如:
这里写图片描述
上面的图为Android Framework中的模块图,相信大家在日常开发中都是这么画模块图的,将每个功能都放在单独的模块中,这样可以简化了很多问题的处理。
        OOP的精髓是把功能或问题模块化,每个模块处理自己的事。但在日常开发中,并不是所有问题都能完美得划分到模块中。举个最简单而又常见的例子:现在想为每个模块加上日志功能,要求模块运行时候能输出日志。在不知道AOP的情况下,一般的处理都是:先设计一个日志输出模块,这个模块提供日志输出API,比如Android中的Log类。然后,其他模块需要输出日志的时候调用Log类的几个函数,比如e(TAG,…),w(TAG,…),d(TAG,…),i(TAG,…)等。
        在没有接触AOP之前,包括我在内,想到的解决方案就是上面这样的。但是,从OOP角度看,除了日志模块本身,其他模块的家务事绝大部分情况下应该都不会包含日志输出功能。什么意思?以ActivityManagerService为例,你能说它的家务事里包含日志输出吗?显然,ActivityManagerService的功能点中不包含输出日志这一项。但实际上,软件中的众多模块确实又需要打印日志。这个日志输出功能,从整体来看,都是一个面上的。而这个面的范围,就不局限在单个模块里了,而是横跨多个模块。
        在没有AOP之前,各个模块要打印日志,就是自己处理。反正日志模块的那几个API都已经写好了,你在其他模块的任何地方,任何时候都可以调用。功能是得到了满足,但是好像没有Oriented的感觉了。是的,随意加日志输出功能,使得其他模块的代码和日志模块耦合非常紧密。而且,将来要是日志模块修改了API,则使用它们的地方都得改。这种搞法,一点也不酷。
        l 第一,我们要认识到OOP世界中,有些功能是横跨并嵌入众多模块里的,比如打印日志,比如统计某个模块中某些函数的执行时间等。这些功能在各个模块里分散得很厉害,可能到处都能见到。
        l 第二,AOP的目标是把这些功能集中起来,放到一个统一的地方来控制和管理。如果说,OOP如果是把问题划分到单个模块的话,那么AOP就是把涉及到众多模块的某一类问题进行统一管理。比如我们可以设计两个Aspects,一个是管理某个软件中所有模块的日志输出的功能,另外一个是管理该软件中一些特殊函数调用的权限检查。

那么…我们何时何地应用AOP呢?

        日志、持久化、性能监控、数据校验、缓存(取决于你所选的其中一种或其他方案)

工具和库

有一些工具和库帮助我们使用 AOP:
        AspectJ: 一个 JavaTM 语言的面向切面编程的无缝扩展(适用Android)。
        Javassist for Android: 用于字节码操作的知名 java 类库 Javassist 的 Android 平台移植版。
        DexMaker: Dalvik 虚拟机上,在编译期或者运行时生成代码的 Java API。
        ASMDEX: 一个类似 ASM 的字节码操作库,运行在Android平台,操作Dex字节码。

为什么用 AspectJ?

我们下面的例子选用 AspectJ,有以下原因:
        功能强大
        支持编译期和加载时代码注入
        易于使用

没有使用AOP的例子:

public class MainActivity extends AppCompatActivity {
    private static final String TAG = "dongnao";
    SimpleDateFormat simpleDateFormat=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }

    /**
     * 语音的模块
     *
     * @param view
     */
    public  void mAudio(View view)
    {
        long beagin=System.currentTimeMillis();

        //摇一摇的代码逻辑
        {
            SystemClock.sleep(3000);

            Log.i(TAG,"  美女  睡不着   热不热");

        }

        Log.i(TAG,"消耗时间:  "+(System.currentTimeMillis()-beagin)+"ms");
    }

    /**
     * 摇一摇的模块
     *
     * @param view
     */
    public  void mText(View view)
    {
        //统计用户行为 的逻辑
        Log.i(TAG,"文字:  使用时间:   "+simpleDateFormat.format(new Date()));
        long beagin=System.currentTimeMillis();

        //摇一摇的代码逻辑
        {
            SystemClock.sleep(3000);
            Log.i(TAG,"  热   我们去18");

        }

        Log.i(TAG,"消耗时间:  "+(System.currentTimeMillis()-beagin)+"ms");
    }
}

        看到上面的代码可以发现,上面是以一个没有使用AOP思想进行编码的统计用户行为的例子,可以看到当没有使用AOP思想的时候,统计用户行为时都需要在代码逻辑执行前后进行操作,但是当需求一改变,每段统计用户行为的逻辑都要去改变,这样就加大了代码的维护,不符合设计原则中的单一职责原则,并也加大了模块间的耦合度。不符合面向对象设计原则。

环境搭建

        那么先从搭环境开始吧,待会会把aspectJ的jar包放在Git上,下载1.8.5.jar包之后,安装在电脑上。直接点安装即可。
这里写图片描述
这里写图片描述
下一步下一步就好。这里我只介绍Android Studio的。Studio就好配置了,在Gradle中进行配置。

apply plugin: 'com.android.application'  
import org.aspectj.bridge.IMessage  
import org.aspectj.bridge.MessageHandler  
import org.aspectj.tools.ajc.Main  
buildscript {  
    repositories {  
        mavenCentral()  
    }  
    dependencies {  
        classpath 'org.aspectj:aspectjtools:1.8.9'  
        classpath 'org.aspectj:aspectjweaver:1.8.9'  
    }  
}  
repositories {  
    mavenCentral()  
}  
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;  
            }  
        }  
    }  
}  

android {  
    compileSdkVersion 24  
    buildToolsVersion "24.0.2"  
    defaultConfig {  
        applicationId "com.example.administrator.aspectjdemo"  
        minSdkVersion 19  
        targetSdkVersion 24  
        versionCode 1  
        versionName "1.0"  
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"  
    }  
    buildTypes {  
        release {  
            minifyEnabled false  
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'  
        }  
    }  
}  



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:24.2.1'  
    testCompile 'junit:junit:4.12'  
    compile files('libs/aspectjrt.jar')  
}  

至于为什么这么配置,AspectJ是对java的扩展,而且是完全兼容java的。但是编译时得用Aspect专门的编译器,这里的配置就是使用Aspect的编译器。这里还在libs导入了一个jar包。创建依赖。
这里写图片描述

利用AOP改进上面的实例:

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.CLASS)
public @interface BehaviorTrace {
    String value();
    int type();
}


@Aspect
public class BehaviorAspect {
    private static final String TAG = "dongnao";
    SimpleDateFormat simpleDateFormat=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    /**
     * 如何切蛋糕,切成什么样的形状
     * 切点
     */
    @Pointcut("execution(@com.example.administrator.aop.BehaviorTrace  * *(..))")
    public void annoBehavior()
    {

    }

    /**
     * 切面
     * 蛋糕按照切点切下来之后   怎么吃
     * @param point
     * @return
     * @throws Throwable
     */
    @Around("annoBehavior()")
    public Object dealPoint(ProceedingJoinPoint point) throws  Throwable
    {
        //方法执行前
        MethodSignature methodSignature= (MethodSignature) point.getSignature();
        BehaviorTrace  behaviorTrace=methodSignature.getMethod().getAnnotation(BehaviorTrace.class);
        String contentType=behaviorTrace.value();
        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;
    }
}


public class MainActivity extends AppCompatActivity {
    private static final String TAG = "dongnao";
    SimpleDateFormat simpleDateFormat=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }

    /**
     * 摇一摇的模块
     *
     * @param view
     */
    @BehaviorTrace(value = "摇一摇",type = 1)
    public  void mShake(View view)
    {
            SystemClock.sleep(3000);
            Log.i(TAG,"  摇到一个嫩模:  约不约");
    }

    /**
     * 语音的模块
     *
     * @param view
     */
    @BehaviorTrace(value = "语音",type = 2)
    public  void mAudio(View view)
    {
        //摇一摇的代码逻辑
        {
            SystemClock.sleep(3000);

            Log.i(TAG,"  美女  睡不着   热不热");

        }
    }

    /**
     * 文字的模块
     *
     * @param view
     */
    @BehaviorTrace(value = "文字",type = 2)
    public  void mText(View view)
    {
        //摇一摇的代码逻辑
        {
            SystemClock.sleep(3000);
            Log.i(TAG,"  热   我们去18");

        }
    }
}

        看上面的代码每段业务逻辑里面只去处理他们自己的业务逻辑,不需要再去管理统计用户行为的逻辑,这样子就符合了面向对象的设计原则,AOP的出现就是为了解决OOP解决不了的问题:跨模块调用增加了模块间的耦合度以及单一职责原则。

总结

这里只是初步的总结,想要进一步学习的话,可以看以下的博客:
        http://blog.csdn.net/innost/article/details/49387395
        http://www.jianshu.com/p/0fa8073fd144
        http://blog.csdn.net/sw5131899/article/details/53885957

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值