Android中AOP(面向切向编程)的深入讲解

一、闲谈AOP

大家都知道OOP,即ObjectOriented Programming,面向对象编程。而本文要介绍的是AOP。AOP是Aspect Oriented Programming的缩写,中译文为面向切向编程。OOP和AOP是什么关系呢?

首先:

  • l OOP和AOP都是方法论。我记得在刚学习C++的时候,最难学的并不是C++的语法,而是C++所代表的那种看问题的方法,即OOP。同样,今天在AOP中,我发现其难度并不在利用AOP干活,而是从AOP的角度来看待问题,设计解决方法。这就是为什么我特意强调AOP是一种方法论的原因!
  • l 在OOP的世界中,问题或者功能都被划分到一个一个的模块里边。每个模块专心干自己的事情,模块之间通过设计好的接口交互。从图示来看,OOP世界中,最常见的表示比如:


图1  Android Framework中的模块

图1中所示为AndroidFramework中的模块。OOP世界中,大家画的模块图基本上是这样的,每个功能都放在一个模块里。非常好理解,而且确实简化了我们所处理问题的难度。

OOP的精髓是把功能或问题模块化,每个模块处理自己的家务事。但在现实世界中,并不是所有问题都能完美得划分到模块中。举个最简单而又常见的例子:现在想为每个模块加上日志功能,要求模块运行时候能输出日志。在不知道AOP的情况下,一般的处理都是:先设计一个日志输出模块,这个模块提供日志输出API,比如Android中的Log类。然后,其他模块需要输出日志的时候调用Log类的几个函数,比如e(TAG,...),w(TAG,...),d(TAG,...),i(TAG,...)等。

在没有接触AOP之前,包括我在内,想到的解决方案就是上面这样的。但是,从OOP角度看,除了日志模块本身,其他模块的家务事绝大部分情况下应该都不会包含日志输出功能。什么意思?以ActivityManagerService为例,你能说它的家务事里包含日志输出吗?显然,ActivityManagerService的功能点中不包含输出日志这一项。但实际上,软件中的众多模块确实又需要打印日志。这个日志输出功能,从整体来看,都是一个面上的。而这个面的范围,就不局限在单个模块里了,而是横跨多个模块。

在没有AOP之前,各个模块要打印日志,就是自己处理。反正日志模块的那几个API都已经写好了,你在其他模块的任何地方,任何时候都可以调用。功能是得到了满足,但是好像没有Oriented的感觉了。是的,随意加日志输出功能,使得其他模块的代码和日志模块耦合非常紧密。而且,将来要是日志模块修改了API,则使用它们的地方都得改。这种搞法,一点也不酷。

AOP的目标就是解决上面提到的不cool的问题。在AOP中:

  • 第一,我们要认识到OOP世界中,有些功能是横跨并嵌入众多模块里的,比如打印日志,比如统计某个模块中某些函数的执行时间等。这些功能在各个模块里分散得很厉害,可能到处都能见到。
  • 第二,AOP的目标是把这些功能集中起来,放到一个统一的地方来控制和管理。如果说,OOP如果是把问题划分到单个模块的话,那么AOP就是把涉及到众多模块的某一类问题进行统一管理。比如我们可以设计两个Aspects,一个是管理某个软件中所有模块的日志输出的功能,另外一个是管理该软件中一些特殊函数调用的权限检查。

讲了这么多,还是先来看个例子。在这个例子中,我们要:

  • Activity的生命周期的几个函数运行时,要输出日志。
  • 几个重要函数调用的时候,要检查有没有权限。

二、没有AOP的例子

先来看没有AOP的情况下,代码怎么写。主要代码都在AopDemoActivity中

[-->AopDemoActivity.java]

public class AopDemoActivity extends Activity {
 private static final String TAG = "AopDemoActivity";
 onCreate,onStart,onRestart,onPause,onResume,onStop,onDestory返回前,都输出一行日志
 protected void onCreate(Bundle savedInstanceState) {
 super.onCreate(savedInstanceState);
 setContentView(R.layout.layout_main);
 Log.e(TAG,"onCreate");
 }
 protected void onStart() {
 super.onStart();
 Log.e(TAG, "onStart");
 }
 protected void onRestart() {
 super.onRestart();
 Log.e(TAG, "onRestart");
 }
 protectedvoid onResume() {
 super.onResume();
 Log.e(TAG, "onResume");
 checkPhoneState会检查app是否申明了android.permission.READ_PHONE_STATE权限
 checkPhoneState();
 }
 protected void onPause() {
 super.onPause();
 Log.e(TAG, "onPause");
 }
 protected void onStop() {
 super.onStop();
 Log.e(TAG, "onStop");
 }
 protected void onDestroy() {
 super.onDestroy();
 Log.e(TAG, "onDestroy");
 }
 private void checkPhoneState(){
 if(checkPermission("android.permission.READ_PHONE_STATE")== false){
 Log.e(TAG,"have no permission to read phone state");
 return;
 }
 Log.e(TAG,"Read Phone State succeed");
 return;
 }
 private boolean checkPermission(String permissionName){
 try{
 PackageManager pm = getPackageManager();
 //调用PackageMangaer的checkPermission函数,检查自己是否申明使用某权限
 int nret = pm.checkPermission(permissionName,getPackageName());
 return nret == PackageManager.PERMISSION_GRANTED;
 }......
 }
}

代码很简单。但是从这个小例子中,你也会发现要是这个程序比较复杂的话,到处都加Log,或者在某些特殊函数加权限检查的代码,真的是一件挺繁琐的事情。

三、AspectJ介绍

3.1  AspectJ极简介

AOP虽然是方法论,但就好像OOP中的Java一样,一些先行者也开发了一套语言来支持AOP。目前用得比较火的就是AspectJ了,它是一种几乎和Java完全一样的语言,而且完全兼容Java(AspectJ应该就是一种扩展Java,但它不是像Groovy[1]那样的拓展。)。当然,除了使用AspectJ特殊的语言外,AspectJ还支持原生的Java,只要加上对应的AspectJ注解就好。所以,使用AspectJ有两种方法:

  • 完全使用AspectJ的语言。这语言一点也不难,和Java几乎一样,也能在AspectJ中调用Java的任何类库。AspectJ只是多了一些关键词罢了。
  • 或者使用纯Java语言开发,然后使用AspectJ注解,简称@AspectJ。

Anyway,不论哪种方法,最后都需要AspectJ的编译工具ajc来编译。由于AspectJ实际上脱胎于Java,所以ajc工具也能编译java源码。

AspectJ现在托管于Eclipse项目中,官方网站是:

  • http://www.eclipse.org/aspectj/   <=AspectJ官方网站
  • http://www.eclipse.org/aspectj/doc/released/runtime-api/index.html  <=AspectJ类库参考文档,内容非常少
  • http://www.eclipse.org/aspectj/doc/released/aspectj5rt-api/index.html  <=@AspectJ文档,以后我们用Annotation的方式最多。

3.2  AspectJ语法

题外话:AspectJ东西比较多,但是AOP做为方法论,它的学习和体会是需要一步一步,并且一定要结合实际来的。如果一下子讲太多,反而会疲倦。更可怕的是,有些胆肥的同学要是一股脑把所有高级玩法全弄上去,反而得不偿失。这就是是方法论学习和其他知识学习不一样的地方。请大家切记。

3.2.1  Join Points介绍

Join Points(以后简称JPoints)是AspectJ中最关键的一个概念。什么是JPoints呢?JPoints就是程序运行时的一些执行点。那么,一个程序中,哪些执行点是JPoints呢?比如:

  • 一个函数的调用可以是一个JPoint。比如Log.e()这个函数。e的执行可以是一个JPoint,而调用e的函数也可以认为是一个JPoint。
  • 设置一个变量,或者读取一个变量,也可以是一个JPoint。比如Demo类中有一个debug的boolean变量。设置它的地方或者读取它的地方都可以看做是JPoints。
  • for循环可以看做是JPoint。

理论上说,一个程序中很多地方都可以被看做是JPoint,但是AspectJ中,只有如表1所示的几种执行点被认为是JPoints:

表1  AspectJ中的Join Point


Join Points


说明


示例


method call


函数调用


比如调用Log.e(),这是一处JPoint


method execution


函数执行


比如Log.e()的执行内部,是一处JPoint。注意它和method call的区别。method call是调用某个函数的地方。而execution是某个函数执行的内部。


constructor call


构造函数调用


和method call类似


constructor execution


构造函数执行


和method execution类似


field get


获取某个变量


比如读取DemoActivity.debug成员


field set


设置某个变量


比如设置DemoActivity.debug成员


pre-initialization


Object在构造函数中做得一些工作。


很少使用,详情见下面的例子


initialization


Object在构造函数中做得工作


详情见下面的例子


static initialization


类初始化


比如类的static{}


handler


异常处理


比如try catch(xxx)中,对应catch内的执行


advice execution


这个是AspectJ的内容,稍后再说

表1列出了AspectJ所认可的JoinPoints的类型。下面我们来看个例子以直观体会一把。

图2  示例代码

图2是一个Java示例代码,下面我们将打印出其中所有的join points。图3所示为打印出来的join points:

图3  所有的join points

图3中的输出为从左到右,我们来解释红框中的内容。先来看左图的第一个红框:

  • staticinitialization(test.Test.<clinit>):表示当前是哪种类型的JPoint,括号中代表目标对象是谁(此处是指Test class的类初始化)。由于Test类没有指定static block,所以后面的at:Test.java:0 表示代码在第0行(其实就是没有找到源代码的意思)。
  • Test类初始化完后,就该执行main函数了。所以,下一个JPoint就是execution(voidtest.Test.main(String[]))。括号中表示此JPoint对应的是test.Test.main函数。at:Test.java:30表示这个JPoint在源代码的第30行。大家可以对比图2的源码,很准确!
  • main函数里首先是执行System.out.println。而这一行代码实际包括两个JPoint。一个是get(PrintStream java.lang.System.out),get表示Field get,它表示从System中获取out对象。另外一个是call(void java.io.PrintStream.println(String)),这是一个call类型的JPoint,表示执行out.println函数。

再来看左图第二个红框,它表示TestBase的类的初始化,由于源码中为TestBase定义了static块,所以这个JPoint清晰指出了源码的位置是at:Test.java:5

接着看左图第三个红框,它和对象的初始化有关。在源码中,我们只是构造了一个TestDerived对象。它会先触发TestDerived Preinitialization JPoint,然后触发基类TestBase的PreInitialization JPoint。注意红框中的before和after 。在TestDerived和TestBase所对应的PreInitialization before和after中都没有包含其他JPoint。所以,Pre-Initialization应该是构造函数中一个比较基础的Phase。这个阶段不包括类中成员变量定义时就赋值的操作,也不包括构造函数中对某些成员变量进行的赋值操作。

而成员变量的初始化(包括成员变量定义时就赋值的操作,比如源码中的int base = 0,以及在构造函数中所做的赋值操作,比如源码中的this.derived = 1000)都被囊括到initialization阶段。请读者对应图三第二个红框到第三个红框(包括第3个红框的内容)看看是不是这样的。

最后来看第5个红框。它包括三个JPoint:

  • testMethod的call类型JPoint
  • testMethod的execution类型JPonint
  • 以及对异常捕获的Handler类型JPoint

好了。JPoint的介绍就先到此。现在大家对JoinPoint应该有了一个很直观的体会,简单直白粗暴点说,JoinPoint就是一个程序中的关键函数(包括构造函数)和代码段(staticblock)。

为什么AspectJ首先要定义好JoinPoint呢?大家仔细想想就能明白,以打印log的AopDemo为例,log在哪里打印?自然是在一些关键点去打印。而谁是关键点?AspectJ定义的这些类型的JPoint就能满足我们绝大部分需求。

注意,要是想在一个for循环中打印一些日志,而AspectJ没有这样的JPoint,所以这个需求我们是无法利用AspectJ来实现了。另外,不同的软件框架对表1中的JPoint类型支持也不同。比如Spring中,不是所有AspectJ支持的JPoint都有。

3.2.2  Pointcuts介绍

pointcuts这个单词不好翻译,此处直接用英文好了。那么,Pointcuts是什么呢?前面介绍的内容可知,一个程序会有很多的JPoints,即使是同一个函数(比如testMethod这个函数),还分为call类型和execution类型的JPoint。显然,不是所有的JPoint,也不是所有类型的JPoint都是我们关注的。再次以AopDemo为例,我们只要求在Activity的几个生命周期函数中打印日志,只有这几个生命周期函数才是我们业务需要的JPoint,而其他的什么JPoint我不需要关注。

怎么从一堆一堆的JPoints中选择自己想要的JPoints呢?恩,这就是Pointcuts的功能。一句话,Pointcuts的目标是提供一种方法使得开发者能够选择自己感兴趣的JoinPoints。

在图2的例子中,怎么把Test.java中所有的Joinpoint选择出来呢?用到的pointcut格式为:

pointcuttestAll():within(Test)。

AspectJ中,pointcut有一套标准语法,涉及的东西很多,还有一些比较高级的玩法。我自己琢磨了半天,需不需要把这些内容一股脑都搬到此文呢?回想我自己学习AOP的经历,好像看了几本书,记得比较清楚的都是简单的case,而那些复杂的case则是到实践中,确实有需求了,才回过头来,重新查阅文档来实施的。恩,那就一步一步来吧。

(1) 一个Pointcuts例子

直接来看一个例子,现在我想把图2中的示例代码中,那些调用println的地方找到,该怎么弄?代码该这么写:

public pointcut testAll(): call(public * *.println(..)) && !within(TestAspect) ;

注意,aspectj的语法和Java一样,只不过多了一些关键词

我们来看看上述代码

第一个public:表示这个pointcut是public访问。这主要和aspect的继承关系有关,属于AspectJ的高级玩法,本文不考虑。

pointcut:关键词,表示这里定义的是一个pointcut。pointcut定义有点像函数定义。总之,在AspectJ中,你得定义一个pointcut。

testAll():pointcut的名字。在AspectJ中,定义Pointcut可分为有名和匿名两种办法。个人建议使用named方法。因为在后面,我们要使用一个pointcut的话,就可以直接使用它的名字就好。

testAll后面有一个冒号,这是pointcut定义名字后,必须加上。冒号后面是这个pointcut怎么选择Joinpoint的条件。

本例中,call(public  *  *.println(..))是一种选择条件。call表示我们选择的Joinpoint类型为call类型。

public  **.println(..):这小行代码使用了通配符。由于我们这里选择的JoinPoint类型为call类型,它对应的目标JPoint一定是某个函数。所以我们要找到这个/些函数。public  表示目标JPoint的访问类型(public/private/protect)。第一个*表示返回值的类型是任意类型。第二个*用来指明包名。此处不限定包名。紧接其后的println是函数名。这表明我们选择的函数是任何包中定义的名字叫println的函数。当然,唯一确定一个函数除了包名外,还有它的参数。在(..)中,就指明了目标函数的参数应该是什么样子的。比如这里使用了通配符..,代表任意个数的参数,任意类型的参数。

再来看call后面的&&:AspectJ可以把几个条件组合起来,目前支持 &&,||,以及!这三个条件。这三个条件的意思不用我说了吧?和Java中的是一样的。

来看最后一个!within(TestAspectJ):前面的!表示不满足某个条件。within是另外一种类型选择方法,特别注意,这种类型和前面讲到的joinpoint的那几种类型不同。within的类型是数据类型,而joinpoint的类型更像是动态的,执行时的类型。

上例中的pointcut合起来就是:

  • 选择那些调用println(而且不考虑println函数的参数是什么)的Joinpoint。
  • 另外,调用者的类型不要是TestAspect的。

图4展示了执行结果:

图4  新pointcut执行结果

我在图2所示的源码中,为Test类定义了一个public static void println()函数,所以图4的执行结果就把这个println给匹配上了。

看完例子,我们来讲点理论。

(2) 直接针对JoinPoint的选择

pointcuts中最常用的选择条件和Joinpoint的类型密切相关,比如图5:

图5  不同类型的JPoint对应的pointcuts查询方法

以图5为例,如果我们想选择类型为methodexecution的JPoint,那么pointcuts的写法就得包括execution(XXX)来限定。

除了指定JPoint类型外,我们还要更进一步选择目标函数。选择的根据就是图5中列出的什么MethodSignature,ConstructorSignature,TypeSinature,FieldSignature等。名字听起来陌生得很,其实就是指定JPoint对应的函数(包括构造函数),Static block的信息。比如图4中的那个println例子,首先它的JPoint类型是call,所以它的查询条件是根据MethodSignature来表达。一个Method Signature的完整表达式为:

@注解 访问权限 返回值的类型 包名.函数名(参数)
  @注解和访问权限(public/private/protect,以及static/final)属于可选项。如果不设置它们,则默认都会选择。以访问权限为例,如果没有设置访问权限作为条件,那么public,private,protect及static、final的函数都会进行搜索。
  返回值类型就是普通的函数的返回值类型。如果不限定类型的话,就用*通配符表示
  包名.函数名用于查找匹配的函数。可以使用通配符,包括*和..以及+号。其中*号用于匹配除.号之外的任意字符,而..则表示任意子package,+号表示子类。
     比如:
     java.*.Date:可以表示java.sql.Date,也可以表示java.util.Date
     Test*:可以表示TestBase,也可以表示TestDervied
     java..*:表示java任意子类
     java..*Model+:表示Java任意package中名字以Model结尾的子类,比如TabelModel,TreeModel
     等
  最后来看函数的参数。参数匹配比较简单,主要是参数类型,比如:
     (int, char):表示参数只有两个,并且第一个参数类型是int,第二个参数类型是char
     (String, ..):表示至少有一个参数。并且第一个参数类型是String,后面参数类型不限。在参数匹配中,
     ..代表任意参数个数和类型
     (Object ...):表示不定个数的参数,且类型都是Object,这里的...不是通配符,而是Java中代表不定参数的意思

是不是很简单呢?

Constructorsignature和Method Signature类似,只不过构造函数没有返回值,而且函数名必须叫new。比如:
public *..TestDerived.new(..):
  public:选择public访问权限
  *..代表任意包名
  TestDerived.new:代表TestDerived的构造函数
  (..):代表参数个数和类型都是任意
再来看Field Signature和Type Signature,用它们的地方见图5。下面直接上几个例子:
Field Signature标准格式:
@注解 访问权限 类型 类名.成员变量名
  其中,@注解和访问权限是可选的
  类型:成员变量类型,*代表任意类型
  类名.成员变量名:成员变量名可以是*,代表任意成员变量
比如,
set(inttest..TestBase.base):表示设置TestBase.base变量时的JPoint
Type Signature:直接上例子
staticinitialization(test..TestBase):表示TestBase类的static block
handler(NullPointerException):表示catch到NullPointerException的JPoint。注意,图2的源码第23行截获的其实是Exception,其真实类型是NullPointerException。但是由于JPointer的查询匹配是静态的,即编译过程中进行的匹配,所以handler(NullPointerException)在运行时并不能真正被截获。只有改成handler(Exception),或者把源码第23行改成NullPointerException才行。

以上例子,读者都可以在aspectj-test例子中自己都试试。

(3) 间接针对JPoint的选择

除了根据前面提到的Signature信息来匹配JPoint外,AspectJ还提供其他一些选择方法来选择JPoint。比如某个类中的所有JPoint,每一个函数执行流程中所包含的JPoint。

特别强调,不论什么选择方法,最终都是为了找到目标的JPoint。

表2列出了一些常用的非JPoint选择方法:

表2  其它常用选择方法


关键词


说明


示例


within(TypePattern)


TypePattern标示package或者类。TypePatter可以使用通配符


表示某个Package或者类中的所有JPoint。比如

within(Test):Test类中(包括内部类)所有JPoint。图2所示的例子就是用这个方法。


withincode(Constructor Signature|Method Signature)


表示某个构造函数或其他函数执行过程中涉及到的JPoint


比如

withinCode(* TestDerived.testMethod(..))

表示testMethod涉及的JPoint

withinCode( *.Test.new(..))

表示Test构造函数涉及的JPoint


cflow(pointcuts)


cflow是call flow的意思

cflow的条件是一个pointcut


比如

cflow(call TestDerived.testMethod):表示调用TestDerived.testMethod函数时所包含的JPoint,包括testMethod的call这个JPoint本身


cflowbelow(pointcuts)


cflow是call flow的意思。


比如

cflowblow(call TestDerived.testMethod):表示调用TestDerived.testMethod函数时所包含的JPoint,不包括testMethod的call这个JPoint本身


this(Type)


JPoint的this对象是Type类型。

(其实就是判断Type是不是某种类型,即是否满足instanceof Type的条件)


JPoint是代码段(不论是函数,异常处理,static block),从语法上说,它都属于一个类。如果这个类的类型是Type标示的类型,则和它相关的JPoint将全部被选中。

图2示例的testMethod是TestDerived类。所以

this(TestDerived)将会选中这个testMethod JPoint


target(Type)


JPoint的target对象是Type类型


和this相对的是target。不过target一般用在call的情况。call一个函数,这个函数可能定义在其他类。比如testMethod是TestDerived类定义的。那么

target(TestDerived)就会搜索到调用testMethod的地方。但是不包括testMethod的execution JPoint


args(TypeSignature)


用来对JPoint的参数进行条件搜索的


比如args(int,..),表示第一个参数是int,后面参数个数和类型不限的JPoint。

上面这些东西,建议读者:

  • 进入androidaopdemo/aspectj-test目录。
  • 修改test/TestAspect.aj文件。主要是其中的pointcuts:testAll()这一行。按照图2中的解释说明,随便改改试试。
  • 执行./create-jar.sh,得到一个test.jar包,然后java -jar test.jar得到执行结果

注意:this()和target()匹配的时候不能使用通配符。

图6给出了修改示例和输出:

图6  示例代码和输出结果

注意,不是所有的AOP实现都支持本节所说的查询条件。比如Spring就不支持withincode查询条件。

3.2.3  advice和aspect介绍

恭喜,看到这个地方来,AspectJ的核心部分就掌握一大部分了。现在,我们知道如何通过pointcuts来选择合适的JPoint。那么,下一步工作就很明确了,选择这些JPoint后,我们肯定是需要干一些事情的。比如前面例子中的输出都有before,after之类的。这其实JPoint在执行前,执行后,都执行了一些我们设置的代码。在AspectJ中,这段代码叫advice。简单点说,advice就是一种Hook。

ASpectJ中有好几个Hook,主要是根据JPoint执行时机的不同而不同,比如下面的:

before():testAll(){
 System.out.println("before calling: " + thisJoinPoint);//打印这个JPoint的信息
 System.out.println(" at:" + thisJoinPoint.getSourceLocation());//打印这个JPoint对应的源代码位置
}

testAll()是前面定义的pointcuts,而before()定义了在这个pointcuts选中的JPoint执行前我们要干的事情。

表3列出了AspectJ所支持的Advice的类型:

表3  advice的类型


关键词


说明


示例


before()


before advice


表示在JPoint执行之前,需要干的事情


after()


after advice


表示JPoint自己执行完了后,需要干的事情。


after():returning(返回值类型)

after():throwing(异常类型)


returning和throwing后面都可以指定具体的类型,如果不指定的话则匹配的时候不限定类型


假设JPoint是一个函数调用的话,那么函数调用执行完有两种方式退出,一个是正常的return,另外一个是抛异常。

注意,after()默认包括returning和throwing两种情况


返回值类型 around()


before和around是指JPoint执行前或执行后备触发,而around就替代了原JPoint


around是替代了原JPoint,如果要执行原JPoint的话,需要调用proceed

注意,after和before没有返回值,但是around的目标是替代原JPoint的,所以它一般会有返回值,而且返回值的类型需要匹配被选中的JPoint。我们来看个例子,见图7。

图7  advice示例和结果

图7中:

  • 第一个红框是修改后的testMethod,在这个testMethod中,肯定会抛出一个空指针异常。
  • 第二个红框是我们配置的advice,除了before以外,还加了一个around。我们重点来看around,它的返回值是Object。虽然匹配的JPoint是testMethod,其定义的返回值是void。但是AspectJ考虑的很周到。在around里,可以设置返回值类型为Object来表示返回任意类型的返回值。AspectJ在真正返回参数的时候,会自动进行转换。比如,假设inttestMethod定义了int作为返回值类型,我们在around里可以返回一个Integer,AspectJ会自动转换成int作为返回值。
  • 再看around中的//proceed()这句话。这代表调用真正的JPoint函数,即testMethod。由于这里我们屏蔽了proceed,所以testMethod真正的内容并未执行,故运行的时候空指针异常就不会抛出来。也就是说,我们完全截获了testMethod的运行,甚至可以任意修改它,让它执行别的函数都没有问题。。

注意:从技术上说,around是完全可以替代before和after的。图7中第二个红框还把after给注释掉了。如果不注释掉,编译时候报错,[error]circular advice precedence: can't determine precedence between two or morepieces of advice that apply to the same join point: method-execution(voidtest.Test$TestDerived.testMethod())(大家可以自己试试)。我猜测其中的原因是around和after冲突了。around本质上代表了目标JPoint,比如此处的testMethod。而after是testMethod之后执行。那么这个testMethod到底是around还是原testMethod呢?真是傻傻分不清楚!

(我觉得再加一些限制条件给after是可以避免这个问题的,但是没搞成功...)

advice讲完了。现在回顾下3.2节从开始到现在我们学到了哪些内容:

  • AspectJ中各种类型的JoinPoint,JPoint是一个程序的关键执行点,也是我们关注的重点。
  • pointcuts:提供了一种方法来选择目标JPoint。程序有很多JPoint,但是需要一种方法来让我们选择我们关注的JPoint。这个方法就是利用pointcuts来完成的。
  • 通过pointcuts选择了目标JPoint后,我们总得干点什么吧?这就用上了advice。advice包括好几种类型,一般情况下都够我们用了。

上面这些东西都有点像函数定义,在Java中,这些东西都是要放到一个class里的。在AspectJ中,也有类似的数据结构,叫aspect。

public aspect 名字 {//aspect关键字和class的功能一样,文件名以.aj结尾
 pointcuts定义...
 advice定义...
}

你看,通过这种方式,定义一个aspect类,就把相关的JPoint和advice包含起来,是不是形成了一个“关注面”?比如:

  • 我们定义一个LogAspect,在LogAspect中,我们在关键JPoint上设置advice,这些advice就是打印日志
  • 再定义一个SecurityCheckAspect,在这个Aspect中,我们在关键JPoint上设置advice,这些advice将检查调用app是否有权限。

通过这种方式,我们在原来的JPoint中,就不需要写log打印的代码,也不需要写权限检查的代码了。所有这些关注点都挪到对应的Aspectj文件中来控制。恩,这就是AOP的精髓。

注意,读者在把玩代码时候,一定会碰到AspectJ语法不熟悉的问题。所以请读者记得随时参考官网的文档。这里有一个官方的语法大全:

http://www.eclipse.org/aspectj/doc/released/quick5.pdf 或者官方的另外一个文档也可以:

http://www.eclipse.org/aspectj/doc/released/progguide/semantics.html

3.2.4  参数传递和JPoint信息

(1) 参数传递

到此,AspectJ最基本的东西其实讲差不多了,但是在实际使用AspectJ的时候,你会发现前面的内容还欠缺一点,尤其是advice的地方:

l 前面介绍的advice都是没有参数信息的,而JPoint肯定是或多或少有参数的。而且advice既然是对JPoint的截获或者hook也好,肯定需要利用传入给JPoint的参数干点什么事情。比方所around advice,我可以对传入的参数进行检查,如果参数不合法,我就直接返回,根本就不需要调用proceed做处理。

往advice传参数比较简单,就是利用前面提到的this(),target(),args()等方法。另外,整个pointcuts和advice编写的语法也有一些区别。具体方法如下:

先在pointcuts定义时候指定参数类型和名字

pointcut testAll(Test.TestDerived derived,int x):call(*Test.TestDerived.testMethod(..))
  && target(derived)&& args(x)

  注意上述pointcuts的写法,首先在testAll中定义参数类型和参数名。这一点和定义一个函数完全一样

接着看target和args。此处的target和args括号中用得是参数名。而参数名则是在前面pointcuts中定义好的。这属于target和args的另外一种用法。

  注意,增加参数并不会影响pointcuts对JPoint的匹配,上面的pointcuts选择和

pointcut testAll():call(*Test.TestDerived.testMethod(..)) && target(Test.TestDerived) &&args(int)是一样的

只不过我们需要把参数传入advice,才需要改造

接下来是修改advice:

Object around(Test.TestDerived derived,int x):testAll(derived,x){
 System.out.println(" arg1=" + derived);
 System.out.println(" arg2=" + x);
 return proceed(derived,x); //注意,proceed就必须把所有参数传进去。
}

advice的定义现在也和函数定义一样,把参数类型和参数名传进来。

接着把参数名传给pointcuts,此处是testAll。注意,advice必须和使用的pointcuts在参数类型和名字上保持一致。

然后在advice的代码中,你就可以引用参数了,比如derived和x,都可以打印出来。

总结,参数传递其实并不复杂,关键是得记住语法:

  • pointcuts修改:像定义函数一样定义pointcuts,然后在this,target或args中绑定参数名(注意,不再是参数类型,而是参数名)。
  • advice修改:也像定义函数一样定义advice,然后在冒号后面的pointcuts中绑定参数名(注意是参数名)
  • 在advice的代码中使用参数名。

(2) JoinPoint信息收集

我们前面示例中都打印出了JPoint的信息,比如当前调用的是哪个函数,JPoint位于哪一行代码。这些都属于JPoint的信息。AspectJ为我们提供如下信息:

  • thisJoinpoint对象:在advice代码中可直接使用。代表JPoint每次被触发时的一些动态信息,比如参数啊之类的、
  • thisJoinpointStatic对象:在advice代码中可直接使用,代表JPoint中那些不变的东西。比如这个JPoint的类型,JPoint所处的代码位置等。
  • thisEnclosingJoinPointStaticPart对象:在advice代码中可直接使用。也代表JPoint中不可变的部分,但是它包含的东西和JPoint的类型有关,比如对一个call类型JPoint而言,thisEnclosingJoinPointStaticPart代表包含调用这个JPoint的函数的信息。对一个handler类型的JPoint而言,它代表包含这个try/catch的函数的信息。

关于thisJoinpoint,建议大家直接查看API文档,非常简单。其地址位于http://www.eclipse.org/aspectj/doc/released/runtime-api/index.html。

四、使用AOP的例子

现在正式回到我们的AndroidAopDemo这个例子来。我们的目标是为AopDemoActivity的几个Activity生命周期函数加上log,另外为checkPhoneState加上权限检查。一切都用AOP来集中控制。

前面提到说AspectJ需要编写aj文件,然后把AOP代码放到aj文件中。但是在Android开发中,我建议不要使用aj文件。因为aj文件只有AspectJ编译器才认识,而Android编译器不认识这种文件。所以当更新了aj文件后,编译器认为源码没有发生变化,所以不会编译它。

当然,这种问题在其他不认识aj文件的java编译环境中也存在。所以,AspectJ提供了一种基于注解的方法来把AOP实现到一个普通的Java文件中。这样我们就把AOP当做一个普通的Java文件来编写、编译就好。

4.1  打印Log

马上来看AopDemoActivity对应的DemoAspect.java文件吧。先看输出日志第一版本:

[-->第一版本]

package com.androidaop.demo;
import android.util.Log;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.JoinPoint;

@Aspect //必须使用@AspectJ标注,这样class DemoAspect就等同于 aspect DemoAspect了
public class DemoAspect {
 staticfinal String TAG = "DemoAspect";
/*
@Pointcut:pointcut也变成了一个注解,这个注解是针对一个函数的,比如此处的logForActivity()
其实它代表了这个pointcut的名字。如果是带参数的pointcut,则把参数类型和名字放到
代表pointcut名字的logForActivity中,然后在@Pointcut注解中使用参数名。
基本和以前一样,只是写起来比较奇特一点。后面我们会介绍带参数的例子
*/
@Pointcut("execution(* com.androidaop.demo.AopDemoActivity.onCreate(..)) ||"
  +"execution(* com.androidaop.demo.AopDemoActivity.onStart(..))")
public void logForActivity(){}; //注意,这个函数必须要有实现,否则Java编译器会报错

/*
@Before:这就是Before的advice,对于after,after -returning,和after-throwing。对于的注解格式为
@After,@AfterReturning,@AfterThrowing。Before后面跟的是pointcut名字,然后其代码块由一个函数来实现。比如此处的log。
*/
 @Before("logForActivity()")
 public void log(JoinPoint joinPoint){
  //对于使用Annotation的AspectJ而言,JoinPoint就不能直接在代码里得到多了,而需要通过
  //参数传递进来。
  Log.e(TAG, joinPoint.toShortString());
 }
}

提示:如果开发者已经切到AndroidStudio的话,AspectJ注解是可以被识别并能自动补齐。

上面的例子仅仅是列出了onCreate和onStart两个函数的日志,如果想在所有的onXXX这样的函数里加上log,该怎么改呢?

@Pointcut("execution(* *..AopDemoActivity.on*(..))")
public void logForActivity(){};

图8给出这个例子的执行结果:

图8  AopDemoActivity执行结果

4.2  检查权限

4.2.1  使用注解

检查权限这个功能的实现也可以采用刚才打印log那样,但是这样就没有太多意思了。我们玩点高级的。不过这个高级的玩法也是来源于现实需求:

  • 权限检查一般是针对API的,比如调用者是否有权限调用某个函数。
  • API往往是通过SDK发布的。一般而言,我们会在这个函数的注释里说明需要调用者声明哪些权限。
  • 然后我们在API检查调用者是不是申明了文档中列出的权限。

如果我有10个API,10个不同的权限,那么在10个函数的注释里都要写,太麻烦了。怎么办?这个时候我想到了注解。注解的本质是源代码的描述。权限声明,从语义上来说,其实是属于API定义的一部分,二者是一个统一体,而不是分离的。

Java提供了一些默认的注解,不过此处我们要使用自己定义的注解:

package com.androidaop.demo;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

//第一个@Target表示这个注解只能给函数使用
//第二个@Retention表示注解内容需要包含的Class字节码里,属于运行时需要的。
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface SecurityCheckAnnotation {//@interface用于定义一个注解。
 publicString declaredPermission(); //declarePermssion是一个函数,其实代表了注解里的参数
}

怎么使用注解呢?接着看代码:

//为checkPhoneState使用SecurityCheckAnnotation注解,并指明调用该函数的人需要声明的权限
 @SecurityCheckAnnotation(declaredPermission="android.permission.READ_PHONE_STATE")
 private void checkPhoneState(){
  //如果不使用AOP,就得自己来检查权限
  if(checkPermission("android.permission.READ_PHONE_STATE") ==false){
   Log.e(TAG,"have no permission to read phone state");
   return;
  }
  Log.e(TAG,"Read Phone State succeed");
  return;
 }

4.2.2  检查权限

下面,我们来看看如何在AspectJ中,充分利用这注解信息来帮助我们检查权限。

/*
来看这个Pointcut,首先,它在选择Jpoint的时候,把@SecurityCheckAnnotation使用上了,这表明所有那些public的,并且携带有这个注解的API都是目标JPoint
接着,由于我们希望在函数中获取注解的信息,所有这里的poincut函数有一个参数,参数类型是
SecurityCheckAnnotation,参数名为ann
这个参数我们需要在后面的advice里用上,所以pointcut还使用了@annotation(ann)这种方法来告诉
AspectJ,这个ann是一个注解
*/
 @Pointcut("execution(@SecurityCheckAnnotation public * *..*.*(..)) && @annotation(ann)")
 publicvoid checkPermssion(SecurityCheckAnnotationann){};

/*
接下来是advice,advice的真正功能由check函数来实现,这个check函数第二个参数就是我们想要
的注解。在实际运行过程中,AspectJ会把这个信息从JPoint中提出出来并传递给check函数。
*/
 @Before("checkPermssion(securityCheckAnnotation)")
 publicvoid check(JoinPoint joinPoint,SecurityCheckAnnotationsecurityCheckAnnotation){
  //从注解信息中获取声明的权限。
  String neededPermission = securityCheckAnnotation.declaredPermission();
  Log.e(TAG, joinPoint.toShortString());
  Log.e(TAG, "\tneeded permission is " + neededPermission);
  return;
 }

如此这般,我们在API源码中使用的注解信息,现在就可以在AspectJ中使用了。这样,我们在源码中定义注释,然后利用AspectJ来检查。图9展示了执行的结果

图9  权限检查的例子

4.2.3  和其他模块交互

事情这样就完了?很明显没有。为什么?刚才权限检查只是简单得打出了日志,但是并没有真正去做权限检查。如何处理?这就涉及到AOP如何与一个程序中其他模块交互的问题了。初看起来容易,其实有难度。

比如,DemoAspect虽然是一个类,但是没有构造函数。而且,我们也没有在代码中主动去构造它。根据AsepctJ的说明,DemoAspect不需要我们自己去构造,AspectJ在编译的时候会把构造函数给你自动加上。具体在程序什么位置加上,其实是有规律的,但是我们并不知道,也不要去知道。

这样的话,DemoAspect岂不是除了打打log就没什么作用了?非也!以此例的权限检查为例,我们需要:

  • 把真正进行权限检查的地方封装到一个模块里,比如SecurityCheck中。
  • SecurityCheck往往在一个程序中只会有一个实例。所以可以为它提供一个函数,比如getInstance以获取SecurityCheck实例对象。
  • 我们就可以在DemoAspect中获取这个对象,然后调用它的check函数,把最终的工作由SecurityCheck来检查了。

恩,这其实是Aspect的真正作用,它负责收集Jpoint,设置advice。一些简单的功能可在Aspect中来完成,而一些复杂的功能,则只是有Aspect来统一收集信息,并交给专业模块来处理。

最终代码:

 @Before("checkPermssion(securityCheckAnnotation)")
 publicvoid check(JoinPoint joinPoint,SecurityCheckAnnotation securityCheckAnnotation){
  String neededPermission = securityCheckAnnotation.declaredPermission();
  Log.e(TAG, "\tneeded permission is " + neededPermission);
  SecurityCheckManager manager =SecurityCheckManager.getInstanc();
  if(manager.checkPermission(neededPermission) == false){
   throw new SecurityException("Need to declare permission:" + neededPermission);
  }
  return;
 }

图10所示为最终的执行结果。

图10  执行真正的权限检查

注意,

编译:请在ubuntu下使用gradle assemble。编译结果放在out/apps/目录下。

五、其他、总结和参考文献

最后我们来讲讲其他一些内容。首先是AspectJ的编译。

5.1  AspectJ编译

  • AspectJ比较强大,除了支持对source文件(即aj文件、或@AspectJ注解的Java文件,或普通java文件)直接进行编译外,
  • 还能对Java字节码(即对class文件)进行处理。有感兴趣的同学可以对aspectj-test小例子的class文件进行反编译,你会发现AspectJ无非是在被选中的JPoint的地方加一些hook函数。当然Before就是在调用JPoint之前加,After就是在JPoint返回之前加。
  • 更高级的做法是当class文件被加载到虚拟机后,由虚拟机根据AOP的规则进行hook。

在Android里边,我们用得是第二种方法,即对class文件进行处理。来看看代码:

//AndroidAopDemo.build.gradle
//此处是编译一个App,所以使用的applicationVariants变量,否则使用libraryVariants变量
//这是由Android插件引入的。所以,需要import com.android.build.gradle.AppPlugin;
android.applicationVariants.all { variant ->
 /*
  这段代码之意是:
  当app编译个每个variant之后,在javaCompile任务的最后添加一个action。此action
  调用ajc函数,对上一步生成的class文件进行aspectj处理。
 */
 AppPluginplugin = project.plugins.getPlugin(AppPlugin)
 JavaCompile javaCompile = variant.javaCompile
 javaCompile.doLast{
  String bootclasspath =plugin.project.android.bootClasspath.join(File.pathSeparator)
  //ajc是一个函数,位于utils.gradle中
  ajc(bootclasspath,javaCompile)
 }
}

ajc函数其实和我们手动试玩aspectj-test目标一样,只是我们没有直接调用ajc命令,而是利用AspectJ提供的API做了和ajc命令一样的事情。

import org.aspectj.bridge.IMessage
import org.aspectj.bridge.MessageHandler
import org.aspectj.tools.ajc.Main

def ajc(String androidbootClassFiles,JavaCompile javaCompile){
 String[] args = ["-showWeaveInfo",
      "-1.8", //1.8是为了兼容java 8。请根据自己java的版本合理设置它
      "-inpath",javaCompile.destinationDir.toString(),
      "-aspectpath",javaCompile.classpath.asPath,
      "-d",javaCompile.destinationDir.toString(),
      "-classpath",javaCompile.classpath.asPath,
     "-bootclasspath", androidbootClassFiles]
 MessageHandlerhandler = new MessageHandler(true);
 new Main().run(args,handler)

 deflog = 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
   throw 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;
  }
 }
 }

主要利用了https://eclipse.org/aspectj/doc/released/devguide/ajc-ref.html中TheAspectJ compiler API一节的内容。由于代码已经在csdn git上,大家下载过来直接用即可。

5.2  总结

除了hook之外,AspectJ还可以为目标类添加变量。另外,AspectJ也有抽象,继承等各种更高级的玩法。根据本文前面的介绍,这些高级玩法一定要靠需求来驱动。AspectJ肯定对原程序是有影响的,如若贸然使用高级用法,则可能带来一些未知的后果。关于这些内容,读者根据情况自行阅读文后所列的参考文献。

最后再来看一个图。

图11 未使用AOP的情况

图11中,左边是一个程序的三个基于OOP而划分的模块(也就是concern)。安全、业务逻辑、交易管理。这三个模块在设计图上一定是互相独立,互不干扰的。

但是在右图实现的时候,这三个模块就搅在一起了。这和我们在AndroidAopDemo中检查权限的例子中完全一样。在业务逻辑的时候,需要显示调用安全检查模块。

自从有了AOP,我们就可以去掉业务逻辑中显示调用安全检查的内容,使得代码归于干净,各个模块又能各司其职。而这之中千丝万缕的联系,都由AOP来连接和管理,岂不美哉?!

5.3  参考文献

[1]  Manning.AspectJ.in.Action第二版

看书还是要挑简单易懂的,AOP概念并不复杂,而AspectJ也有很多书,但是真正写得通俗易懂的就是这本,虽然它本意是介绍Spring中的AOP,但对AspectJ的解释真得是非常到位,而且还有对@AspectJ注解的介绍。本文除第一个图外,其他参考用图全是来自于此书。

[2]  http://fernandocejas.com/2014/08/03/aspect-oriented-programming-in-android/

Android中如何使用AspectJ,最重要的是它教会我们怎么使用aspectj编译工具API。

好了,以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对我们的支持。

(0)

相关推荐

  • Android中AOP的应用实践之过滤重复点击

    前言 大家对AOP应该都不陌生, 就算没有用过也肯定听说过,切面编程一直是一个热点的话题,AOP即Aspect Oriented Programming的缩写,习惯称为切面编程;与OOP(面向对象编程)万物模块化的思想不同,AOP则是将涉及到众多模块的某一类问题进行统一管理,AOP的优点是将业务逻辑与系统化功能高度解耦,让我们在开发过程中可以只专注于业务逻辑,其他一些系统化功能(如路由.日志.权限控制.拦截器.埋点.事件防抖等)则由AOP统一处理; AspectJ简介 AOP是一种编程思想,或者

  • 浅谈Android面向切面编程(AOP)

    一.简述 1.AOP的概念 如果你用java做过后台开发,那么你一定知道AOP这个概念.如果不知道也无妨,套用百度百科的介绍,也能让你明白这玩意是干什么的: AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术.AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型.利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合

  • Android AOP之注解处理解释器详解(二)

    Android APO 注解处理解释器 相关文章: Android AOP注解Annotation详解(一) Android AOP之注解处理解释器详解(二) Android AOP 注解详解及简单使用实例(三) 一.提取Annotation信息 当开发者使用了Annotation修饰了类.方法.Field等成员之后,这些Annotation不会自己生效,必须由开发者提供相应的代码来提取并处理Annotation信息.这些处理提取和处理Annotation的代码统称为APT(Annotation

  • Android AOP 注解详解及简单使用实例(三)

    Android  注解 相关文章: Android AOP注解Annotation详解(一) Android AOP之注解处理解释器详解(二) Android AOP 注解详解及简单使用实例(三) 一.简介 在Android 里面 注解主要用来干这么几件事: 和编译器一起给你一些提示警告信息. 配合一些ide 可以更加方便快捷 安全有效的编写Java代码.谷歌出的support-annotations这个库 就是主要干这个的. 和反射一起 提供一些类似于spring 可配置的功能,方便简洁. 二

  • AndroidStudio 配置 AspectJ 环境实现AOP的方法

    昨天看了一段android配置aspectj实现AOP的直播视频,就试着自己配置了一下,可能是因为我自己的AndroidStudio环境的问题,碰到了不少的坑(其实还是因为对gradle理解的不多),但总归是配置好了,就分享一下. 试了两种方式,不过项目下的build.gradle,没什么变化,直接看一下代码吧: build.gradle(项目下) buildscript { ext { //android appcompat支持库版本 androidSupportVersion = '26.1

  • Android AOP框架AspectJ使用详解

    前言 之前了解过android的AOP框架,用法主要用来打日志:现在有一个需求需要函数在新线程中执行,并且函数主体执行完之后,在UI线程返回结果.想到手写的话,每次都要new Thread的操作,比较麻烦:因此就尝试用注解的方法解决这个问题. AspectJ的使用核心就是它的编译器,它就做了一件事,将AspectJ的代码在编译期插入目标程序当中,运行时跟在其它地方没什么两样,因此要使用它最关键的就是使用它的编译器去编译代码ajc.ajc会构建目标程序与AspectJ代码的联系,在编译期将Aspe

  • Android 采用AOP方式封装6.0权限管理的方法

    [一]背景 6.0运行时申请权限已经是一个老生常谈的内容了,最近项目TargetSDKVersion升到23以上,所以我们也需要做权限管理,我想到的需求是这样的: 1.支持单个权限.多个权限申请 2.运行时申请 3.无侵入式申请,无需关注权限申请的逻辑 4.除了Activity.Fragment之外,还需要支持Service中申请 5.对国产手机做兼容处理 第一.二点,Google都有对应的API: 第三点可以通过自定义注解+AOP切面方式来解决.为什么采用AOP方式呢?首先看AOP定义: 面向

  • Android AOP注解Annotation详解(一)

    Android 注解Annotation 相关文章: Android AOP注解Annotation详解(一) Android AOP之注解处理解释器详解(二) Android AOP 注解详解及简单使用实例(三) Android AOP 等在Android上应用越来越广泛,例如框架ButterKnife,Dagger2,EventBus3等等,这里我自己总结了一个学习路程. - Java的注解Annotation - 注解处理解析器APT(Annotation Processing Tool)

  • Android中AOP(面向切向编程)的深入讲解

    一.闲谈AOP 大家都知道OOP,即ObjectOriented Programming,面向对象编程.而本文要介绍的是AOP.AOP是Aspect Oriented Programming的缩写,中译文为面向切向编程.OOP和AOP是什么关系呢? 首先: l OOP和AOP都是方法论.我记得在刚学习C++的时候,最难学的并不是C++的语法,而是C++所代表的那种看问题的方法,即OOP.同样,今天在AOP中,我发现其难度并不在利用AOP干活,而是从AOP的角度来看待问题,设计解决方法.这就是为什

  • JScript|Event]面向事件驱动的编程(二)--实例讲解:将span模拟成超连接

    作者:泣红亭 在上一篇文章<面向事件驱动的编程>中我讲了三种将事件绑定到元素的方法,而推荐使用第三种方法,即使用attachEvent/addEventListener来绑定.上一篇文章的主旨是告诉大家如何使用事件,而这一篇文章的主旨是让大家弄懂如何灵活应用事件来批处理某一类的对象行为. 首先讲一讲事件传递的概念.什么是事件传递?举个现实的例子,有个人捏了一下你的手指,你可能会说他捏了你手指,也可能会说他捏了你的手,甚至可能会说他捏了你.事实上三种说法都没错,在浏览器事件的执行中亦有相似的情况

  • yui3的AOP(面向切面编程)和OOP(面向对象编程)

    首先请把手放胸前成沉思状:我上了生活,还是被生活上了自己? 没想出答案把,恩,可以读下文了.从语义角度讲,同一事物的不同表述可以反映人的主观视角的不同,从哲学角度将,世界观影响方法论,我们看事物的角度不同,有时会得出截然相悖的结论,从而会影响我们的做事方式和行为准则,现实生活如此,在丰富多彩的编程语言中更是如此,编程模式充满了对现实世界的各种模拟,包括是面向过程,面向对象,还有面向切面.我们大概已经非常熟悉面向过程和面向对象,切面的英文是Aspects(有时译作方面,我感觉用切面更能贴切的表达A

  • Javascript aop(面向切面编程)之around(环绕)分析

    Aop又叫面向切面编程,其中"通知"是切面的具体实现,分为before(前置通知).after(后置通知).around(环绕通知),用过spring的同学肯定对它非常熟悉,而在js中,AOP是一个被严重忽视的技术点.但是利用aop可以有效的改善js代码逻辑,比如前端框架dojo和yui3中AOP则被提升至自定义事件的一种内在机制,在源码中随处可见.得益于这种抽象使得dojo的自定义事件异常强大和灵活.dojo中aop的实现在dojo/aspect模块中,主要有三个方法:before.

  • MVC AOP面向切面编程简单介绍及实例

    MVC AOP面向切面编程 AOP这个词相信大家都没有接触太多过,但是实际上你们已经有所接触了,就在设计模式中.AOP所用的思想其实和设计模式是一样的,即在不修改原代码的情况下统一增加或者修改功能.还有,AOP大多用在spring里面,但是本文所写的只是在MVC中的应用,要注意. 一.简介 所谓AOP(Aspect Oriented Programming的缩写)意为面向切面的编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术.AOP是OOP的延续,是软件开发中的一个热点,也是

  • SpringBoot整合aop面向切面编程过程解析

    这篇文章主要介绍了SpringBoot整合aop面向切面编程过程解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术.AOP是Spring框架中的一个重要内容,它通过对既有程序定义一个切入点,然后在其前后切入不同的执行内容,比如常见的有:打开数据库连接/关闭数据库连接.打开事务/关闭事务.记录日

  • Spring使用AspectJ的注解式实现AOP面向切面编程

    1.认识Spring AOP 1.1 AOP的简介 AOP:面向切面编程,相对于OOP面向对象编程. Spring的AOP的存在目的是为了解耦.AOP可以让一组类共享相同的行为.在OOP中只能通过继承类和实现接口,来使代码的耦合度增强,而且类的继承只能为单继承,阻碍更多行为添加到一组类上,AOP弥补了OOP的不足. 1.2 AOP中的概念 切入点(pointcut): 切入点(pointcut):在哪些类.哪些方法上切入. 通知(advice):在方法前.方法后.方法前后做什么. 切面(aspe

  • java开发AOP面向切面编程入门

    目录 引言 不好的解决方案 面向过程的解决方案 使用继承解决方案 使用聚合的解决方案 面向切面的编程基本概念 基于Spring面向切面程序实现 小结 引言 在实际应用场景中,我们封装一个学生的类,这个类用于封装学生的日常行为,如:上学.吃饭.上课等.然而,在疫情期间,学生上学时入校.吃饭时进入餐厅,需要测温查验证件等行为,拿到这样的需求我们怎么办? 不好的解决方案 面向过程的解决方案 遇到问题解决问题,在上学.吃饭方法中加上测温.查验证件方法,或者在学生类中提炼一个测温查验证件私有的方法,在需要

  • Spring框架AOP面向切面编程原理全面分析

    目录 1.什么是AOP AOP面向切面的优势 AOP需要添加的依赖 2.简述AOP工作运行原理 动态创建的总结: 3.使用Spring创建AOP 测试类 Spring.xml 1.什么是AOP AOP:Aspect Oriented Programming ⾯向切⾯编程. AOP面向切面的优势 降低模块之间的耦合度. 使系统更容易扩展. 更好的代码复⽤. ⾮业务代码更加集中,不分散,便于统⼀管理. 业务代码更加简洁存粹,不参杂其他代码的影响. AOP 是对⾯向对象编程的⼀个补充,在运⾏时,动态地

随机推荐