AspectJ教程--AOP面向切面编程框架(Android)
2017-10-13 14:02
579 查看
AOP的概念很久前就接触过了,不过没有真正写过项目,甚至Demo都没有,今天把这点缺陷补上。
推荐两篇文章(本文部分图片引自这两篇文章):
1. 【翻译】Android中的AOP编程
2. 【深入理解Android之AOP】
OOP:面向对象思想简单理解就是,需要把各功能封装为独立模块,然后把他们简单拼装成为产品。Android系统的各个模块封装就遵循OOP(下图)。
AOP:在这些独立的模块间,在特定的切入点进行hook,将共同的逻辑添加到模块中而不影响原有模块的独立性。下图,在不同的模块中加入日志、缓存、性能检测功能,并不影响原有的架构。
持久化操作
性能监控
数据校验
缓存
等…
代码冗余
逻辑不清晰
重构不方便
3.3.1.1 第一步 配置AspectJ
在app的gradle文件中添加如下代码,作用:使用ajc代替javac编译java代码。具体说明见:【翻译】Android中的AOP编程
3.3.1.2 第二步 自定义注解
3.3.1.3 第三步 实现各模块,并加上@TimerTrace注解
拿登录模块来举例,该模块中已经不包含耗时统计的逻辑。
3.3.1.4 第四步 用@Aspect标注切面类
3.3.1.5 第五步 在切面类中定义PointCut(切入点)
3.3.1.6 第六步 在切面类中定义Advance(通知)
具体注入的代码
3.3.1.6 第六步 依次执行登录、转账、本地上传
3.3.1.7 总结优点
减少代码冗余
代码逻辑更清晰
方便扩展、重构
原始的AOP模式
动态代理实现AOP
等…
4.1 AspectJ介绍
AspectJ是一个面向切面的框架,它扩展了Java语言。AspectJ定义了AOP语法,所以它有一个专门的编译器用来生成遵守Java字节编码规范的Class文件。
使用AspectJ有两种方法:
完全使用 AspectJ语法(资料比较少,用AS未编译成功,下面不讲)
使用java开发,加上AspectJ注解(推荐使用,下面语法主要讲解注解的语法)。
官方网站:
AspectJ官方网站(下载AspectJ的jar包,更新AspectJ的adt等):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
4.2 语法
本节只讲解AspectJ的注解语法
4.2.1 Join Points介绍
在Aspect的术语章节讲过,join Point(连接点):所有可以注入代码的地方,在AspectJ中是有规定的,只有在下表的几个地方才认为是join Ponit。
4.2.2 Pointcuts介绍
在Aspect的术语章节讲过,PointCut(切入点):告诉AOP框架,我应该在哪个join point注入一段代码。那么Pointcuts就是筛选出来的符合条件的所有切入点。
1、 直接选择Join Point
MethodSignature匹配规则:
@注解 访问权限 返回值的类型 包名.函数名(参数)
1. @注解和访问权限(public/private/protect,以及static/final)属于可选项。如果不设置它们,则默认都会选择。以访问权限为例,如果没有设置访问权限作为条件,那么public,private,protect及static、final的函数都会进行搜索。
2. 返回值类型就是普通的函数的返回值类型。如果不限定类型的话,就用
3. 包名.函数名用于查找匹配的函数。可以使用通配符,包括
比如:
java.*.Date:可以表示java.sql.Date,也可以表示java.util.Date
Test*:可以表示TestBase,也可以表示TestDervied
java..*:表示java任意子类
java..*Model+:表示Java任意package中名字以Model结尾的子类,比如TabelModel,TreeModel
等
4. 最后来看函数的参数。参数匹配比较简单,主要是参数类型,比如:
(int, char):表示参数只有两个,并且第一个参数类型是int,第二个参数类型是char
(String, ..):表示至少有一个参数。并且第一个参数类型是String,后面参数类型不限。在参数匹配中,
..代表任意参数个数和类型
(Object …):表示不定个数的参数,且类型都是Object,这里的…不是通配符,而是Java中代表不定参数的意思
ConstructorSignature匹配规则:
Constructorsignature和Method Signature类似,只不过构造函数没有返回值,而且函数名必须叫new。比如:
public *..TestDerived.new(..):
public:选择public访问权限
*..代表任意包名
TestDerived.new:代表TestDerived的构造函数
(..):代表参数个数和类型都是任意
FieldSignature匹配规则:
Field Signature标准格式:
@注解 访问权限 类型 类名.成员变量名
其中,@注解和访问权限是可选的类型:成员变量类型,
比如,
set(*.base):表示设置所有包名下base变量时的JPoint
2、 间接选择Join Point
这一类,在demo中没有示例,有兴趣的自己在Demo中添加查看效果。
4.2.3 advice介绍
前面例子中已经用过了,具体看下面表中说明即可。
4.3 实现步骤
PS:这一节已经很详细的在 3.3.1中写了,还有代码示例,往上看。
4.4 AspectJ原理
4.4.1 找到AS下的class文件
路径如下:app->build->intermediates->classes->debug->com包下即是我们使用ajc编译后的class代码。
4.4.2 Around原理
使用Around处理后,编译出来的class文件
可以看出来,在这个方法的开头和结尾,都被注入了一些代码,成为我们最终运行到手机上的class文件。
如果有对应的AOP使用场景,建议使用AspectJ,你会感觉到很爽的。
推荐两篇文章(本文部分图片引自这两篇文章):
1. 【翻译】Android中的AOP编程
2. 【深入理解Android之AOP】
1. 本篇文章总览
2. 什么是AOP
2.1 定义
AOP是Aspect Oriented Program的首字母缩写,译为:面向切面编程。类似的OOP,译为:面向对象编程。OOP:面向对象思想简单理解就是,需要把各功能封装为独立模块,然后把他们简单拼装成为产品。Android系统的各个模块封装就遵循OOP(下图)。
AOP:在这些独立的模块间,在特定的切入点进行hook,将共同的逻辑添加到模块中而不影响原有模块的独立性。下图,在不同的模块中加入日志、缓存、性能检测功能,并不影响原有的架构。
2.2 相关术语
术语名称 | 术语解释 |
---|---|
Cross-cutting concerns(横切关注点) | 多个模块可能添加相同附属功能的点 |
Advice(通知) | 注入到class文件中的代码。典型的 Advice 类型有 before、after 和 around,分别表示在目标方法执行之前、执行后和完全替代目标方法执行的代码。 除了在方法中注入代码,也可能会对代码做其他修改,比如在一个class中增加字段或者接口。 |
join Point(连接点) | 所有可以注入代码的地方 |
PointCut(切入点) | 告诉AOP框架,我应该在哪个join point注入一段代码 |
Aspect(切面) | 由PointCut和Advice组成的公共逻辑成为切面,切面逻辑只需开发一次,多处调用 |
Weaving(织入) | 注入代码到目标位置 |
2.3 AOP使用场景
日志相关持久化操作
性能监控
数据校验
缓存
等…
3 OOP和AOP实现具体需求
统计三个模块耗时。3.1 OOP实现转向AOP实现图例
3.2 OOP实现
3.2.1 编写登录模块
简写代码如下:/** * OOP 登录模块 * Created by Administrator on 2017/10/13. */ public class LoginUtils { private static final String TAG = "OOP"; public static boolean Login(String userName, String passWord){ long start=System.currentTimeMillis(); long end; StringBuffer stringBuffer = new StringBuffer(); try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } if ("张三".equals(userName) && "123456".equals(passWord)){ end = System.currentTimeMillis(); stringBuffer.append("登录成功,耗时:") .append(end - start); Log.e(TAG,stringBuffer.toString()); return true; }else{ end = System.currentTimeMillis(); stringBuffer.append("登录失败,耗时:") .append(end - start); Log.e(TAG,stringBuffer.toString()); return false; } } }
3.2.2 编写文件上传模块
简写代码如下/** * OOP 文件上传模块 * Created by Administrator on 2017/10/13. */ public class UploadFileUtils { private static final String TAG = "OOP"; public static boolean upload(String url, String path){ long start=System.currentTimeMillis(); long end; StringBuffer stringBuffer1 = new StringBuffer(); try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } StringBuffer stringBuffer = new StringBuffer(); stringBuffer.append("从本地上传") .append(path) .append("到") .append(url); Log.e(TAG,stringBuffer.toString()); end = System.currentTimeMillis(); stringBuffer1.append("文件上传成功,耗时:") .append(end - start); Log.e(TAG,stringBuffer1.toString()); return true; } }
3.2.3 依次执行登录、转账、本地上传
日志如下:10-12 13:28:17.300 14605-14605/com.aspectjdemo E/OOP: 登录成功,耗时:2000 10-12 13:28:19.315 14605-14605/com.aspectjdemo E/OOP: 从111112账户转出100.0到222221 10-12 13:28:19.315 14605-14605/com.aspectjdemo E/OOP: 转账成功,耗时:2001 10-12 13:28:21.317 14605-14605/com.aspectjdemo E/OOP: 从本地上传/sd/example.png到www.baidu.com 10-12 13:28:21.317 14605-14605/com.aspectjdemo E/OOP: 文件上传成功,耗时:2001
3.2.4 找出弊端
按照上面的实现方式,弊端有以下几个:代码冗余
逻辑不清晰
重构不方便
3.3 AOP实现
3.3.1 AspectJ实现AOP
AspectJ是一个非侵入式的AOP框架,下一章专门介绍。此处只写Android Studio的实现方式,Eclipse实现方式不太一样。3.3.1.1 第一步 配置AspectJ
在app的gradle文件中添加如下代码,作用:使用ajc代替javac编译java代码。具体说明见:【翻译】Android中的AOP编程
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' } } apply plugin: 'com.android.application' 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; } } } } dependencies { compile 'org.aspectj:aspectjrt:1.8.11' }
3.3.1.2 第二步 自定义注解
/** * 自定义注解 * Created by Administrator on 2017/10/13. */ @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface TimeTrace { String value(); }
3.3.1.3 第三步 实现各模块,并加上@TimerTrace注解
拿登录模块来举例,该模块中已经不包含耗时统计的逻辑。
/** * AOP 登录模块 * Created by Administrator on 2017/10/13. */ public class AOPLoginUtils { private static final String TAG = "OOP"; @TimeTrace(value = "登录") public static boolean Login(String userName, String passWord){ try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } if ("张三".equals(userName) && "123456".equals(passWord)){ return true; }else{ return false; } } }
3.3.1.4 第四步 用@Aspect标注切面类
@Aspect public class TimeTraceAspect { }
3.3.1.5 第五步 在切面类中定义PointCut(切入点)
// 语法:execution(@注解 访问权限 返回值的类型 包名.函数名(参数)) // 表示:使用TimeTrace注解的任意类型返回值任意方法名(任意参数) @Pointcut("execution(@com.aspectjdemo.aop.TimeTrace * *(..))") public void myPointCut(){ }
3.3.1.6 第六步 在切面类中定义Advance(通知)
具体注入的代码
// Advance比较常用的有:Before():方法执行前,After():方法执行后,Around():代替原有逻辑 @Around("myPointCut()") public Object dealPoint(ProceedingJoinPoint point) throws Throwable { // 方法执行前先记录时间 long start=System.currentTimeMillis(); MethodSignature methodSignature = (MethodSignature) point.getSignature(); // 获取注解 TimeTrace annotation = methodSignature.getMethod().getAnnotation(TimeTrace.class); String value = annotation.value(); // 执行原方法体 Object proceed = point.proceed(); // 方法执行完成后,记录时间,打印日志 long end = System.currentTimeMillis(); StringBuffer stringBuffer = new StringBuffer(); if (proceed instanceof Boolean){ // 返回的是boolean if ((Boolean)proceed){ stringBuffer.append(value) .append("成功,耗时:") .append(end - start); }else{ stringBuffer.append(value) .append("失败,耗时:") .append(end - start); } } Log.e(TAG,stringBuffer.toString()); return proceed; }
3.3.1.6 第六步 依次执行登录、转账、本地上传
10-12 13:25:44.106 12332-12332/com.aspectjdemo E/AOP: 登录成功,耗时:2001 10-12 13:25:46.136 12332-12332/com.aspectjdemo E/OOP: 从111112账户转出100.0到222221 10-12 13:25:46.137 12332-12332/com.aspectjdemo E/AOP: 转账成功,耗时:2002 10-12 13:25:48.140 12332-12332/com.aspectjdemo E/OOP: 从本地上传/sd/example.png到www.baidu.com 10-12 13:25:48.140 12332-12332/com.aspectjdemo E/AOP: 文件上传成功,耗时:2001
3.3.1.7 总结优点
减少代码冗余
代码逻辑更清晰
方便扩展、重构
3.3.2 其他方式实现AOP
不是本文重点,不深入,其实是我还没了解其他方式(~ ̄▽ ̄)~,稍微罗列一下。原始的AOP模式
动态代理实现AOP
等…
4 AspectJ详解
建议这一部分直接去看这个文章,这个文章,这部分很详细,很多语法,各种说明:【深入理解Android之AOP】4.1 AspectJ介绍
AspectJ是一个面向切面的框架,它扩展了Java语言。AspectJ定义了AOP语法,所以它有一个专门的编译器用来生成遵守Java字节编码规范的Class文件。
使用AspectJ有两种方法:
完全使用 AspectJ语法(资料比较少,用AS未编译成功,下面不讲)
使用java开发,加上AspectJ注解(推荐使用,下面语法主要讲解注解的语法)。
官方网站:
AspectJ官方网站(下载AspectJ的jar包,更新AspectJ的adt等):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
4.2 语法
本节只讲解AspectJ的注解语法
4.2.1 Join Points介绍
在Aspect的术语章节讲过,join Point(连接点):所有可以注入代码的地方,在AspectJ中是有规定的,只有在下表的几个地方才认为是join Ponit。
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的内容,稍后再说 |
在Aspect的术语章节讲过,PointCut(切入点):告诉AOP框架,我应该在哪个join point注入一段代码。那么Pointcuts就是筛选出来的符合条件的所有切入点。
1、 直接选择Join Point
Join Point | Ponitcut语法 | 示例 |
---|---|---|
Method execution | execution(MethodSignature) | 在Activtiy的所有生命周期执行前,注入代码:@Before(“execution(* android.app.Activity.on**(..))”) |
Method call | call(MethodSignature) | 在调用指定方法后,注入代码:@Before(“execution(* android.app.Activity.on**(..))”) |
constructor call | call(ConstructorSignature) | 在调用指定构造方法后,注入代码:@Before(“call(com.aspectjdemo.aopexample.UIUtils.new())”) |
constructor execution | execution(ConstructorSignature) | 在执行指定构造方法后,注入代码:@After(“call(com.aspectjdemo.aopexample.UIUtils.new())”) |
field get | get(FieldSignature) | 在调用指定字段get方法后,注入代码:@After(“get(String com.aspectjdemo.aopexample.AspectJActivity.userName)”) |
field set | set(FieldSignature) | 在调用指定字段get方法前,注入代码:@Before(“set(String com.aspectjdemo.aopexample.AspectJActivity.userName)”) |
Object initialization | initialization(ConstructorSignature) | 在指定的对象初始化后,注入代码:@After(“initialization(com.aspectjdemo.aopexample.UIUtils.new())”) |
@注解 访问权限 返回值的类型 包名.函数名(参数)
1. @注解和访问权限(public/private/protect,以及static/final)属于可选项。如果不设置它们,则默认都会选择。以访问权限为例,如果没有设置访问权限作为条件,那么public,private,protect及static、final的函数都会进行搜索。
2. 返回值类型就是普通的函数的返回值类型。如果不限定类型的话,就用
*通配符表示
3. 包名.函数名用于查找匹配的函数。可以使用通配符,包括
*和..以及+号。其中
*号用于匹配除.号之外的任意字符,而..则表示任意子package,+号表示子类。
比如:
java.*.Date:可以表示java.sql.Date,也可以表示java.util.Date
Test*:可以表示TestBase,也可以表示TestDervied
java..*:表示java任意子类
java..*Model+:表示Java任意package中名字以Model结尾的子类,比如TabelModel,TreeModel
等
4. 最后来看函数的参数。参数匹配比较简单,主要是参数类型,比如:
(int, char):表示参数只有两个,并且第一个参数类型是int,第二个参数类型是char
(String, ..):表示至少有一个参数。并且第一个参数类型是String,后面参数类型不限。在参数匹配中,
..代表任意参数个数和类型
(Object …):表示不定个数的参数,且类型都是Object,这里的…不是通配符,而是Java中代表不定参数的意思
ConstructorSignature匹配规则:
Constructorsignature和Method Signature类似,只不过构造函数没有返回值,而且函数名必须叫new。比如:
public *..TestDerived.new(..):
public:选择public访问权限
*..代表任意包名
TestDerived.new:代表TestDerived的构造函数
(..):代表参数个数和类型都是任意
FieldSignature匹配规则:
Field Signature标准格式:
@注解 访问权限 类型 类名.成员变量名
其中,@注解和访问权限是可选的类型:成员变量类型,
*代表任意类型类名.成员变量名:成员变量名可以是*,代表任意成员变量
比如,
set(*.base):表示设置所有包名下base变量时的JPoint
2、 间接选择Join Point
这一类,在demo中没有示例,有兴趣的自己在Demo中添加查看效果。
关键词 | 说明 | 示例 |
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。 |
前面例子中已经用过了,具体看下面表中说明即可。
关键词 | 说明 | 示例 |
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 |
PS:这一节已经很详细的在 3.3.1中写了,还有代码示例,往上看。
4.4 AspectJ原理
4.4.1 找到AS下的class文件
路径如下:app->build->intermediates->classes->debug->com包下即是我们使用ajc编译后的class代码。
4.4.2 Around原理
使用Around处理后,编译出来的class文件
@TimeTrace("登录") public static boolean Login(String userName, String passWord) { JoinPoint var5 = Factory.makeJP(ajc$tjp_0, (Object)null, (Object)null, userName, passWord); TimeTraceAspect var10000 = TimeTraceAspect.aspectOf(); Object[] var6 = new Object[]{userName, passWord, var5}; return Conversions.booleanValue(var10000.dealPoint((new AOPLoginUtils$AjcClosure1(var6)).linkClosureAndJoinPoint(65536))); }
可以看出来,在这个方法的开头和结尾,都被注入了一些代码,成为我们最终运行到手机上的class文件。
5. 总结
本文只是对AspectJ做了一个入门的介绍,很多高级的用法都未加入进来,在实际项目使用时再进行挖掘吧。如果有对应的AOP使用场景,建议使用AspectJ,你会感觉到很爽的。
相关文章推荐
- Spring3.0 学习-AOP面向切面编程_Spring AOP的注解模式即Aspectj模式
- 从壹开始前后端分离【 .NET Core2.0 Api + Vue 2.0 + AOP + 分布式】框架之十 || AOP面向切面编程浅解析:简单日志记录 + 服务切面缓存
- Java实现AOP面向切面编程的实例教程
- AOP面向切面编程在Android中的使用
- Android面向切面(AOP)编程实战
- 深入理解面向切面的编程AOP、AspectJ、Spring
- spring学习--面向切面编程AOP和AspectJ
- Spring AOP编程(两种:1,传统的AOP切面编程 2,基于aspectj框架切点传统开发)
- Java实现AOP面向切面编程的实例教程
- AOP-面向切面编程——AspectJ
- spring面向切面编程AOP(Aspect-orented programming)
- 面向切面编程AOP:基于注解的配置
- python 多个装饰器组合应用,实现面向切面之AOP编程
- spring aop 面向切面编程 如何来做一个强大的日志记录功能
- AOP面向切面编程
- Spring AOP:面向切面编程简介
- Web项目中静态代理和动态代理为基础的面向切面编程AOP
- AOP 面向切面编程
- AOP(面向切面编程,翻译自MSDN)