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

xiaoxiao2021-02-28  104

前言

        大家都知道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

转载请注明原文地址: https://www.6miu.com/read-51376.html

最新回复(0)