Spring之AOP原理_动态代理

       面向方面编程(Aspect Oriented Programming,简称AOP)是一种声明式编程(Declarative Programming)。声明式编程是和命令式编程(Imperative Programming)相对的概念。咱们平时使用的编程语言,好比C++JavaRubyPython等,都属命令式编程。命令式编程的意思是,程序员须要一步步写清楚程序须要如何作什么(How to do What)。声明式编程的意思是,程序员不须要一步步告诉程序如何作,只须要告诉程序在哪些地方作什么(Where to do What)。比起命令式编程来,声明式编程是在一个更高的层次上编程。声明式编程语言是更高级的语言。声明式编程一般处理一些总结性、总览性的工做,不适合作顺序相关的细节相关的底层工做。java

         若是说命令式编程是拼杀在第一线的基层工做人员,声明式编程就是总设计师、规则制定者。声明式编程语言的概念,和领域专用语言(Domain Specific Language,简称DSL)的概念有相通之处。DSL主要是指一些对应专门领域的高层编程语言,和通用编程语言的概念相对。DSL对应的专门领域(Domain)通常比较狭窄,或者对应于某个行业,或者对应于某一类具体应用程序,好比数据库等。程序员

        最多见的DSL就是关系数据库的结构化数据查询语言SQL。同时,SQL也是一门声明式语言。SQL只须要告诉数据库,处理符合必定条件的数据,而不须要本身一步步判断每一条数据是否符合条件。SQL的形式通常是 select … where …,update … where …,delete … where …。固然,这样一来,不少基层工做,SQL作不了。所以,大部分数据库都提供了另外的命令式编程语言,用来编写存储过程等,以便处理一些更加细节的工做。数据库

        常见的DSL还有规则引擎(Rule Engine)语言、工做流(Workflow)语言等。规则引擎和工做流同时带有命令式编程和声明式编程的特色。规则引擎容许用户按照优先级定义一系列条件组合,并定义对知足条件的数据的处理过程。工做流也大体相似。工做流把最基本的条件判断和循环语句的常见组合,定义为更加高级复杂的经常使用程序流程逻辑块。用户能够用这些高级流程块组合更加复杂的流程块,从而定义更加复杂的流程跳转条件。用户也能够定义当程序运行上下文知足必定条件的时候,应该作什么样的处理工做。规则引擎和工做流的语言形式有多是XML格式,也有多是RubyPythonJavascript等脚本格式。我我的比较倾向于脚本格式,由于XML适合表达结构化数据,而不擅长表达逻辑流程。固然,XML格式的好处也是显而易见的。解析器能够很容易分析XML文件的结构,XML定义的条件或者程序流程均可以很方便地做为数据来处理。编程

           介绍了声明式编程和DSL以后,咱们来看本章题目表达的内容——AOPAOP是声明式编程,AOP语言也能够看做是DSLAOP语言对应的专门领域(Domain)就是程序结构的方方面面(Aspect),好比程序的类、方法、成员变量等结构,以及针对这些程序结构的通用工做处理,好比日志管理、权限管理、事务管理等。设计模式

AOP处理的工做内容通常都是这样的一些总结性工做:“我想让全部的数据库类都自动进行数据库映射”、“我想打印出全部业务类的工做流程日志”、“我想给全部关键业务方法都加上事务管理功能”、“我想给全部敏感数据处理方法都加上安全管理受权机制”等等。 
         下面咱们介绍AOP的实现原理和使用方法。安全

AOP实现原理编程语言

AOP的实现原理能够看做是Proxy/Decorator设计模式的泛化。咱们先来看Proxy模式的简单例子。函数式编程

 Proxy { 
     innerObject; // 真正的对象 
     f1() { 
         // 作一些额外的事情 
  
        innerObject.f1(); // 调用真正的对象的对应方法 
 
           // 作一些额外的事情 
     } 
}


PythonRuby等动态类型语言中,只要实现了f1()方法的类,均可以被Proxy包装。在Java等静态类型语言中,则要求Proxy和被包装对象实现相同的接口。动态语言实现Proxy模式要比静态语言容易得多,动态语言实现AOP也要比静态语言容易得多。假设咱们用Proxy包装了10个类,咱们经过调用Proxyf1()方法来调用这10个类的f1()方法,这样,全部的f1()调用都会执行一样的一段“额外的工做”,从而实现了“全部被Proxy包装的类,都执行一段一样的额外工做”的任务。这段“额外的工做”多是进行日志记录,权限检查,事务管理等常见工做。函数

Proxy模式是能够叠加的。咱们能够定义多种完成特定方面任务(Aspect),好比,咱们能够定义LogProxySecurityProxyTransactionProxy,分别进行日志管理、权限管理、事务管理。spa

 LogProxy { 
      f1(){ 
           // 记录方法进入信息 
  
            innerObject.f1();// 调用真正的对象的对应方法 
          // 记录方法退出信息 
   } 
} 
 SecurityProxy { 
      f1(){ 
          // 进行权限验证 
 
          innerObject.f1();// 调用真正的对象的对应方法 
       } 
} 
TransactonProxy { 
      f1(){ 
          Open Transaction 
          innerObject.f1();// 调用真正的对象的对应方法 
         Close Transaction 
      } 
}


根据AOP的惯用叫法,上述的这些Proxy也叫作Advice。这些Proxyor Advice)能够按照必定的内外顺序套起来,最外面的Proxy会最早执行。包装f1()方法,也叫作截获(Interceptf1()方法。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(); // 调用真正的对象的对应方法 
       
        // 作一些额外的事情 
  }  
}


这样作有几个不利之处。一是会形成代码重复,Proxyf1()f2()里面的“作一些额外的事情”代码重复。二是难以扩展,被包装对象可能有多个不一样的方法,不一样的被包装对象须要被包装的方法也可能不一样。如今的问题就变成,“Proxy如何才能包装截获任何类的任何方法?” 
答案呼之欲出。对,就是ReflectionJavaPythonRuby都支持Reflection,都支持Method(方法)对象。那么咱们就利用Method Reflection编写一个可以解惑任何类的任何方法的Proxy/Advice/Interceptor

MethodInterceptor{ 

    around( method ){ 
        // 作些额外的工做 
        
       method.invoke(…); // 调用真正的对象方法 
       
        // 作些额外的工做 
    } 
}


上述的MethodInterceptor就能够分别包装和截获f1()f2()两个方法。

这里的method参数就是方法对象,在JavaRuby等面向对象语言中,须要用Reflection获取方法对象。这个方法对象就至关于函数式编程的函数对象。在函数式编程中,函数对象属于“一等公民”,函数对象的获取不须要通过Reflection机制。因此,函数式编程对AOP的支持,比面向对象编程更好。由此咱们看到,AOP对应的问题领域确实超出了OOP的力所能及的范围。OOP只能处理同一个类体系内的同一个方法签名的截获和包装工做,一旦涉及到一个类的多个不一样方法,或者多个不一样类体系的不一样方法,OOP就黔驴技穷,无能为力了。

使用Method Reflection的方式截获任何方法对象,是AOP的经常使用实现手段之一。另外一个常见手段就是自动代码生成了。这也回答了前面提出的问题二——如何在应用系统中大规模使用AOP

Proxy Pattern + Method Reflection + 自动代码生成这样一个三元组合,就是AOP的基本实现原理。Proxy Pattern 和 Method Reflection,前面已经作了阐述,下面咱们来说解自动代码生成。

首先,AOP须要定义一种Aspect描述的DSLAspect DSL主要用来描述这样的内容:“用TransactionProxy包装截获business目录下的全部类的公共业务方法”、“ 用SecurityProxy包装截获全部Login/Logout开头的类的全部公共方法”、“用LogProxy包装截获全部文件的全部方法”等等。Aspect DSL的形式有多种多样。有的是一种相似Java的语法,好比AspectJ;有的是XML格式或者各类脚本语言,好比,Spring AOP等。

有了Aspect DSLAOP处理程序就能够生成代码了。AOP生成代码有三种可能方式:

1)静态编译时期,源代码生成。为每一个符合条件的类方法产生对应的Proxy对象。AspectJ之前就是这种方式。

2)静态编译时期,处理编译后的字节码。JavaPython之类的虚拟机语言都有一种中间代码(Java的中间代码叫作字节码),AOP处理程序能够分析字节码,并直接产生字节码形式的Proxy。这种方式也叫作静态字节码加强。AspectJ也支持这种方式。Java有一些开源项目,好比 ASMCglib等,能够分析并生成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的便利程度,可能要超过函数式编程语言。

相关文章
相关标签/搜索