`

AOP实现原理:从命令式编程和声明式编程说起

阅读更多

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

如果说命……―Domain Specific Language,简称DSL)的概念有相通之处。DSL主要是指一些对应专门领域的高层编程语言,和通用编程语言的概念相对。DSL对应的专门领域(Domain)一般比较狭窄,或者对应于某个行业,或者对应于某一类具体应用程序,比如数据库等。

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

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

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

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

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处理程序就可以生成代码了。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的便利程度,可能要超过函数式编程语言。

 

原文:http://developer.51cto.com/art/200906/130799.htm

分享到:
评论

相关推荐

    Spring AOP面向方面编程原理:AOP概念

    ### Spring AOP面向方面编程原理:AOP概念详解 #### 一、引言 随着软件系统的日益复杂,传统的面向对象编程(OOP)逐渐暴露出难以应对某些横切关注点(cross-cutting concerns)的问题。为了解决这一挑战,面向方面编程...

    spring aop实现原理

    NULL 博文链接:https://zhang-yingjie-qq-com.iteye.com/blog/319927

    注解方式实现AOP编程

    在Java开发中,面向切面编程(Aspect Oriented Programming,简称AOP)是一种重要的编程范式,它允许程序员将关注点分离,使代码更模块化,更易于维护。AOP通常用于实现横切关注点,如日志记录、事务管理、权限检查...

    反射实现 AOP 动态代理模式(Spring AOP 的实现原理)

    面向切面编程(AOP)是一种编程范式,旨在将横切关注点(如日志、安全等)与业务逻辑分离,从而提高模块化。AOP通过预定义的“切面”对横切关注点进行模块化,从而可以在不修改业务逻辑代码的情况下增加新功能。动态...

    深入解析Spring AOP源码:从原理到实现,全方位掌握Java AOP编程精髓

    Spring AOP,全称为Spring的面向切面编程,是Spring框架的重要组成部分,旨在实现关注点的分离,提高代码的可维护性和灵活性。AOP的核心思想是将横切关注点(如日志、事务管理)从核心业务逻辑中解耦出来,形成独立...

    Spring AOP面向方面编程原理Spring AOP面向方面编程原理

    ### Spring AOP面向方面编程原理详解 #### 一、引言与定义 Spring AOP(Aspect Oriented Programming)是Spring框架中的一个核心组件,用于实现面向切面编程。AOP是一种编程范式,旨在将横切关注点(Cross-cutting...

    Hibernate编程式事务与Spring Aop的声明式事务(spring与hibernate集成)

    本篇文章将重点讨论如何在Spring框架中集成Hibernate,并探讨编程式事务管理和Spring AOP的声明式事务。 首先,让我们理解Spring与Hibernate的集成。为了整合这两个库,你需要在Spring配置文件中定义Hibernate的...

    Spring AOP源码深度解析:掌握Java高级编程核心技术

    Spring AOP(面向切面编程)是Spring框架中的核心组件,它通过代理模式实现了对程序逻辑的非侵入式增强,使得开发者可以集中精力于业务逻辑,而将横切关注点(如日志、事务管理)剥离出来。本文将深入探讨Spring AOP...

    PHP-设计模式: AOP切面编程,DI依赖注入,DIC容器DEMO。简单易懂哦

    本篇我们将聚焦于“PHP设计模式”中的两个核心概念:AOP(面向切面编程)和DI(依赖注入),以及与之相关的DIC(依赖注入容器)。 首先,我们来理解一下“面向切面编程”(AOP)。在传统的面向对象编程中,我们通常...

    Spring 代理 Aop 实现原理讲解.docx

    Spring的事务管理可以通过编程式(如`PlatformTransactionManager`)和声明式(`@Transactional`注解)两种方式实现,提供了统一的事务管理API,简化了事务处理。 11. **Spring与Mybatis整合** Spring通过...

    使用动态代理演示Spring的AOP编程原理

    为了说明Spring的AOP原理,本人使用代理模式中的动态代理完成演示AOP编程的原理的演示。相信,如果你耐心看完整个程序(几乎一行注释一行代码),那么你对Spring这个东西就不是觉得有什么神秘了! 阅读对象:凡是喜爱...

    C++ AOP编程理论

    C++中没有原生的AOP支持,但是可以通过一些编程技巧和设计模式来近似实现AOP。例如,可以通过继承和多态性来模拟切面的行为,或者利用函数指针、模板编程以及现代C++中的lambda表达式和函数对象来实现。此外,还有...

    反射实现 AOP 动态代理模式(Spring AOP 的实现 原理) - Java 例子 -

    本文将深入探讨Spring AOP的实现原理,以及如何使用反射来实现动态代理模式。 首先,我们需要了解AOP的基本概念。AOP的核心思想是切面,它包含两个主要部分:切点(Pointcut)和通知(Advice)。切点定义了在程序...

    代理机制及AOP原理实现

    在Java编程领域,代理机制和面向切面编程(AOP)是两个重要的概念,它们在Spring框架中得到了广泛的应用。本文将深入探讨这两个主题,并结合JDK的反射和CGLIB库来阐述其实现原理。 首先,我们来看动态代理。动态...

    Spring AOP实现机制

    - `<aop:aspect>`标签用于定义完整的切面,包括切入点和通知。 - **注解配置**: - 使用`@Aspect`注解定义切面类,`@Pointcut`定义切入点方法,`@Before`、`@After`、`@Around`等定义不同类型的通知。 ### 5. ...

    aop:aspect

    Spring AOP提供了一种声明式的方式来实现这些关注点,而`aop:aspect`就是这个机制的核心部分。 在Spring的XML配置中,`<aop:aspect>`元素定义了一个切面,它可以包含多个通知(advises),如前置通知(before ...

    AOP联盟:aopalliance.jar API包

    AOP联盟:aopalliance.jar API包a set of AOP Java interface .

    基于Spring AOP 的声明式和编程式分布式锁,支持RedisTemplate、Redisson、Zookeeper

    本文将深入探讨如何在Spring Boot环境中利用AOP实现声明式和编程式的分布式锁,并结合RedisTemplate、Redisson及Zookeeper这三种常见中间件进行详细讲解。 首先,让我们了解Spring AOP的基本概念。AOP(Aspect-...

    揭秘AOP:切面编程的综合指南配套演示项目资源

    本资源包是《揭秘AOP:切面编程的综合指南》一书的配套演示项目,旨在通过实际操作帮助读者深入理解AOP的概念、原理以及在Spring中的实现。 **AOP核心概念** 1. **切面(Aspect)**:切面是关注点的一个模块化,它...

    Spring Aop的底层实现技术 --- Jdk动态代理原理

    通过使用 Proxy 和 InvocationHandler,可以将性能监视的横切代码从业务类中移除,并将其安置在 PerformaceHandler 中,从而实现了 AOP 式的编程。 在使用 JDK 动态代理时,需要实现 InvocationHandler 接口,并在 ...

Global site tag (gtag.js) - Google Analytics