源:http://blog.csdn.net/zuyi532/article/details/7992323
评:
AOP实现原理
AOP的实现原理可以看作是Proxy/Decorator设计模式的泛化。我们先来看Proxy模式的简单例子。
Proxy {
innerObject; // 真正的对象
f1() {
// 做一些额外的事情
innerObject.f1(); // 调用真正的对象的对应方法
// 做一些额外的事情
}
}
在Python、Ruby等动态类型语言中,只要实现了f1()方法的类,都可以被Proxy包装。在Java等静态类型语言中,则要求Proxy和被包装对象实现相同的接口。动态语言实现Proxy模式要比静态语言容易得多,动态语言实现AOP也要比静态语言容易得多。假设我们用Proxy包装了10个类,我们通过调用Proxy的f1()方法来调用这10个类的f1()方法,这样,所有的f1()调用都会执行同样的一段“额外的工作”,从而实现了“所有被Proxy包装的类,都执行一段同样的额外工作”的任务。这段“额外的工作”可能是进行日志记录,权限检查,事务管理等常见工作。
Proxy模式是可以叠加的。我们可以定义多种完成特定方面任务(Aspect),比如,我们可以定义LogProxy、SecurityProxy、TransactionProxy,分别进行日志管理、权限管理、事务管理。
LogProxy {
f1(){
// 记录方法进入信息
innerObject.f1();// 调用真正的对象的对应方法
// 记录方法退出信息
}
}
SecurityProxy {
f1(){
// 进行权限验证
innerObject.f1();// 调用真正的对象的对应方法
}
}
TransactonProxy {
f1(){
Open Transaction
innerObject.f1();// 调用真正的对象的对应方法
Close Transaction
}
}
根据AOP的惯用叫法,上述的这些Proxy也叫做Advice。这些Proxy(or Advice)可以按照一定的内外顺序套起来,最外面的Proxy会最先执行。包装f1()方法,也叫做截获(Intercept)f1()方法。Proxy/Advice有时候也叫做Interceptor。
看到这里,读者可能会产生两个问题。
问题一:上述代码采用的Proxy模式只是面向对象的特性,怎么会扯上一个新概念“面向方面(AOP)”呢?
问题二:Proxy模式虽然避免了重复“额外工作”代码的问题,但是,每个相关类都要被Proxy包装,这个工作也是很烦人。AOP Proxy如何能在应用程序中大规模使用呢?
下面我们来解答着两个问题。
对于问题一,我们来看一个复杂一点的例子。假设被包装对象有f1()和f2()两个方法都要被包装。
RealObject{
f1() {…}
f2() {…}
}
这个时候,我们应该如何做?难道让Proxy也定义f1()和f2()两个方法?就象下面代码这样?
Proxy {
innerObject; // 真正的对象
f1() {
// 做一些额外的事情
innerObject.f1(); // 调用真正的对象的对应方法
// 做一些额外的事情
}
f2() {
// 做一些额外的事情
innerObject.f2(); // 调用真正的对象的对应方法
// 做一些额外的事情
}
}
这样做有几个不利之处。一是会造成代码重复,Proxy的f1()和f2()里面的“做一些额外的事情”代码重复。二是难以扩展,被包装对象可能有多个不同的方法,不同的被包装对象需要被包装的方法也可能不同。现在的问题就变成,“Proxy如何才能包装截获任何类的任何方法?”
答案呼之欲出。对,就是Reflection。Java、Python、Ruby都支持Reflection,都支持Method(方法)对象。那么我们就利用Method Reflection编写一个能够解惑任何类的任何方法的Proxy/Advice/Interceptor。
MethodInterceptor{
around( method ){
// 做些额外的工作
method.invoke(…); // 调用真正的对象方法
// 做些额外的工作
}
}
上述的MethodInterceptor就可以分别包装和截获f1()和f2()两个方法。
这里的method参数就是方法对象,在Java、Ruby等面向对象语言中,需要用Reflection获取方法对象。这个方法对象就相当于函数式编程的函数对象。在函数式编程中,函数对象属于“一等公民”,函数对象的获取不需要经过Reflection机制。所以,函数式编程对AOP的支持,比面向对象编程更好。由此我们看到,AOP对应的问题领域确实超出了OOP的力所能及的范围。OOP只能处理同一个类体系内的同一个方法签名的截获和包装工作,一旦涉及到一个类的多个不同方法,或者多个不同类体系的不同方法,OOP就黔驴技穷,无能为力了。
使用Method Reflection的方式截获任何方法对象,是AOP的常用实现手段之一。另一个常见手段就是自动代码生成了。这也回答了前面提出的问题二——如何在应用系统中大规模使用AOP。
Proxy Pattern + Method Reflection + 自动代码生成这样一个三元组合,就是AOP的基本实现原理。Proxy Pattern 和 Method Reflection,前面已经做了阐述,下面我们来讲解自动代码生成。
首先,AOP需要定义一种Aspect描述的DSL。Aspect DSL主要用来描述这样的内容:“用TransactionProxy包装截获business目录下的所有类的公共业务方法”、“ 用SecurityProxy包装截获所有Login/Logout开头的类的所有公共方法”、“用LogProxy包装截获所有文件的所有方法”等等。Aspect DSL的形式有多种多样。有的是一种类似Java的语法,比如AspectJ;有的是XML格式或者各种脚本语言,比如,Spring AOP等。
有了Aspect DSL,AOP处理程序就可以生成代码了。[b]AOP生成代码有三种可能方式:
(1)静态编译时期,源代码生成。为每个符合条件的类方法产生对应的Proxy对象。AspectJ以前就是这种方式。
(2)静态编译时期,处理编译后的字节码。Java、Python之类的虚拟机语言都有一种中间代码(Java的中间代码叫做字节码),AOP处理程序可以分析字节码,并直接产生字节码形式的Proxy。这种方式也叫做静态字节码增强。AspectJ也支持这种方式。Java有一些开源项目,比如 ASM、Cglib等,可以分析并生成Java字节码。这些开源项目不仅可以静态分析增强字节码,还可以在程序运行期动态分析增强字节码。很多AOP项目,比如Spring AOP,都采用ASM/Cglib处理字节码。
(3)动态运行时期,即时处理装载到虚拟机内部的类结构字节码。这也叫做动态增强。比如,Spring AOP。如前所述,Spring AOP使用ASM/Cglib之类的处理字节码的开源项目。Java运行库本身也提供了类似于ASM/Cglib的简单的动态处理字节码的API,叫做 Dynamic Proxy。
以上就是AOP的实现原理:Proxy Pattern + Method Reflection + Aspect DSL + 自动代码生成。
总体来说,实现AOP的便利程度,函数式编程语言 > 动态类型语言 > 静态类型语言。当然,这个不等式并不是绝对的。有些动态类型语言提供了丰富强大的语法特性,实现AOP的便利程度,可能要超过函数式编程语言。[/b]
评:
AOP实现原理
AOP的实现原理可以看作是Proxy/Decorator设计模式的泛化。我们先来看Proxy模式的简单例子。
Proxy {
innerObject; // 真正的对象
f1() {
// 做一些额外的事情
innerObject.f1(); // 调用真正的对象的对应方法
// 做一些额外的事情
}
}
在Python、Ruby等动态类型语言中,只要实现了f1()方法的类,都可以被Proxy包装。在Java等静态类型语言中,则要求Proxy和被包装对象实现相同的接口。动态语言实现Proxy模式要比静态语言容易得多,动态语言实现AOP也要比静态语言容易得多。假设我们用Proxy包装了10个类,我们通过调用Proxy的f1()方法来调用这10个类的f1()方法,这样,所有的f1()调用都会执行同样的一段“额外的工作”,从而实现了“所有被Proxy包装的类,都执行一段同样的额外工作”的任务。这段“额外的工作”可能是进行日志记录,权限检查,事务管理等常见工作。
Proxy模式是可以叠加的。我们可以定义多种完成特定方面任务(Aspect),比如,我们可以定义LogProxy、SecurityProxy、TransactionProxy,分别进行日志管理、权限管理、事务管理。
LogProxy {
f1(){
// 记录方法进入信息
innerObject.f1();// 调用真正的对象的对应方法
// 记录方法退出信息
}
}
SecurityProxy {
f1(){
// 进行权限验证
innerObject.f1();// 调用真正的对象的对应方法
}
}
TransactonProxy {
f1(){
Open Transaction
innerObject.f1();// 调用真正的对象的对应方法
Close Transaction
}
}
根据AOP的惯用叫法,上述的这些Proxy也叫做Advice。这些Proxy(or Advice)可以按照一定的内外顺序套起来,最外面的Proxy会最先执行。包装f1()方法,也叫做截获(Intercept)f1()方法。Proxy/Advice有时候也叫做Interceptor。
看到这里,读者可能会产生两个问题。
问题一:上述代码采用的Proxy模式只是面向对象的特性,怎么会扯上一个新概念“面向方面(AOP)”呢?
问题二:Proxy模式虽然避免了重复“额外工作”代码的问题,但是,每个相关类都要被Proxy包装,这个工作也是很烦人。AOP Proxy如何能在应用程序中大规模使用呢?
下面我们来解答着两个问题。
对于问题一,我们来看一个复杂一点的例子。假设被包装对象有f1()和f2()两个方法都要被包装。
RealObject{
f1() {…}
f2() {…}
}
这个时候,我们应该如何做?难道让Proxy也定义f1()和f2()两个方法?就象下面代码这样?
Proxy {
innerObject; // 真正的对象
f1() {
// 做一些额外的事情
innerObject.f1(); // 调用真正的对象的对应方法
// 做一些额外的事情
}
f2() {
// 做一些额外的事情
innerObject.f2(); // 调用真正的对象的对应方法
// 做一些额外的事情
}
}
这样做有几个不利之处。一是会造成代码重复,Proxy的f1()和f2()里面的“做一些额外的事情”代码重复。二是难以扩展,被包装对象可能有多个不同的方法,不同的被包装对象需要被包装的方法也可能不同。现在的问题就变成,“Proxy如何才能包装截获任何类的任何方法?”
答案呼之欲出。对,就是Reflection。Java、Python、Ruby都支持Reflection,都支持Method(方法)对象。那么我们就利用Method Reflection编写一个能够解惑任何类的任何方法的Proxy/Advice/Interceptor。
MethodInterceptor{
around( method ){
// 做些额外的工作
method.invoke(…); // 调用真正的对象方法
// 做些额外的工作
}
}
上述的MethodInterceptor就可以分别包装和截获f1()和f2()两个方法。
这里的method参数就是方法对象,在Java、Ruby等面向对象语言中,需要用Reflection获取方法对象。这个方法对象就相当于函数式编程的函数对象。在函数式编程中,函数对象属于“一等公民”,函数对象的获取不需要经过Reflection机制。所以,函数式编程对AOP的支持,比面向对象编程更好。由此我们看到,AOP对应的问题领域确实超出了OOP的力所能及的范围。OOP只能处理同一个类体系内的同一个方法签名的截获和包装工作,一旦涉及到一个类的多个不同方法,或者多个不同类体系的不同方法,OOP就黔驴技穷,无能为力了。
使用Method Reflection的方式截获任何方法对象,是AOP的常用实现手段之一。另一个常见手段就是自动代码生成了。这也回答了前面提出的问题二——如何在应用系统中大规模使用AOP。
Proxy Pattern + Method Reflection + 自动代码生成这样一个三元组合,就是AOP的基本实现原理。Proxy Pattern 和 Method Reflection,前面已经做了阐述,下面我们来讲解自动代码生成。
首先,AOP需要定义一种Aspect描述的DSL。Aspect DSL主要用来描述这样的内容:“用TransactionProxy包装截获business目录下的所有类的公共业务方法”、“ 用SecurityProxy包装截获所有Login/Logout开头的类的所有公共方法”、“用LogProxy包装截获所有文件的所有方法”等等。Aspect DSL的形式有多种多样。有的是一种类似Java的语法,比如AspectJ;有的是XML格式或者各种脚本语言,比如,Spring AOP等。
有了Aspect DSL,AOP处理程序就可以生成代码了。[b]AOP生成代码有三种可能方式:
(1)静态编译时期,源代码生成。为每个符合条件的类方法产生对应的Proxy对象。AspectJ以前就是这种方式。
(2)静态编译时期,处理编译后的字节码。Java、Python之类的虚拟机语言都有一种中间代码(Java的中间代码叫做字节码),AOP处理程序可以分析字节码,并直接产生字节码形式的Proxy。这种方式也叫做静态字节码增强。AspectJ也支持这种方式。Java有一些开源项目,比如 ASM、Cglib等,可以分析并生成Java字节码。这些开源项目不仅可以静态分析增强字节码,还可以在程序运行期动态分析增强字节码。很多AOP项目,比如Spring AOP,都采用ASM/Cglib处理字节码。
(3)动态运行时期,即时处理装载到虚拟机内部的类结构字节码。这也叫做动态增强。比如,Spring AOP。如前所述,Spring AOP使用ASM/Cglib之类的处理字节码的开源项目。Java运行库本身也提供了类似于ASM/Cglib的简单的动态处理字节码的API,叫做 Dynamic Proxy。
以上就是AOP的实现原理:Proxy Pattern + Method Reflection + Aspect DSL + 自动代码生成。
总体来说,实现AOP的便利程度,函数式编程语言 > 动态类型语言 > 静态类型语言。当然,这个不等式并不是绝对的。有些动态类型语言提供了丰富强大的语法特性,实现AOP的便利程度,可能要超过函数式编程语言。[/b]